diff options
Diffstat (limited to 'core')
534 files changed, 25926 insertions, 8400 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 2620c44..cbc8150 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -462,7 +462,9 @@ public abstract class AccessibilityService extends Service { * anything behind it, then only the modal window will be reported * (assuming it is the top one). For convenience the returned windows * are ordered in a descending layer order, which is the windows that - * are higher in the Z-order are reported first. + * are higher in the Z-order are reported first. Since the user can always + * interact with the window that has input focus by typing, the focused + * window is always returned (even if covered by a modal window). * <p> * <strong>Note:</strong> In order to access the windows your service has * to declare the capability to retrieve window content by setting the diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 4f9ba59..4edb0c6 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -286,8 +286,8 @@ public class AccessibilityServiceInfo implements Parcelable { /** * This flag indicates to the system that the accessibility service wants * to access content of all interactive windows. An interactive window is a - * window that can be touched by a sighted user when explore by touch is not - * enabled. If this flag is not set your service will not receive + * window that has input focus or can be touched by a sighted user when explore + * by touch is not enabled. If this flag is not set your service will not receive * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED} * events, calling AccessibilityService{@link AccessibilityService#getWindows() * AccessibilityService.getWindows()} will return an empty list, and {@link diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 12fcdcf..806a55b 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -359,7 +359,29 @@ public class AccountManager { */ public AuthenticatorDescription[] getAuthenticatorTypes() { try { - return mService.getAuthenticatorTypes(); + return mService.getAuthenticatorTypes(UserHandle.getCallingUserId()); + } catch (RemoteException e) { + // will never happen + throw new RuntimeException(e); + } + } + + /** + * @hide + * Lists the currently registered authenticators for a given user id. + * + * <p>It is safe to call this method from the main thread. + * + * <p>The caller has to be in the same user or have the permission + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. + * + * @return An array of {@link AuthenticatorDescription} for every + * authenticator known to the AccountManager service. Empty (never + * null) if no authenticators are known. + */ + public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) { + try { + return mService.getAuthenticatorTypes(userId); } catch (RemoteException e) { // will never happen throw new RuntimeException(e); @@ -389,6 +411,28 @@ public class AccountManager { /** * @hide + * Lists all accounts of any type registered on the device for a given + * user id. Equivalent to getAccountsByType(null). + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS}. + * + * @return An array of {@link Account}, one for each account. Empty + * (never null) if no accounts have been added. + */ + public Account[] getAccountsAsUser(int userId) { + try { + return mService.getAccountsAsUser(null, userId); + } catch (RemoteException e) { + // won't ever happen + throw new RuntimeException(e); + } + } + + /** + * @hide * For use by internal activities. Returns the list of accounts that the calling package * is authorized to use, particularly for shared accounts. * @param packageName package name of the calling app. diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 86e279f..1373dc8 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -29,7 +29,7 @@ import android.os.Bundle; interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); - AuthenticatorDescription[] getAuthenticatorTypes(); + AuthenticatorDescription[] getAuthenticatorTypes(int userId); Account[] getAccounts(String accountType); Account[] getAccountsForPackage(String packageName, int uid); Account[] getAccountsByTypeForPackage(String type, String packageName); diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 933135d..0f5e954 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -17,14 +17,22 @@ package android.animation; import android.content.Context; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.content.res.Resources.NotFoundException; +import android.graphics.Path; import android.util.AttributeSet; +import android.util.Log; +import android.util.PathParser; import android.util.StateSet; import android.util.TypedValue; import android.util.Xml; +import android.view.InflateException; import android.view.animation.AnimationUtils; + +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -41,7 +49,7 @@ import java.util.ArrayList; * <em>something</em> file.) */ public class AnimatorInflater { - + private static final String TAG = "AnimatorInflater"; /** * These flags are used when parsing AnimatorSet objects */ @@ -53,9 +61,12 @@ public class AnimatorInflater { */ private static final int VALUE_TYPE_FLOAT = 0; private static final int VALUE_TYPE_INT = 1; + private static final int VALUE_TYPE_PATH = 2; private static final int VALUE_TYPE_COLOR = 4; private static final int VALUE_TYPE_CUSTOM = 5; + private static final boolean DBG_ANIMATOR_INFLATER = false; + /** * Loads an {@link Animator} object from a resource * @@ -66,11 +77,26 @@ public class AnimatorInflater { */ public static Animator loadAnimator(Context context, int id) throws NotFoundException { + return loadAnimator(context.getResources(), context.getTheme(), id); + } + + /** + * Loads an {@link Animator} object from a resource + * + * @param resources The resources + * @param theme The theme + * @param id The resource id of the animation to load + * @return The animator object reference by the specified id + * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded + * @hide + */ + public static Animator loadAnimator(Resources resources, Theme theme, int id) + throws NotFoundException { XmlResourceParser parser = null; try { - parser = context.getResources().getAnimation(id); - return createAnimatorFromXml(context, parser); + parser = resources.getAnimation(id); + return createAnimatorFromXml(resources, theme, parser); } catch (XmlPullParserException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException("Can't load animation resource ID #0x" + @@ -139,7 +165,7 @@ public class AnimatorInflater { int stateIndex = 0; for (int i = 0; i < attributeCount; i++) { int attrName = attributeSet.getAttributeNameResource(i); - if (attrName == com.android.internal.R.attr.animation) { + if (attrName == R.attr.animation) { animator = loadAnimator(context, attributeSet.getAttributeResourceValue(i, 0)); } else { @@ -150,7 +176,8 @@ public class AnimatorInflater { } if (animator == null) { - animator = createAnimatorFromXml(context, parser); + animator = createAnimatorFromXml(context.getResources(), + context.getTheme(), parser); } if (animator == null) { @@ -166,150 +193,242 @@ public class AnimatorInflater { } } - private static Animator createAnimatorFromXml(Context c, XmlPullParser parser) - throws XmlPullParserException, IOException { - return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0); - } - - private static Animator createAnimatorFromXml(Context c, XmlPullParser parser, - AttributeSet attrs, AnimatorSet parent, int sequenceOrdering) - throws XmlPullParserException, IOException { - - Animator anim = null; - ArrayList<Animator> childAnims = null; - - // Make sure we are on a start tag. - int type; - int depth = parser.getDepth(); - - while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { + /** + * PathDataEvaluator is used to interpolate between two paths which are + * represented in the same format but different control points' values. + * The path is represented as an array of PathDataNode here, which is + * fundamentally an array of floating point numbers. + */ + private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> { + private PathParser.PathDataNode[] mNodeArray; + + /** + * Create a PathParser.PathDataNode[] that does not reuse the animated value. + * Care must be taken when using this option because on every evaluation + * a new <code>PathParser.PathDataNode[]</code> will be allocated. + */ + private PathDataEvaluator() {} + + /** + * Create a PathDataEvaluator that reuses <code>nodeArray</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 nodeArray The array to modify and return from <code>evaluate</code>. + */ + public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { + mNodeArray = nodeArray; + } - if (type != XmlPullParser.START_TAG) { - continue; + @Override + public PathParser.PathDataNode[] evaluate(float fraction, + PathParser.PathDataNode[] startPathData, + PathParser.PathDataNode[] endPathData) { + if (!PathParser.canMorph(startPathData, endPathData)) { + throw new IllegalArgumentException("Can't interpolate between" + + " two incompatible pathData"); } - String name = parser.getName(); - - if (name.equals("objectAnimator")) { - anim = loadObjectAnimator(c, attrs); - } else if (name.equals("animator")) { - anim = loadAnimator(c, attrs, null); - } else if (name.equals("set")) { - anim = new AnimatorSet(); - TypedArray a = c.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AnimatorSet); - int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering, - TOGETHER); - createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim, ordering); - a.recycle(); - } else { - throw new RuntimeException("Unknown animator name: " + parser.getName()); + if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) { + mNodeArray = PathParser.deepCopyNodes(startPathData); } - if (parent != null) { - if (childAnims == null) { - childAnims = new ArrayList<Animator>(); - } - childAnims.add(anim); + for (int i = 0; i < startPathData.length; i++) { + mNodeArray[i].interpolatePathDataNode(startPathData[i], + endPathData[i], fraction); } - } - if (parent != null && childAnims != null) { - Animator[] animsArray = new Animator[childAnims.size()]; - int index = 0; - for (Animator a : childAnims) { - animsArray[index++] = a; - } - if (sequenceOrdering == TOGETHER) { - parent.playTogether(animsArray); - } else { - parent.playSequentially(animsArray); - } - } - - return anim; - - } - private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs) - throws NotFoundException { - - ObjectAnimator anim = new ObjectAnimator(); - - loadAnimator(context, attrs, anim); - - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator); - - String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName); - - anim.setPropertyName(propertyName); - - a.recycle(); - - return anim; + return mNodeArray; + } } /** - * Creates a new animation whose parameters come from the specified context and - * attributes set. - * - * @param context the application environment - * @param attrs the set of attributes holding the animation parameters + * @param anim Null if this is a ValueAnimator, otherwise this is an + * ObjectAnimator + * @param arrayAnimator Incoming typed array for Animator's attributes. + * @param arrayObjectAnimator Incoming typed array for Object Animator's + * attributes. */ - private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim) - throws NotFoundException { + private static void parseAnimatorFromTypeArray(ValueAnimator anim, + TypedArray arrayAnimator, TypedArray arrayObjectAnimator) { + long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300); - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator); + long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0); - long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300); - - long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0); - - int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType, + int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_FLOAT); if (anim == null) { anim = new ValueAnimator(); } - TypeEvaluator evaluator = null; - int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom; - int valueToIndex = com.android.internal.R.styleable.Animator_valueTo; + TypeEvaluator evaluator = null; boolean getFloats = (valueType == VALUE_TYPE_FLOAT); - TypedValue tvFrom = a.peekValue(valueFromIndex); + TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom); boolean hasFrom = (tvFrom != null); int fromType = hasFrom ? tvFrom.type : 0; - TypedValue tvTo = a.peekValue(valueToIndex); + TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo); boolean hasTo = (tvTo != null); int toType = hasTo ? tvTo.type : 0; - if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && - (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || - (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) && - (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { - // special case for colors: ignore valueType and get ints - getFloats = false; - evaluator = ArgbEvaluator.getInstance(); + // TODO: Further clean up this part of code into 4 types : path, color, + // integer and float. + if (valueType == VALUE_TYPE_PATH) { + evaluator = setupAnimatorForPath(anim, arrayAnimator); + } else { + // Integer and float value types are handled here. + if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || + (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { + // special case for colors: ignore valueType and get ints + getFloats = false; + evaluator = ArgbEvaluator.getInstance(); + } + setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType); + } + + anim.setDuration(duration); + anim.setStartDelay(startDelay); + + if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) { + anim.setRepeatCount( + arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0)); + } + if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) { + anim.setRepeatMode( + arrayAnimator.getInt(R.styleable.Animator_repeatMode, + ValueAnimator.RESTART)); + } + if (evaluator != null) { + anim.setEvaluator(evaluator); + } + + if (arrayObjectAnimator != null) { + setupObjectAnimator(anim, arrayObjectAnimator, getFloats); } + } + /** + * Setup the Animator to achieve path morphing. + * + * @param anim The target Animator which will be updated. + * @param arrayAnimator TypedArray for the ValueAnimator. + * @return the PathDataEvaluator. + */ + private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim, + TypedArray arrayAnimator) { + TypeEvaluator evaluator = null; + String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom); + String toString = arrayAnimator.getString(R.styleable.Animator_valueTo); + PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); + PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); + + if (nodesFrom != null) { + if (nodesTo != null) { + anim.setObjectValues(nodesFrom, nodesTo); + if (!PathParser.canMorph(nodesFrom, nodesTo)) { + throw new InflateException(arrayAnimator.getPositionDescription() + + " Can't morph from " + fromString + " to " + toString); + } + } else { + anim.setObjectValues((Object)nodesFrom); + } + evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); + } else if (nodesTo != null) { + anim.setObjectValues((Object)nodesTo); + evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + } + + if (DBG_ANIMATOR_INFLATER && evaluator != null) { + Log.v(TAG, "create a new PathDataEvaluator here"); + } + + return evaluator; + } + + /** + * Setup ObjectAnimator's property or values from pathData. + * + * @param anim The target Animator which will be updated. + * @param arrayObjectAnimator TypedArray for the ObjectAnimator. + * @param getFloats True if the value type is float. + */ + private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator, + boolean getFloats) { + ObjectAnimator oa = (ObjectAnimator) anim; + String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData); + + // Note that if there is a pathData defined in the Object Animator, + // valueFrom / valueTo will be ignored. + if (pathData != null) { + String propertyXName = + arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName); + String propertyYName = + arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName); + + if (propertyXName == null && propertyYName == null) { + throw new InflateException(arrayObjectAnimator.getPositionDescription() + + " propertyXName or propertyYName is needed for PathData"); + } else { + Path path = PathParser.createPathFromPathData(pathData); + Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats); + PropertyValuesHolder x = null; + PropertyValuesHolder y = null; + if (propertyXName != null) { + x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]); + } + if (propertyYName != null) { + y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]); + } + if (x == null) { + oa.setValues(y); + } else if (y == null) { + oa.setValues(x); + } else { + oa.setValues(x, y); + } + } + } else { + String propertyName = + arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName); + oa.setPropertyName(propertyName); + } + } + + /** + * Setup ValueAnimator's values. + * This will handle all of the integer, float and color types. + * + * @param anim The target Animator which will be updated. + * @param arrayAnimator TypedArray for the ValueAnimator. + * @param getFloats True if the value type is float. + * @param hasFrom True if "valueFrom" exists. + * @param fromType The type of "valueFrom". + * @param hasTo True if "valueTo" exists. + * @param toType The type of "valueTo". + */ + private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator, + boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) { + int valueFromIndex = R.styleable.Animator_valueFrom; + int valueToIndex = R.styleable.Animator_valueTo; if (getFloats) { float valueFrom; float valueTo; if (hasFrom) { if (fromType == TypedValue.TYPE_DIMENSION) { - valueFrom = a.getDimension(valueFromIndex, 0f); + valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f); } else { - valueFrom = a.getFloat(valueFromIndex, 0f); + valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f); } if (hasTo) { if (toType == TypedValue.TYPE_DIMENSION) { - valueTo = a.getDimension(valueToIndex, 0f); + valueTo = arrayAnimator.getDimension(valueToIndex, 0f); } else { - valueTo = a.getFloat(valueToIndex, 0f); + valueTo = arrayAnimator.getFloat(valueToIndex, 0f); } anim.setFloatValues(valueFrom, valueTo); } else { @@ -317,9 +436,9 @@ public class AnimatorInflater { } } else { if (toType == TypedValue.TYPE_DIMENSION) { - valueTo = a.getDimension(valueToIndex, 0f); + valueTo = arrayAnimator.getDimension(valueToIndex, 0f); } else { - valueTo = a.getFloat(valueToIndex, 0f); + valueTo = arrayAnimator.getFloat(valueToIndex, 0f); } anim.setFloatValues(valueTo); } @@ -328,21 +447,21 @@ public class AnimatorInflater { int valueTo; if (hasFrom) { if (fromType == TypedValue.TYPE_DIMENSION) { - valueFrom = (int) a.getDimension(valueFromIndex, 0f); + valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f); } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) { - valueFrom = a.getColor(valueFromIndex, 0); + valueFrom = arrayAnimator.getColor(valueFromIndex, 0); } else { - valueFrom = a.getInt(valueFromIndex, 0); + valueFrom = arrayAnimator.getInt(valueFromIndex, 0); } if (hasTo) { if (toType == TypedValue.TYPE_DIMENSION) { - valueTo = (int) a.getDimension(valueToIndex, 0f); + valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f); } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { - valueTo = a.getColor(valueToIndex, 0); + valueTo = arrayAnimator.getColor(valueToIndex, 0); } else { - valueTo = a.getInt(valueToIndex, 0); + valueTo = arrayAnimator.getInt(valueToIndex, 0); } anim.setIntValues(valueFrom, valueTo); } else { @@ -351,40 +470,138 @@ public class AnimatorInflater { } else { if (hasTo) { if (toType == TypedValue.TYPE_DIMENSION) { - valueTo = (int) a.getDimension(valueToIndex, 0f); + valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f); } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && - (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { - valueTo = a.getColor(valueToIndex, 0); + (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueTo = arrayAnimator.getColor(valueToIndex, 0); } else { - valueTo = a.getInt(valueToIndex, 0); + valueTo = arrayAnimator.getInt(valueToIndex, 0); } anim.setIntValues(valueTo); } } } + } - anim.setDuration(duration); - anim.setStartDelay(startDelay); + private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser) + throws XmlPullParserException, IOException { + return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0); + } - if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) { - anim.setRepeatCount( - a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0)); + private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, + AttributeSet attrs, AnimatorSet parent, int sequenceOrdering) + throws XmlPullParserException, IOException { + + Animator anim = null; + ArrayList<Animator> childAnims = null; + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals("objectAnimator")) { + anim = loadObjectAnimator(res, theme, attrs); + } else if (name.equals("animator")) { + anim = loadAnimator(res, theme, attrs, null); + } else if (name.equals("set")) { + anim = new AnimatorSet(); + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.AnimatorSet); + } + int ordering = a.getInt(R.styleable.AnimatorSet_ordering, + TOGETHER); + createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering); + a.recycle(); + } else { + throw new RuntimeException("Unknown animator name: " + parser.getName()); + } + + if (parent != null) { + if (childAnims == null) { + childAnims = new ArrayList<Animator>(); + } + childAnims.add(anim); + } } - if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) { - anim.setRepeatMode( - a.getInt(com.android.internal.R.styleable.Animator_repeatMode, - ValueAnimator.RESTART)); + if (parent != null && childAnims != null) { + Animator[] animsArray = new Animator[childAnims.size()]; + int index = 0; + for (Animator a : childAnims) { + animsArray[index++] = a; + } + if (sequenceOrdering == TOGETHER) { + parent.playTogether(animsArray); + } else { + parent.playSequentially(animsArray); + } } - if (evaluator != null) { - anim.setEvaluator(evaluator); + + return anim; + + } + + private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs) + throws NotFoundException { + ObjectAnimator anim = new ObjectAnimator(); + + loadAnimator(res, theme, attrs, anim); + + return anim; + } + + /** + * Creates a new animation whose parameters come from the specified context + * and attributes set. + * + * @param res The resources + * @param attrs The set of attributes holding the animation parameters + * @param anim Null if this is a ValueAnimator, otherwise this is an + * ObjectAnimator + */ + private static ValueAnimator loadAnimator(Resources res, Theme theme, + AttributeSet attrs, ValueAnimator anim) + throws NotFoundException { + + TypedArray arrayAnimator = null; + TypedArray arrayObjectAnimator = null; + + if (theme != null) { + arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0); + } else { + arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator); + } + + // If anim is not null, then it is an object animator. + if (anim != null) { + if (theme != null) { + arrayObjectAnimator = theme.obtainStyledAttributes(attrs, + R.styleable.PropertyAnimator, 0, 0); + } else { + arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator); + } } + parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator); final int resID = - a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); + arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0); if (resID > 0) { - anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID)); + anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID)); } - a.recycle(); + + arrayAnimator.recycle(); + arrayObjectAnimator.recycle(); return anim; } diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 8947550..da56a77 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -469,7 +469,10 @@ public final class ObjectAnimator extends ValueAnimator { */ 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); + Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]); + return ofPropertyValuesHolder(target, x, y); } /** diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java index bc4843d..810f050 100644 --- a/core/java/android/animation/StateListAnimator.java +++ b/core/java/android/animation/StateListAnimator.java @@ -141,7 +141,7 @@ public class StateListAnimator { return; } if (mLastMatch != null) { - cancel(mLastMatch); + cancel(); } mLastMatch = match; if (match != null) { @@ -151,13 +151,15 @@ public class StateListAnimator { private void start(Tuple match) { match.mAnimator.setTarget(getTarget()); - mRunningAnimator = match.mAnimator; - match.mAnimator.start(); + mRunningAnimator = match.mAnimator.clone(); + mRunningAnimator.start(); } - private void cancel(Tuple lastMatch) { - lastMatch.mAnimator.cancel(); - lastMatch.mAnimator.setTarget(null); + private void cancel() { + if (mRunningAnimator != null) { + mRunningAnimator.cancel(); + mRunningAnimator = null; + } } /** diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f6883e2..83cbaa1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -30,6 +30,7 @@ import com.android.internal.policy.PolicyManager; import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; @@ -929,7 +930,8 @@ public class Activity extends ContextThemeWrapper /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with - * the attribute {@link android.R.attr#persistable} set true. + * the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. * * @param savedInstanceState if the activity is being re-initialized after * previously being shut down then this Bundle contains the data it most @@ -1012,8 +1014,9 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. The {@link - * android.os.PersistableBundle} passed came from the restored PersistableBundle first + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed + * came from the restored PersistableBundle first * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}. * * <p>This method is called between {@link #onStart} and @@ -1111,7 +1114,8 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onPostCreate(Bundle)} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. * * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState} * @param persistentState The data caming from the PersistableBundle first @@ -1352,10 +1356,10 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onSaveInstanceState} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. The {@link - * android.os.PersistableBundle} passed in will be saved and presented in - * {@link #onCreate(Bundle, PersistableBundle)} the first time that this activity - * is restarted following the next device reboot. + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed + * in will be saved and presented in {@link #onCreate(Bundle, PersistableBundle)} + * the first time that this activity is restarted following the next device reboot. * * @param outState Bundle in which to place your saved state. * @param outPersistentState State which will be saved across reboots. @@ -5135,78 +5139,7 @@ public class Activity extends ContextThemeWrapper return onCreateView(name, context, attrs); } - String fname = attrs.getAttributeValue(null, "class"); - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); - if (fname == null) { - fname = a.getString(com.android.internal.R.styleable.Fragment_name); - } - int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID); - String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); - a.recycle(); - - int containerId = parent != null ? parent.getId() : 0; - if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { - throw new IllegalArgumentException(attrs.getPositionDescription() - + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); - } - - // If we restored from a previous state, we may already have - // instantiated this fragment from the state and should use - // that instance instead of making a new one. - Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null; - if (fragment == null && tag != null) { - fragment = mFragments.findFragmentByTag(tag); - } - if (fragment == null && containerId != View.NO_ID) { - fragment = mFragments.findFragmentById(containerId); - } - - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" - + Integer.toHexString(id) + " fname=" + fname - + " existing=" + fragment); - if (fragment == null) { - fragment = Fragment.instantiate(this, fname); - fragment.mFromLayout = true; - fragment.mFragmentId = id != 0 ? id : containerId; - fragment.mContainerId = containerId; - fragment.mTag = tag; - fragment.mInLayout = true; - fragment.mFragmentManager = mFragments; - fragment.onInflate(this, attrs, fragment.mSavedFragmentState); - mFragments.addFragment(fragment, true); - - } else if (fragment.mInLayout) { - // A fragment already exists and it is not one we restored from - // previous state. - throw new IllegalArgumentException(attrs.getPositionDescription() - + ": Duplicate id 0x" + Integer.toHexString(id) - + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) - + " with another fragment for " + fname); - } else { - // This fragment was retained from a previous instance; get it - // going now. - fragment.mInLayout = true; - // If this fragment is newly instantiated (either right now, or - // from last saved state), then give it the attributes to - // initialize itself. - if (!fragment.mRetaining) { - fragment.onInflate(this, attrs, fragment.mSavedFragmentState); - } - mFragments.moveToState(fragment); - } - - if (fragment.mView == null) { - throw new IllegalStateException("Fragment " + fname - + " did not create a view."); - } - if (id != 0) { - fragment.mView.setId(id); - } - if (fragment.mView.getTag() == null) { - fragment.mView.setTag(tag); - } - return fragment.mView; + return mFragments.onCreateView(parent, name, context, attrs); } /** @@ -5320,13 +5253,15 @@ public class Activity extends ContextThemeWrapper * drawn and it is safe to make this Activity translucent again. * @param options activity options delivered to the activity below this one. The options * are retrieved using {@link #getActivityOptions}. + * @return <code>true</code> if Window was opaque and will become translucent or + * <code>false</code> if window was translucent and no change needed to be made. * * @see #convertFromTranslucent() * @see TranslucentConversionListener * * @hide */ - public void convertToTranslucent(TranslucentConversionListener callback, + public boolean convertToTranslucent(TranslucentConversionListener callback, ActivityOptions options) { boolean drawComplete; try { @@ -5343,6 +5278,7 @@ public class Activity extends ContextThemeWrapper // Window is already translucent. mTranslucentCallback.onTranslucentConversionComplete(drawComplete); } + return mChangeCanvasToTranslucent; } /** @hide */ @@ -5929,14 +5865,21 @@ public class Activity extends ContextThemeWrapper } /** - * Put this Activity in a mode where the user is locked to the + * Request to put this Activity in a mode where the user is locked to the * current task. * * This will prevent the user from launching other apps, going to settings, * or reaching the home screen. * - * Lock task mode will only start if the activity has been whitelisted by the - * Device Owner through DevicePolicyManager#setLockTaskComponents. + * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true + * for this component then the app will go directly into Lock Task mode. The user + * will not be able to exit this mode until {@link Activity#stopLockTask()} is called. + * + * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false + * then the system will prompt the user with a dialog requesting permission to enter + * this mode. When entered through this method the user can exit at any time by + * swiping down twice from the top of the screen. Calling stopLockTask will also + * exit the mode. */ public void startLockTask() { try { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 0f65454..572d389 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -943,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM b = data.readStrongBinder(); IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b); int userId = data.readInt(); - boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId); + String abiOverride = data.readString(); + boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId, + abiOverride); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -2143,6 +2145,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_LOCK_TASK_BY_CURRENT: { + data.enforceInterface(IActivityManager.descriptor); + startLockTaskModeOnCurrent(); + reply.writeNoException(); + return true; + } + case STOP_LOCK_TASK_MODE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); stopLockTaskMode(); @@ -2150,6 +2159,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case STOP_LOCK_TASK_BY_CURRENT: { + data.enforceInterface(IActivityManager.descriptor); + stopLockTaskModeOnCurrent(); + reply.writeNoException(); + return true; + } + case IS_IN_LOCK_TASK_MODE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); final boolean isInLockTaskMode = isInLockTaskMode(); @@ -3339,7 +3355,8 @@ class ActivityManagerProxy implements IActivityManager public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException { + IUiAutomationConnection connection, int userId, String instructionSet) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3350,6 +3367,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); data.writeStrongBinder(connection != null ? connection.asBinder() : null); data.writeInt(userId); + data.writeString(instructionSet); mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; @@ -4943,6 +4961,17 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void startLockTaskModeOnCurrent() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(START_LOCK_TASK_BY_CURRENT, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public void stopLockTaskMode() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -4954,6 +4983,17 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void stopLockTaskModeOnCurrent() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(STOP_LOCK_TASK_BY_CURRENT, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public boolean isInLockTaskMode() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index a057c3e..0ecead9 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -29,8 +29,6 @@ import android.view.View; import android.view.Window; import java.util.ArrayList; -import java.util.List; -import java.util.Map; /** * Helper class for building an options Bundle that can be used with @@ -394,8 +392,18 @@ public class ActivityOptions { if (sharedElements != null) { for (int i = 0; i < sharedElements.length; i++) { Pair<View, String> sharedElement = sharedElements[i]; - names.add(sharedElement.second); - mappedNames.add(sharedElement.first.getViewName()); + String sharedElementName = sharedElement.second; + if (sharedElementName == null) { + throw new IllegalArgumentException("Shared element name must not be null"); + } + String name = sharedElement.first.getTransitionName(); + if (name == null) { + throw new IllegalArgumentException("Shared elements must have non-null " + + "transitionNames"); + } + + names.add(sharedElementName); + mappedNames.add(name); } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d9adba3..f5514f8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -98,6 +98,7 @@ import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.util.FastPrintWriter; import com.android.org.conscrypt.OpenSSLSocketImpl; +import com.android.org.conscrypt.TrustedCertificateStore; import com.google.android.collect.Lists; import dalvik.system.VMRuntime; @@ -190,11 +191,13 @@ public final class ActivityThread { /** Reference to singleton {@link ActivityThread} */ private static ActivityThread sCurrentActivityThread; Instrumentation mInstrumentation; + String mInstrumentationPackageName = null; String mInstrumentationAppDir = null; - String mInstrumentationAppLibraryDir = null; - String mInstrumentationAppPackage = null; + String[] mInstrumentationSplitAppDirs = null; + String mInstrumentationLibDir = null; String mInstrumentedAppDir = null; - String mInstrumentedAppLibraryDir = null; + String[] mInstrumentedSplitAppDirs = null; + String mInstrumentedLibDir = null; boolean mSystemThread = false; boolean mJitEnabled = false; @@ -316,7 +319,7 @@ public final class ActivityThread { } public boolean isPersistable() { - return (activityInfo.flags & ActivityInfo.FLAG_PERSISTABLE) != 0; + return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS; } public String toString() { @@ -1584,11 +1587,11 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, - int displayId, Configuration overrideConfiguration, + Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, + String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) { - return mResourcesManager.getTopLevelResources(resDir, overlayDirs, libDirs, displayId, - overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); + return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, + displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); } final Handler getHandler() { @@ -4314,16 +4317,20 @@ public final class ActivityThread { + data.instrumentationName); } + mInstrumentationPackageName = ii.packageName; mInstrumentationAppDir = ii.sourceDir; - mInstrumentationAppLibraryDir = ii.nativeLibraryDir; - mInstrumentationAppPackage = ii.packageName; + mInstrumentationSplitAppDirs = ii.splitSourceDirs; + mInstrumentationLibDir = ii.nativeLibraryDir; mInstrumentedAppDir = data.info.getAppDir(); - mInstrumentedAppLibraryDir = data.info.getLibDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); ApplicationInfo instrApp = new ApplicationInfo(); instrApp.packageName = ii.packageName; instrApp.sourceDir = ii.sourceDir; instrApp.publicSourceDir = ii.publicSourceDir; + instrApp.splitSourceDirs = ii.splitSourceDirs; + instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs; instrApp.dataDir = ii.dataDir; instrApp.nativeLibraryDir = ii.nativeLibraryDir; LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, @@ -5049,6 +5056,10 @@ public final class ActivityThread { Security.addProvider(new AndroidKeyStoreProvider()); + // Make sure TrustedCertificateStore looks in the right place for CA certificates + final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); + TrustedCertificateStore.setDefaultUserDirectory(configDir); + Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 703df51..c351cd5 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -129,9 +129,6 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; - // The background fade in/out duration. TODO: Enable tuning this. - public static final int FADE_BACKGROUND_DURATION_MS = 300; - protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); /** @@ -209,6 +206,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected ResultReceiver mResultReceiver; final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); final protected boolean mIsReturning; + private Runnable mPendingTransition; + private boolean mIsStartingTransition; + public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, @@ -233,6 +233,13 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); mTransitioningViews.removeAll(mSharedElements); + Rect r = new Rect(); + for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { + View view = mTransitioningViews.get(i); + if (!view.getGlobalVisibleRect(r)) { + mTransitioningViews.remove(i); + } + } } setEpicenter(); } @@ -261,13 +268,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (view == null) { mEpicenterCallback.setEpicenter(null); } else { - int[] loc = new int[2]; - view.getLocationOnScreen(loc); - int left = loc[0] + Math.round(view.getTranslationX()); - int top = loc[1] + Math.round(view.getTranslationY()); - int right = left + view.getWidth(); - int bottom = top + view.getHeight(); - Rect epicenter = new Rect(left, top, right, bottom); + Rect epicenter = new Rect(); + view.getBoundsOnScreen(epicenter); mEpicenterCallback.setEpicenter(epicenter); } } @@ -279,7 +281,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { public ArrayList<String> getMappedNames() { ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); for (int i = 0; i < mSharedElements.size(); i++) { - names.add(mSharedElements.get(i).getViewName()); + names.add(mSharedElements.get(i).getTransitionName()); } return names; } @@ -298,13 +300,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (transition == null || views == null || views.isEmpty()) { return null; } + // Add the targets to a set containing transition so that transition + // remains unaffected. We don't want to modify the targets of transition itself. TransitionSet set = new TransitionSet(); - set.addTransition(transition); if (views != null) { - for (View view: views) { + for (View view : views) { set.addTarget(view); } } + // By adding the transition after addTarget, we prevent addTarget from + // affecting transition. + set.addTransition(transition); return set; } @@ -352,6 +358,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { String name = mAllSharedElementNames.get(i); View sharedElement = sharedElements.get(name); if (sharedElement != null) { + if (sharedElement.getTransitionName() == null) { + throw new IllegalArgumentException("Shared elements must have " + + "non-null transitionNames"); + } mSharedElementNames.add(name); mSharedElements.add(sharedElement); } @@ -484,7 +494,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (bitmap != null) { snapshot.setBackground(new BitmapDrawable(resources, bitmap)); } - snapshot.setViewName(name); + snapshot.setTransitionName(name); setSharedElementState(snapshot, name, state, parentLoc); snapshots.add(snapshot); } @@ -504,15 +514,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected Bundle captureSharedElementState() { Bundle bundle = new Bundle(); - int[] tempLoc = new int[2]; + Rect tempBounds = new Rect(); for (int i = 0; i < mSharedElementNames.size(); i++) { View sharedElement = mSharedElements.get(i); String name = mSharedElementNames.get(i); - captureSharedElementState(sharedElement, name, bundle, tempLoc); + captureSharedElementState(sharedElement, name, bundle, tempBounds); } return bundle; } + protected long getFadeDuration() { + return getWindow().getTransitionBackgroundFadeDuration(); + } + /** * Captures placement information for Views with a shared element name for * Activity Transitions. @@ -521,20 +535,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * @param name The shared element name in the target Activity to apply the placement * information for. * @param transitionArgs Bundle to store shared element placement information. - * @param tempLoc A temporary int[2] for capturing the current location of views. + * @param tempBounds A temporary Rect for capturing the current location of views. */ - private static void captureSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { + protected static void captureSharedElementState(View view, String name, Bundle transitionArgs, + Rect tempBounds) { Bundle sharedElementBundle = new Bundle(); - view.getLocationOnScreen(tempLoc); - float scaleX = view.getScaleX(); - sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); - int width = Math.round(view.getWidth() * scaleX); + tempBounds.set(0, 0, view.getWidth(), view.getHeight()); + view.getBoundsOnScreen(tempBounds); + sharedElementBundle.putInt(KEY_SCREEN_X, tempBounds.left); + int width = tempBounds.width(); sharedElementBundle.putInt(KEY_WIDTH, width); - float scaleY = view.getScaleY(); - sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); - int height = Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempBounds.top); + int height = tempBounds.height(); sharedElementBundle.putInt(KEY_HEIGHT, height); sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); @@ -560,6 +573,32 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { transitionArgs.putBundle(name, sharedElementBundle); } + + protected void startTransition(Runnable runnable) { + if (mIsStartingTransition) { + mPendingTransition = runnable; + } else { + mIsStartingTransition = true; + runnable.run(); + } + } + + protected void transitionStarted() { + mIsStartingTransition = false; + } + + protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter { + @Override + public void onTransitionStart(Transition transition) { + mIsStartingTransition = false; + Runnable pending = mPendingTransition; + mPendingTransition = null; + if (pending != null) { + startTransition(pending); + } + } + } + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { if (scaleType == SCALE_TYPE_VALUES[i]) { diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 097c64e..94ea2c5 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -53,6 +53,7 @@ public class ActivityView extends ViewGroup { private int mHeight; private Surface mSurface; private int mLastVisibility; + private ActivityViewCallback mActivityViewCallback; // Only one IIntentSender or Intent may be queued at a time. Most recent one wins. IIntentSender mQueuedPendingIntent; @@ -254,6 +255,25 @@ public class ActivityView extends ViewGroup { } } + /** + * Set the callback to use to report certain state changes. + * @param callback The callback to report events to. + * + * @see ActivityViewCallback + */ + public void setCallback(ActivityViewCallback callback) { + mActivityViewCallback = callback; + } + + public static abstract class ActivityViewCallback { + /** + * Called when all activities in the ActivityView have completed and been removed. Register + * using {@link ActivityView#setCallback(ActivityViewCallback)}. Each ActivityView may + * have at most one callback registered. + */ + public abstract void onAllActivitiesComplete(ActivityView view); + } + private class ActivityViewSurfaceTextureListener implements SurfaceTextureListener { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, @@ -313,14 +333,32 @@ public class ActivityView extends ViewGroup { if (DEBUG) Log.v(TAG, "setVisible(): container=" + container + " visible=" + visible + " ActivityView=" + mActivityViewWeakReference.get()); } + + @Override + public void onAllActivitiesComplete(IBinder container) { + final ActivityView activityView = mActivityViewWeakReference.get(); + if (activityView != null) { + final ActivityViewCallback callback = activityView.mActivityViewCallback; + if (callback != null) { + activityView.post(new Runnable() { + @Override + public void run() { + callback.onAllActivitiesComplete(activityView); + } + }); + } + } + } } private static class ActivityContainerWrapper { private final IActivityContainer mIActivityContainer; private final CloseGuard mGuard = CloseGuard.get(); + boolean mOpened; // Protected by mGuard. ActivityContainerWrapper(IActivityContainer container) { mIActivityContainer = container; + mOpened = true; mGuard.open("release"); } @@ -388,11 +426,16 @@ public class ActivityView extends ViewGroup { } void release() { - if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called"); - try { - mIActivityContainer.release(); - mGuard.close(); - } catch (RemoteException e) { + synchronized (mGuard) { + if (mOpened) { + if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called"); + try { + mIActivityContainer.release(); + mGuard.close(); + } catch (RemoteException e) { + } + mOpened = false; + } } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 5867232..a480219 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -189,7 +189,9 @@ public class AppOpsManager { /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */ public static final int OP_GET_USAGE_STATS = 43; /** @hide */ - public static final int _NUM_OP = 44; + public static final int OP_MUTE_MICROPHONE = 44; + /** @hide */ + public static final int _NUM_OP = 45; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = @@ -257,6 +259,7 @@ public class AppOpsManager { OP_COARSE_LOCATION, OP_COARSE_LOCATION, OP_GET_USAGE_STATS, + OP_MUTE_MICROPHONE }; /** @@ -308,6 +311,7 @@ public class AppOpsManager { OPSTR_MONITOR_LOCATION, OPSTR_MONITOR_HIGH_POWER_LOCATION, null, + null, }; /** @@ -358,7 +362,8 @@ public class AppOpsManager { "WAKE_LOCK", "MONITOR_LOCATION", "MONITOR_HIGH_POWER_LOCATION", - "GET_USAGE_STATS" + "GET_USAGE_STATS", + "OP_MUTE_MICROPHONE", }; /** @@ -410,6 +415,7 @@ public class AppOpsManager { null, // no permission for generic location monitoring null, // no permission for high power location monitoring android.Manifest.permission.PACKAGE_USAGE_STATS, + null, // no permission for muting/unmuting microphone }; /** @@ -462,6 +468,7 @@ public class AppOpsManager { null, //MONITOR_LOCATION null, //MONITOR_HIGH_POWER_LOCATION null, //GET_USAGE_STATS + UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE }; /** @@ -512,6 +519,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_IGNORED, // OP_GET_USAGE_STATS + AppOpsManager.MODE_ALLOWED, }; /** @@ -566,6 +574,7 @@ public class AppOpsManager { false, false, false, + false, }; private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>(); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2f35160..13b922c 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -48,11 +48,13 @@ import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.ArrayMap; import android.util.Log; import android.view.Display; @@ -823,8 +825,10 @@ final class ApplicationPackageManager extends PackageManager { if (app.packageName.equals("system")) { return mContext.mMainThread.getSystemContext().getResources(); } + final boolean sameUid = (app.uid == Process.myUid()); Resources r = mContext.mMainThread.getTopLevelResources( - app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir, + sameUid ? app.sourceDir : app.publicSourceDir, + sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo); if (r != null) { return r; @@ -1455,10 +1459,22 @@ final class ApplicationPackageManager extends PackageManager { * @hide */ @Override - public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig, - int userIdDest) { + public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId, + int flags) { try { - mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest); + mPM.addCrossProfileIntentFilter(filter, sourceUserId, targetUserId, flags); + } catch (RemoteException e) { + // Should never happen! + } + } + + /** + * @hide + */ + public void addCrossProfileIntentsForPackage(String packageName, + int sourceUserId, int targetUserId) { + try { + mPM.addCrossProfileIntentsForPackage(packageName, sourceUserId, targetUserId); } catch (RemoteException e) { // Should never happen! } @@ -1468,14 +1484,23 @@ final class ApplicationPackageManager extends PackageManager { * @hide */ @Override - public void clearForwardingIntentFilters(int userIdOrig) { + public void clearCrossProfileIntentFilters(int sourceUserId) { try { - mPM.clearForwardingIntentFilters(userIdOrig); + mPM.clearCrossProfileIntentFilters(sourceUserId); } catch (RemoteException e) { // Should never happen! } } + /** + * @hide + */ + @Override + public Bitmap getUserIcon(int userId) { + UserManager um = UserManager.get(mContext); + return um.getUserIcon(userId); + } + private final ContextImpl mContext; private final IPackageManager mPM; diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index ef4099f..5998d7a 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -1184,6 +1184,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInt(level); mRemote.transact(SCHEDULE_TRIM_MEMORY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + data.recycle(); } public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin, diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 89ee145..4433a3a 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -16,36 +16,53 @@ package android.app; +import com.android.internal.util.FastPrintWriter; + +import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.transition.Transition; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.ArrayMap; import android.util.Log; import android.util.LogWriter; -import com.android.internal.util.FastPrintWriter; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; final class BackStackState implements Parcelable { final int[] mOps; final int mTransition; final int mTransitionStyle; + final int mCustomTransition; + final int mSceneRoot; final String mName; final int mIndex; final int mBreadCrumbTitleRes; final CharSequence mBreadCrumbTitleText; final int mBreadCrumbShortTitleRes; final CharSequence mBreadCrumbShortTitleText; + final ArrayList<String> mSharedElementSourceNames; + final ArrayList<String> mSharedElementTargetNames; public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { int numRemoved = 0; BackStackRecord.Op op = bse.mHead; while (op != null) { - if (op.removed != null) numRemoved += op.removed.size(); + if (op.removed != null) { + numRemoved += op.removed.size(); + } op = op.next; } - mOps = new int[bse.mNumOp*7 + numRemoved]; + mOps = new int[bse.mNumOp * 7 + numRemoved]; if (!bse.mAddToBackStack) { throw new IllegalStateException("Not on back stack"); @@ -63,7 +80,7 @@ final class BackStackState implements Parcelable { if (op.removed != null) { final int N = op.removed.size(); mOps[pos++] = N; - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { mOps[pos++] = op.removed.get(i).mIndex; } } else { @@ -79,6 +96,10 @@ final class BackStackState implements Parcelable { mBreadCrumbTitleText = bse.mBreadCrumbTitleText; mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; + mCustomTransition = bse.mCustomTransition; + mSceneRoot = bse.mSceneRoot; + mSharedElementSourceNames = bse.mSharedElementSourceNames; + mSharedElementTargetNames = bse.mSharedElementTargetNames; } public BackStackState(Parcel in) { @@ -91,6 +112,10 @@ final class BackStackState implements Parcelable { mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mBreadCrumbShortTitleRes = in.readInt(); mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mCustomTransition = in.readInt(); + mSceneRoot = in.readInt(); + mSharedElementSourceNames = in.createStringArrayList(); + mSharedElementTargetNames = in.createStringArrayList(); } public BackStackRecord instantiate(FragmentManagerImpl fm) { @@ -100,8 +125,10 @@ final class BackStackState implements Parcelable { while (pos < mOps.length) { BackStackRecord.Op op = new BackStackRecord.Op(); op.cmd = mOps[pos++]; - if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); + if (FragmentManagerImpl.DEBUG) { + Log.v(FragmentManagerImpl.TAG, + "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); + } int findex = mOps[pos++]; if (findex >= 0) { Fragment f = fm.mActive.get(findex); @@ -116,9 +143,11 @@ final class BackStackState implements Parcelable { final int N = mOps[pos++]; if (N > 0) { op.removed = new ArrayList<Fragment>(N); - for (int i=0; i<N; i++) { - if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "Instantiate " + bse + " set remove fragment #" + mOps[pos]); + for (int i = 0; i < N; i++) { + if (FragmentManagerImpl.DEBUG) { + Log.v(FragmentManagerImpl.TAG, + "Instantiate " + bse + " set remove fragment #" + mOps[pos]); + } Fragment r = fm.mActive.get(mOps[pos++]); op.removed.add(r); } @@ -135,6 +164,10 @@ final class BackStackState implements Parcelable { bse.mBreadCrumbTitleText = mBreadCrumbTitleText; bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; + bse.mCustomTransition = mCustomTransition; + bse.mSceneRoot = mSceneRoot; + bse.mSharedElementSourceNames = mSharedElementSourceNames; + bse.mSharedElementTargetNames = mSharedElementTargetNames; bse.bumpBackStackNesting(1); return bse; } @@ -153,6 +186,10 @@ final class BackStackState implements Parcelable { TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); dest.writeInt(mBreadCrumbShortTitleRes); TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); + dest.writeInt(mCustomTransition); + dest.writeInt(mSceneRoot); + dest.writeStringList(mSharedElementSourceNames); + dest.writeStringList(mSharedElementTargetNames); } public static final Parcelable.Creator<BackStackState> CREATOR @@ -217,6 +254,11 @@ final class BackStackRecord extends FragmentTransaction implements int mBreadCrumbShortTitleRes; CharSequence mBreadCrumbShortTitleText; + int mCustomTransition; + int mSceneRoot; + ArrayList<String> mSharedElementSourceNames; + ArrayList<String> mSharedElementTargetNames; + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -240,78 +282,112 @@ final class BackStackRecord extends FragmentTransaction implements void dump(String prefix, PrintWriter writer, boolean full) { if (full) { - writer.print(prefix); writer.print("mName="); writer.print(mName); - writer.print(" mIndex="); writer.print(mIndex); - writer.print(" mCommitted="); writer.println(mCommitted); + writer.print(prefix); + writer.print("mName="); + writer.print(mName); + writer.print(" mIndex="); + writer.print(mIndex); + writer.print(" mCommitted="); + writer.println(mCommitted); if (mTransition != FragmentTransaction.TRANSIT_NONE) { - writer.print(prefix); writer.print("mTransition=#"); - writer.print(Integer.toHexString(mTransition)); - writer.print(" mTransitionStyle=#"); - writer.println(Integer.toHexString(mTransitionStyle)); + writer.print(prefix); + writer.print("mTransition=#"); + writer.print(Integer.toHexString(mTransition)); + writer.print(" mTransitionStyle=#"); + writer.println(Integer.toHexString(mTransitionStyle)); } - if (mEnterAnim != 0 || mExitAnim !=0) { - writer.print(prefix); writer.print("mEnterAnim=#"); - writer.print(Integer.toHexString(mEnterAnim)); - writer.print(" mExitAnim=#"); - writer.println(Integer.toHexString(mExitAnim)); + if (mEnterAnim != 0 || mExitAnim != 0) { + writer.print(prefix); + writer.print("mEnterAnim=#"); + writer.print(Integer.toHexString(mEnterAnim)); + writer.print(" mExitAnim=#"); + writer.println(Integer.toHexString(mExitAnim)); } - if (mPopEnterAnim != 0 || mPopExitAnim !=0) { - writer.print(prefix); writer.print("mPopEnterAnim=#"); - writer.print(Integer.toHexString(mPopEnterAnim)); - writer.print(" mPopExitAnim=#"); - writer.println(Integer.toHexString(mPopExitAnim)); + if (mPopEnterAnim != 0 || mPopExitAnim != 0) { + writer.print(prefix); + writer.print("mPopEnterAnim=#"); + writer.print(Integer.toHexString(mPopEnterAnim)); + writer.print(" mPopExitAnim=#"); + writer.println(Integer.toHexString(mPopExitAnim)); } if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbTitleRes)); - writer.print(" mBreadCrumbTitleText="); - writer.println(mBreadCrumbTitleText); + writer.print(prefix); + writer.print("mBreadCrumbTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbTitleRes)); + writer.print(" mBreadCrumbTitleText="); + writer.println(mBreadCrumbTitleText); } if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); - writer.print(" mBreadCrumbShortTitleText="); - writer.println(mBreadCrumbShortTitleText); + writer.print(prefix); + writer.print("mBreadCrumbShortTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); + writer.print(" mBreadCrumbShortTitleText="); + writer.println(mBreadCrumbShortTitleText); } } if (mHead != null) { - writer.print(prefix); writer.println("Operations:"); + writer.print(prefix); + writer.println("Operations:"); String innerPrefix = prefix + " "; Op op = mHead; int num = 0; while (op != null) { String cmdStr; switch (op.cmd) { - case OP_NULL: cmdStr="NULL"; break; - case OP_ADD: cmdStr="ADD"; break; - case OP_REPLACE: cmdStr="REPLACE"; break; - case OP_REMOVE: cmdStr="REMOVE"; break; - case OP_HIDE: cmdStr="HIDE"; break; - case OP_SHOW: cmdStr="SHOW"; break; - case OP_DETACH: cmdStr="DETACH"; break; - case OP_ATTACH: cmdStr="ATTACH"; break; - default: cmdStr="cmd=" + op.cmd; break; + case OP_NULL: + cmdStr = "NULL"; + break; + case OP_ADD: + cmdStr = "ADD"; + break; + case OP_REPLACE: + cmdStr = "REPLACE"; + break; + case OP_REMOVE: + cmdStr = "REMOVE"; + break; + case OP_HIDE: + cmdStr = "HIDE"; + break; + case OP_SHOW: + cmdStr = "SHOW"; + break; + case OP_DETACH: + cmdStr = "DETACH"; + break; + case OP_ATTACH: + cmdStr = "ATTACH"; + break; + default: + cmdStr = "cmd=" + op.cmd; + break; } - writer.print(prefix); writer.print(" Op #"); writer.print(num); - writer.print(": "); writer.print(cmdStr); - writer.print(" "); writer.println(op.fragment); + writer.print(prefix); + writer.print(" Op #"); + writer.print(num); + writer.print(": "); + writer.print(cmdStr); + writer.print(" "); + writer.println(op.fragment); if (full) { if (op.enterAnim != 0 || op.exitAnim != 0) { - writer.print(innerPrefix); writer.print("enterAnim=#"); - writer.print(Integer.toHexString(op.enterAnim)); - writer.print(" exitAnim=#"); - writer.println(Integer.toHexString(op.exitAnim)); + writer.print(innerPrefix); + writer.print("enterAnim=#"); + writer.print(Integer.toHexString(op.enterAnim)); + writer.print(" exitAnim=#"); + writer.println(Integer.toHexString(op.exitAnim)); } if (op.popEnterAnim != 0 || op.popExitAnim != 0) { - writer.print(innerPrefix); writer.print("popEnterAnim=#"); - writer.print(Integer.toHexString(op.popEnterAnim)); - writer.print(" popExitAnim=#"); - writer.println(Integer.toHexString(op.popExitAnim)); + writer.print(innerPrefix); + writer.print("popEnterAnim=#"); + writer.print(Integer.toHexString(op.popEnterAnim)); + writer.print(" popExitAnim=#"); + writer.println(Integer.toHexString(op.popExitAnim)); } } if (op.removed != null && op.removed.size() > 0) { - for (int i=0; i<op.removed.size(); i++) { + for (int i = 0; i < op.removed.size(); i++) { writer.print(innerPrefix); if (op.removed.size() == 1) { writer.print("Removed: "); @@ -319,8 +395,10 @@ final class BackStackRecord extends FragmentTransaction implements if (i == 0) { writer.println("Removed:"); } - writer.print(innerPrefix); writer.print(" #"); writer.print(i); - writer.print(": "); + writer.print(innerPrefix); + writer.print(" #"); + writer.print(i); + writer.print(": "); } writer.println(op.removed.get(i)); } @@ -494,6 +572,51 @@ final class BackStackRecord extends FragmentTransaction implements return this; } + @Override + public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) { + mSceneRoot = sceneRootId; + mCustomTransition = transitionId; + return this; + } + + @Override + public FragmentTransaction setSharedElement(View sharedElement, String name) { + String transitionName = sharedElement.getTransitionName(); + if (transitionName == null) { + throw new IllegalArgumentException("Unique transitionNames are required for all" + + " sharedElements"); + } + mSharedElementSourceNames = new ArrayList<String>(1); + mSharedElementSourceNames.add(transitionName); + + mSharedElementTargetNames = new ArrayList<String>(1); + mSharedElementTargetNames.add(name); + return this; + } + + @Override + public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) { + if (sharedElements == null || sharedElements.length == 0) { + mSharedElementSourceNames = null; + mSharedElementTargetNames = null; + } else { + ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length); + ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length); + for (int i = 0; i < sharedElements.length; i++) { + String transitionName = sharedElements[i].first.getTransitionName(); + if (transitionName == null) { + throw new IllegalArgumentException("Unique transitionNames are required for all" + + " sharedElements"); + } + sourceNames.add(transitionName); + targetNames.add(sharedElements[i].second); + } + mSharedElementSourceNames = sourceNames; + mSharedElementTargetNames = targetNames; + } + return this; + } + public FragmentTransaction setTransitionStyle(int styleRes) { mTransitionStyle = styleRes; return this; @@ -550,21 +673,27 @@ final class BackStackRecord extends FragmentTransaction implements if (!mAddToBackStack) { return; } - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this - + " by " + amt); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting in " + this + + " by " + amt); + } Op op = mHead; while (op != null) { if (op.fragment != null) { op.fragment.mBackStackNesting += amt; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + op.fragment + " to " + op.fragment.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + op.fragment + " to " + op.fragment.mBackStackNesting); + } } if (op.removed != null) { - for (int i=op.removed.size()-1; i>=0; i--) { + for (int i = op.removed.size() - 1; i >= 0; i--) { Fragment r = op.removed.get(i); r.mBackStackNesting += amt; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + r + " to " + r.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + r + " to " + r.mBackStackNesting); + } } } op = op.next; @@ -578,9 +707,11 @@ final class BackStackRecord extends FragmentTransaction implements public int commitAllowingStateLoss() { return commitInternal(true); } - + int commitInternal(boolean allowStateLoss) { - if (mCommitted) throw new IllegalStateException("commit already called"); + if (mCommitted) { + throw new IllegalStateException("commit already called"); + } if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); @@ -597,9 +728,11 @@ final class BackStackRecord extends FragmentTransaction implements mManager.enqueueAction(this, allowStateLoss); return mIndex; } - + public void run() { - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Run: " + this); + } if (mAddToBackStack) { if (mIndex < 0) { @@ -609,6 +742,9 @@ final class BackStackRecord extends FragmentTransaction implements bumpBackStackNesting(1); + TransitionState state = beginTransition(mSharedElementSourceNames, + mSharedElementTargetNames); + Op op = mHead; while (op != null) { switch (op.cmd) { @@ -616,14 +752,17 @@ final class BackStackRecord extends FragmentTransaction implements Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); - } break; + } + break; case OP_REPLACE: { Fragment f = op.fragment; if (mManager.mAdded != null) { - for (int i=0; i<mManager.mAdded.size(); i++) { + for (int i = 0; i < mManager.mAdded.size(); i++) { Fragment old = mManager.mAdded.get(i); - if (FragmentManagerImpl.DEBUG) Log.v(TAG, - "OP_REPLACE: adding=" + f + " old=" + old); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, + "OP_REPLACE: adding=" + f + " old=" + old); + } if (f == null || old.mContainerId == f.mContainerId) { if (old == f) { op.fragment = f = null; @@ -635,8 +774,10 @@ final class BackStackRecord extends FragmentTransaction implements old.mNextAnim = op.exitAnim; if (mAddToBackStack) { old.mBackStackNesting += 1; - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " - + old + " to " + old.mBackStackNesting); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + old + " to " + old.mBackStackNesting); + } } mManager.removeFragment(old, mTransition, mTransitionStyle); } @@ -647,32 +788,38 @@ final class BackStackRecord extends FragmentTransaction implements f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); } - } break; + } + break; case OP_REMOVE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.removeFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.hideFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.showFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_DETACH: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.detachFragment(f, mTransition, mTransitionStyle); - } break; + } + break; case OP_ATTACH: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.attachFragment(f, mTransition, mTransitionStyle); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } @@ -687,9 +834,174 @@ final class BackStackRecord extends FragmentTransaction implements if (mAddToBackStack) { mManager.addBackStackState(this); } + + if (state != null) { + updateTransitionEndState(state, mSharedElementTargetNames); + } } - public void popFromBackStack(boolean doStateMove) { + private TransitionState beginTransition(ArrayList<String> sourceNames, + ArrayList<String> targetNames) { + if (mCustomTransition <= 0 || mSceneRoot <= 0) { + return null; + } + View rootView = mManager.mContainer.findViewById(mSceneRoot); + if (!(rootView instanceof ViewGroup)) { + throw new IllegalArgumentException("SceneRoot is not a ViewGroup"); + } + TransitionState state = new TransitionState(); + // get Transition scene root and create Transitions + state.sceneRoot = (ViewGroup) rootView; + state.sceneRoot.captureTransitioningViews(state.transitioningViews); + + state.exitTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + state.sharedElementTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + state.enterTransition = TransitionInflater.from(mManager.mActivity) + .inflateTransition(mCustomTransition); + // Adding a non-existent target view makes sure that the transitions don't target + // any views by default. They'll only target the views we tell add. If we don't + // add any, then no views will be targeted. + View nonExistentView = new View(mManager.mActivity); + state.enterTransition.addTarget(nonExistentView); + state.exitTransition.addTarget(nonExistentView); + state.sharedElementTransition.addTarget(nonExistentView); + + setSharedElementEpicenter(state.enterTransition, state); + + state.excludingTransition = new TransitionSet() + .addTransition(state.exitTransition) + .addTransition(state.enterTransition); + + if (sourceNames != null) { + // Map shared elements. + state.sceneRoot.findNamedViews(state.namedViews); + state.namedViews.retainAll(sourceNames); + View epicenterView = state.namedViews.get(sourceNames.get(0)); + if (epicenterView != null) { + // The epicenter is only the first shared element. + setEpicenter(state.exitTransition, epicenterView); + setEpicenter(state.sharedElementTransition, epicenterView); + } + state.transitioningViews.removeAll(state.namedViews.values()); + state.excludingTransition.addTransition(state.sharedElementTransition); + addTransitioningViews(state.sharedElementTransition, state.namedViews.values()); + } + + // Adds the (maybe) exiting views, not including the shared element. + // If some stay, that's ok. + addTransitioningViews(state.exitTransition, state.transitioningViews); + + // Prepare for shared element name mapping. This could be chained in the case + // of popping several back stack states. + state.excludingTransition.setNameOverrides(new ArrayMap<String, String>()); + setNameOverrides(state, sourceNames, targetNames); + + // Don't include any subtree in the views that are hidden when capturing the + // view hierarchy transitions. They should be as if not there. + excludeHiddenFragments(state, true); + + TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition); + return state; + } + + private void updateTransitionEndState(TransitionState state, ArrayList<String> names) { + // Find all views that are entering. + ArrayList<View> enteringViews = new ArrayList<View>(); + state.sceneRoot.captureTransitioningViews(enteringViews); + enteringViews.removeAll(state.transitioningViews); + + if (names != null) { + // find all shared elements. + state.namedViews.clear(); + state.sceneRoot.findNamedViews(state.namedViews); + state.namedViews.retainAll(names); + if (!state.namedViews.isEmpty()) { + enteringViews.removeAll(state.namedViews.values()); + addTransitioningViews(state.sharedElementTransition, state.namedViews.values()); + // now we know the epicenter of the entering transition. + state.mEnteringEpicenterView = state.namedViews.get(names.get(0)); + } + } + + // Add all entering views to the enter transition. + addTransitioningViews(state.enterTransition, enteringViews); + + // Don't allow capturing state for the newly-hidden fragments. + excludeHiddenFragments(state, false); + + // Allow capturing state for the newly-shown fragments + includeVisibleFragments(state.excludingTransition); + } + + private void addTransitioningViews(Transition transition, Collection<View> views) { + if (views.isEmpty()) { + // Add a view so that we can modify the valid views at the end of the + // fragment transaction. + transition.addTarget(new View(mManager.mActivity)); + } else { + for (View view : views) { + transition.addTarget(view); + } + } + } + + private void excludeHiddenFragments(TransitionState state, boolean forceExclude) { + if (mManager.mAdded != null) { + for (int i = 0; i < mManager.mAdded.size(); i++) { + Fragment fragment = mManager.mAdded.get(i); + if (fragment.mView != null && fragment.mHidden + && (forceExclude || !state.hiddenViews.contains(fragment.mView))) { + state.excludingTransition.excludeTarget(fragment.mView, true); + state.hiddenViews.add(fragment.mView); + } + } + } + if (forceExclude && state.hiddenViews.isEmpty()) { + state.excludingTransition.excludeTarget(new View(mManager.mActivity), true); + } + } + + private void includeVisibleFragments(Transition transition) { + if (mManager.mAdded != null) { + for (int i = 0; i < mManager.mAdded.size(); i++) { + Fragment fragment = mManager.mAdded.get(i); + if (fragment.mView != null && !fragment.mHidden) { + transition.excludeTarget(fragment.mView, false); + } + } + } + } + + private static void setEpicenter(Transition transition, View view) { + final Rect epicenter = new Rect(); + view.getBoundsOnScreen(epicenter); + + transition.setEpicenterCallback(new Transition.EpicenterCallback() { + @Override + public Rect onGetEpicenter(Transition transition) { + return epicenter; + } + }); + } + + private void setSharedElementEpicenter(Transition transition, final TransitionState state) { + transition.setEpicenterCallback(new Transition.EpicenterCallback() { + private Rect mEpicenter; + + @Override + public Rect onGetEpicenter(Transition transition) { + if (mEpicenter == null && state.mEnteringEpicenterView != null) { + mEpicenter = new Rect(); + state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter); + } + return mEpicenter; + } + }); + } + + public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) { if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "popFromBackStack: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); @@ -698,6 +1010,12 @@ final class BackStackRecord extends FragmentTransaction implements pw.flush(); } + if (state == null) { + state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames); + } else { + setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames); + } + bumpBackStackNesting(-1); Op op = mTail; @@ -709,7 +1027,8 @@ final class BackStackRecord extends FragmentTransaction implements mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_REPLACE: { Fragment f = op.fragment; if (f != null) { @@ -719,42 +1038,48 @@ final class BackStackRecord extends FragmentTransaction implements mTransitionStyle); } if (op.removed != null) { - for (int i=0; i<op.removed.size(); i++) { + for (int i = 0; i < op.removed.size(); i++) { Fragment old = op.removed.get(i); old.mNextAnim = op.popEnterAnim; mManager.addFragment(old, false); } } - } break; + } + break; case OP_REMOVE: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.addFragment(f, false); - } break; + } + break; case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.showFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.hideFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_DETACH: { Fragment f = op.fragment; f.mNextAnim = op.popEnterAnim; mManager.attachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; case OP_ATTACH: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.detachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } @@ -766,12 +1091,39 @@ final class BackStackRecord extends FragmentTransaction implements if (doStateMove) { mManager.moveToState(mManager.mCurState, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); + if (state != null) { + updateTransitionEndState(state, mSharedElementSourceNames); + state = null; + } } if (mIndex >= 0) { mManager.freeBackStackIndex(mIndex); mIndex = -1; } + return state; + } + + private static void setNameOverride(Transition transition, String source, String target) { + ArrayMap<String, String> overrides = transition.getNameOverrides(); + for (int index = 0; index < overrides.size(); index++) { + if (source.equals(overrides.valueAt(index))) { + overrides.setValueAt(index, target); + return; + } + } + overrides.put(source, target); + } + + private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames, + ArrayList<String> targetNames) { + if (sourceNames != null) { + for (int i = 0; i < sourceNames.size(); i++) { + String source = sourceNames.get(i); + String target = targetNames.get(i); + setNameOverride(state.excludingTransition, source, target); + } + } } public String getName() { @@ -789,4 +1141,16 @@ final class BackStackRecord extends FragmentTransaction implements public boolean isEmpty() { return mNumOp == 0; } + + public class TransitionState { + public ArrayList<View> hiddenViews = new ArrayList<View>(); + public ArrayList<View> transitioningViews = new ArrayList<View>(); + public ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); + public Transition exitTransition; + public Transition sharedElementTransition; + public Transition enterTransition; + public TransitionSet excludingTransition; + public ViewGroup sceneRoot; + public View mEnteringEpicenterView; + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c5190d3..425a140 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -35,7 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IntentSender; +import android.content.IRestrictionsManager; import android.content.ReceiverCallNotAllowedException; +import android.content.RestrictionsManager; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; @@ -113,9 +115,9 @@ import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.service.fingerprint.IFingerprintService; import android.service.fingerprint.FingerprintManager; -import android.service.fingerprint.FingerprintManagerReceiver; -import android.service.fingerprint.FingerprintService; +import android.telecomm.TelecommManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; @@ -140,6 +142,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.appwidget.IAppWidgetService.Stub; import com.android.internal.os.IDropBoxManagerService; +import com.android.internal.telecomm.ITelecommService; import java.io.File; import java.io.FileInputStream; @@ -462,11 +465,6 @@ class ContextImpl extends Context { return new KeyguardManager(); }}); - registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { - public Object createService(ContextImpl ctx) { - return new FingerprintManager(ctx); - }}); - registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); @@ -515,6 +513,9 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); + if (service == null) { + Log.wtf(TAG, "Failed to get power manager service."); + } return new PowerManager(ctx.getOuterContext(), service, ctx.mMainThread.getHandler()); }}); @@ -552,6 +553,13 @@ class ContextImpl extends Context { return new TelephonyManager(ctx.getOuterContext()); }}); + registerService(TELECOMM_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(TELECOMM_SERVICE); + return new TelecommManager(ctx.getOuterContext(), + ITelecommService.Stub.asInterface(b)); + }}); + registerService(UI_MODE_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new UiModeManager(); @@ -654,6 +662,13 @@ class ContextImpl extends Context { } }); + registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE); + IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b); + return new RestrictionsManager(ctx, service); + } + }); registerService(PRINT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); @@ -672,6 +687,7 @@ class ContextImpl extends Context { return new MediaSessionManager(ctx); } }); + registerService(TRUST_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(TRUST_SERVICE); @@ -679,6 +695,14 @@ class ContextImpl extends Context { } }); + registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder binder = ServiceManager.getService(FINGERPRINT_SERVICE); + IFingerprintService service = IFingerprintService.Stub.asInterface(binder); + return new FingerprintManager(ctx.getOuterContext(), service); + } + }); + registerService(TV_INPUT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE); @@ -1747,7 +1771,8 @@ class ContextImpl extends Context { arguments.setAllowFds(false); } return ActivityManagerNative.getDefault().startInstrumentation( - className, profileFile, 0, arguments, null, null, getUserId()); + className, profileFile, 0, arguments, null, null, getUserId(), + null /* ABI override */); } catch (RemoteException e) { // System has crashed, nothing we can do. } @@ -2168,10 +2193,10 @@ class ContextImpl extends Context { || overrideConfiguration != null || (compatInfo != null && compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale)) { - resources = mResourcesManager.getTopLevelResources( - packageInfo.getResDir(), packageInfo.getOverlayDirs(), - packageInfo.getApplicationInfo().sharedLibraryFiles, - displayId, overrideConfiguration, compatInfo, activityToken); + resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(), + packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, + overrideConfiguration, compatInfo, activityToken); } } mResources = resources; diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 779e3de..1d7a0ec 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -55,6 +55,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsExitTransitionComplete; private boolean mIsReadyForTransition; private Bundle mSharedElementsBundle; + private boolean mWasOpaque; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning) { @@ -66,15 +67,16 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { Bundle resultReceiverBundle = new Bundle(); resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); - getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (mIsReadyForTransition) { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - } - return mIsReadyForTransition; - } - }); + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (mIsReadyForTransition) { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + } + return mIsReadyForTransition; + } + }); } public void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { @@ -105,7 +107,16 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private void sendSharedElementDestination() { ViewGroup decor = getDecor(); - if (!decor.isLayoutRequested()) { + boolean allReady = !decor.isLayoutRequested(); + if (allReady) { + for (int i = 0; i < mSharedElements.size(); i++) { + if (mSharedElements.get(i).isLayoutRequested()) { + allReady = false; + break; + } + } + } + if (allReady) { Bundle state = captureSharedElementState(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else { @@ -114,10 +125,15 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { @Override public boolean onPreDraw() { getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + Bundle state = captureSharedElementState(); + mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); return true; } }); } + if (allowOverlappingTransitions()) { + startEnterTransitionOnly(); + } } private static SharedElementListener getListener(Activity activity, boolean isReturning) { @@ -176,7 +192,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { protected void prepareEnter() { mActivity.overridePendingTransition(0, 0); if (!mIsReturning) { - mActivity.convertToTranslucent(null, null); + mWasOpaque = mActivity.convertToTranslucent(null, null); Drawable background = getDecor().getBackground(); if (background != null) { getWindow().setBackgroundDrawable(null); @@ -206,24 +222,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - protected void onTakeSharedElements() { - if (!mIsReadyForTransition || mSharedElementsBundle == null) { - return; - } - final Bundle sharedElementState = mSharedElementsBundle; - mSharedElementsBundle = null; - getDecor().getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - startSharedElementTransition(sharedElementState); - return false; - } - }); - getDecor().invalidate(); - } - private void startSharedElementTransition(Bundle sharedElementState) { setEpicenter(); // Remove rejected shared elements @@ -241,7 +239,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); - boolean startEnterTransition = allowOverlappingTransitions(); + boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); @@ -257,6 +255,29 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mResultReceiver = null; // all done sending messages. } + private void onTakeSharedElements() { + if (!mIsReadyForTransition || mSharedElementsBundle == null) { + return; + } + final Bundle sharedElementState = mSharedElementsBundle; + mSharedElementsBundle = null; + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + startTransition(new Runnable() { + @Override + public void run() { + startSharedElementTransition(sharedElementState); + } + }); + return false; + } + }); + getDecor().invalidate(); + } + private void requestLayoutForSharedElements() { int numSharedElements = mSharedElements.size(); for (int i = 0; i < numSharedElements; i++) { @@ -281,9 +302,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (transition == null) { sharedElementTransitionStarted(); } else { - transition.addListener(new Transition.TransitionListenerAdapter() { + transition.addListener(new ContinueTransitionListener() { @Override public void onTransitionStart(Transition transition) { + super.onTransitionStart(transition); transition.removeListener(this); sharedElementTransitionStarted(); } @@ -291,12 +313,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } if (transition != null) { + if (sharedElementTransition == null) { + transition.addListener(new ContinueTransitionListener()); + } TransitionManager.beginDelayedTransition(getDecor(), transition); if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { mSharedElements.get(0).invalidate(); } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { mTransitioningViews.get(0).invalidate(); } + } else { + transitionStarted(); } return transition; } @@ -315,7 +342,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (background != null) { background = background.mutate(); mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); - mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS); + mBackgroundAnimator.setDuration(getFadeDuration()); mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -350,7 +377,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private void makeOpaque() { if (!mHasStopped && mActivity != null) { - mActivity.convertFromTranslucent(); + if (mWasOpaque) { + mActivity.convertFromTranslucent(); + } mActivity = null; } } @@ -387,11 +416,21 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { protected void onRemoteExitTransitionComplete() { if (!allowOverlappingTransitions()) { - boolean startEnterTransition = true; - boolean startSharedElementTransition = false; - Transition transition = beginTransition(startEnterTransition, - startSharedElementTransition); - startEnterTransition(transition); + startEnterTransitionOnly(); } } + + private void startEnterTransitionOnly() { + startTransition(new Runnable() { + @Override + public void run() { + setEpicenter(); + boolean startEnterTransition = true; + boolean startSharedElementTransition = false; + Transition transition = beginTransition(startEnterTransition, + startSharedElementTransition); + startEnterTransition(transition); + } + }); + } } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index ba1638f..9f3dbdc 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -19,6 +19,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Intent; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -27,6 +28,7 @@ import android.os.Message; import android.transition.Transition; import android.transition.TransitionManager; import android.view.View; +import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import java.util.ArrayList; @@ -60,10 +62,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsHidden; - private boolean mExitTransitionStarted; - private Bundle mExitSharedElementBundle; + private ArrayList<View> mSharedElementSnapshots; + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), @@ -106,30 +108,83 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { break; case MSG_SHARED_ELEMENT_DESTINATION: mExitSharedElementBundle = resultData; - if (mExitTransitionStarted) { + sharedElementExitBack(); + break; + } + } + + private void sharedElementExitBack() { + if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { + startTransition(new Runnable() { + public void run() { startSharedElementExit(); } - break; + }); } } private void startSharedElementExit() { - if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { - Transition transition = getSharedElementExitTransition(); - TransitionManager.beginDelayedTransition(getDecor(), transition); - ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, - mSharedElementNames); - setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); + Transition transition = getSharedElementExitTransition(); + final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, + mSharedElementNames); + mSharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames); + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + setViewVisibility(mSharedElements, View.INVISIBLE); + ViewGroupOverlay overlay = getDecor().getOverlay(); + if (mSharedElementSnapshots != null) { + for (int i = 0; i < mSharedElementSnapshots.size(); i++) { + overlay.add(mSharedElementSnapshots.get(i)); + } + } + } + }); + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); + return true; + } + }); + TransitionManager.beginDelayedTransition(getDecor(), transition); + getDecor().invalidate(); + } + + private static ArrayList<View> copySnapshots(ArrayList<View> snapshots) { + ArrayList<View> copy = new ArrayList<View>(snapshots.size()); + for (int i = 0; i < snapshots.size(); i++) { + View view = snapshots.get(i); + View viewCopy = new View(view.getContext()); + viewCopy.setBackground(view.getBackground()); + copy.add(viewCopy); } + return copy; } private void hideSharedElements() { - setViewVisibility(mSharedElements, View.INVISIBLE); + if (!mIsHidden) { + setViewVisibility(mSharedElements, View.INVISIBLE); + } + if (mSharedElementSnapshots != null) { + ViewGroupOverlay overlay = getDecor().getOverlay(); + for (int i = 0; i < mSharedElementSnapshots.size(); i++) { + overlay.remove(mSharedElementSnapshots.get(i)); + } + mSharedElementSnapshots = null; + } finishIfNecessary(); } public void startExit() { - beginTransitions(); + startTransition(new Runnable() { + @Override + public void run() { + beginTransitions(); + } + }); setViewVisibility(mTransitioningViews, View.INVISIBLE); } @@ -159,29 +214,25 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }, options); + startTransition(new Runnable() { + @Override + public void run() { + startExitTransition(); + } + }); + } + + private void startExitTransition() { Transition sharedElementTransition = mSharedElements.isEmpty() ? null : getSharedElementTransition(); if (sharedElementTransition == null) { sharedElementTransitionComplete(); } - Transition transition = mergeTransitions(sharedElementTransition, getExitTransition()); - if (transition == null) { - mExitTransitionStarted = true; - } else { + Transition transition = mergeTransitions(sharedElementTransition, + getExitTransition()); + if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); setViewVisibility(mTransitioningViews, View.INVISIBLE); - getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mExitTransitionStarted = true; - if (mExitSharedElementBundle != null) { - startSharedElementExit(); - } - notifyComplete(); - return true; - } - }); } } @@ -199,7 +250,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }); - mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS); + mBackgroundAnimator.setDuration(getFadeDuration()); mBackgroundAnimator.start(); } } @@ -212,7 +263,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { if (viewsTransition == null) { exitTransitionComplete(); } else { - viewsTransition.addListener(new Transition.TransitionListenerAdapter() { + viewsTransition.addListener(new ContinueTransitionListener() { @Override public void onTransitionEnd(Transition transition) { exitTransitionComplete(); @@ -238,7 +289,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { if (sharedElementTransition == null) { sharedElementTransitionComplete(); } else { - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { + sharedElementTransition.addListener(new ContinueTransitionListener() { @Override public void onTransitionEnd(Transition transition) { sharedElementTransitionComplete(); @@ -257,7 +308,6 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition viewsTransition = getExitTransition(); Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); - mExitTransitionStarted = true; if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); } @@ -269,15 +319,31 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } protected boolean isReadyToNotify() { - return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady - && mExitTransitionStarted; + return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; } private void sharedElementTransitionComplete() { - mSharedElementBundle = captureSharedElementState(); + mSharedElementBundle = mExitSharedElementBundle == null + ? captureSharedElementState() : captureExitSharedElementsState(); notifyComplete(); } + private Bundle captureExitSharedElementsState() { + Bundle bundle = new Bundle(); + Rect bounds = new Rect(); + for (int i = 0; i < mSharedElementNames.size(); i++) { + String name = mSharedElementNames.get(i); + Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); + if (sharedElementState != null) { + bundle.putBundle(name, sharedElementState); + } else { + View view = mSharedElements.get(i); + captureSharedElementState(view, name, bundle, bounds); + } + } + return bundle; + } + protected void notifyComplete() { if (isReadyToNotify()) { if (!mSharedElementNotified) { @@ -294,8 +360,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } private void finishIfNecessary() { - if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() - || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) { + if (mIsReturning && mExitNotified && mActivity != null && mSharedElementSnapshots == null) { mActivity.finish(); mActivity.overridePendingTransition(0, 0); mActivity = null; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 6c0d379..2ff3d57 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -1104,7 +1105,15 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * inflation. Maybe this should become a public API. Note sure. */ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { - return mActivity.getLayoutInflater(); + // Newer platform versions use the child fragment manager's LayoutInflaterFactory. + if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { + LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity); + getChildFragmentManager(); // Init if needed; use raw implementation below. + result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory()); + return result; + } else { + return mActivity.getLayoutInflater(); + } } /** diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 76f9d97..a97fa65 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -19,6 +19,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; +import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Bundle; @@ -27,11 +28,13 @@ import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; +import android.util.AttributeSet; import android.util.DebugUtils; import android.util.Log; import android.util.LogWriter; import android.util.SparseArray; import android.util.SuperNotCalledException; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -397,7 +400,7 @@ interface FragmentContainer { /** * Container for fragments associated with an activity. */ -final class FragmentManagerImpl extends FragmentManager { +final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { static boolean DEBUG = false; static final String TAG = "FragmentManager"; @@ -432,7 +435,7 @@ final class FragmentManagerImpl extends FragmentManager { boolean mDestroyed; String mNoTransactionsBecause; boolean mHavePendingDeferredStart; - + // Temporary vars for state save and restore. Bundle mStateBundle = null; SparseArray<Parcelable> mStateArray = null; @@ -1494,7 +1497,7 @@ final class FragmentManagerImpl extends FragmentManager { return false; } final BackStackRecord bss = mBackStack.remove(last); - bss.popFromBackStack(true); + bss.popFromBackStack(true, null); reportBackStackChanged(); } else { int index = -1; @@ -1538,9 +1541,10 @@ final class FragmentManagerImpl extends FragmentManager { states.add(mBackStack.remove(i)); } final int LAST = states.size()-1; + BackStackRecord.TransitionState state = null; for (int i=0; i<=LAST; i++) { if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); - states.get(i).popFromBackStack(i == LAST); + state = states.get(i).popFromBackStack(i == LAST, state); } reportBackStackChanged(); } @@ -2051,4 +2055,100 @@ final class FragmentManagerImpl extends FragmentManager { } return animAttr; } + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + if (!"fragment".equals(name)) { + return null; + } + + String fname = attrs.getAttributeValue(null, "class"); + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); + if (fname == null) { + fname = a.getString(com.android.internal.R.styleable.Fragment_name); + } + int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID); + String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); + a.recycle(); + + int containerId = parent != null ? parent.getId() : 0; + if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { + throw new IllegalArgumentException(attrs.getPositionDescription() + + ": Must specify unique android:id, android:tag, or have a parent with" + + " an id for " + fname); + } + + // If we restored from a previous state, we may already have + // instantiated this fragment from the state and should use + // that instance instead of making a new one. + Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null; + if (fragment == null && tag != null) { + fragment = findFragmentByTag(tag); + } + if (fragment == null && containerId != View.NO_ID) { + fragment = findFragmentById(containerId); + } + + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" + + Integer.toHexString(id) + " fname=" + fname + + " existing=" + fragment); + if (fragment == null) { + fragment = Fragment.instantiate(context, fname); + fragment.mFromLayout = true; + fragment.mFragmentId = id != 0 ? id : containerId; + fragment.mContainerId = containerId; + fragment.mTag = tag; + fragment.mInLayout = true; + fragment.mFragmentManager = this; + fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + addFragment(fragment, true); + } else if (fragment.mInLayout) { + // A fragment already exists and it is not one we restored from + // previous state. + throw new IllegalArgumentException(attrs.getPositionDescription() + + ": Duplicate id 0x" + Integer.toHexString(id) + + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) + + " with another fragment for " + fname); + } else { + // This fragment was retained from a previous instance; get it + // going now. + fragment.mInLayout = true; + // If this fragment is newly instantiated (either right now, or + // from last saved state), then give it the attributes to + // initialize itself. + if (!fragment.mRetaining) { + fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + } + } + + // If we haven't finished entering the CREATED state ourselves yet, + // push the inflated child fragment along. + if (mCurState < Fragment.CREATED && fragment.mFromLayout) { + moveToState(fragment, Fragment.CREATED, 0, 0, false); + } else { + moveToState(fragment); + } + + if (fragment.mView == null) { + throw new IllegalStateException("Fragment " + fname + + " did not create a view."); + } + if (id != 0) { + fragment.mView.setId(id); + } + if (fragment.mView.getTag() == null) { + fragment.mView.setTag(tag); + } + return fragment.mView; + } + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + return null; + } + + LayoutInflater.Factory2 getLayoutInflaterFactory() { + return this; + } } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 6e99899..b6ea3c3 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -1,5 +1,8 @@ package android.app; +import android.util.Pair; +import android.view.View; + /** * API for performing a set of Fragment operations. * @@ -169,6 +172,36 @@ public abstract class FragmentTransaction { public abstract FragmentTransaction setTransition(int transit); /** + * Set a {@link android.transition.Transition} resource id to use with this transaction. + * <var>transitionId</var> will be played for fragments when going forward and when popping + * the back stack. + * @param sceneRootId The ID of the element acting as the scene root for the transition. + * This should be a ViewGroup containing all Fragments in the transaction. + * @param transitionId The resource ID for the Transition used during the Fragment transaction. + */ + public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId); + + /** + * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden + * Fragment to a View from a shown or added Fragment. + * <var>sharedElement</var> must have a unique transitionName in the View hierarchy. + * @param sharedElement A View in a disappearing Fragment to match with a View in an + * appearing Fragment. + * @param name The transitionName for a View in an appearing Fragment to match to the shared + * element. + */ + public abstract FragmentTransaction setSharedElement(View sharedElement, String name); + + /** + * Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden + * Fragments to a Views from a shown or added Fragments. Views in + * <var>sharedElements</var> must have unique transitionNames in the View hierarchy. + * @param sharedElements Pairs of Views in disappearing Fragments to transitionNames in + * appearing Fragments. + */ + public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements); + + /** * Set a custom style resource that will be used for resolving transit * animations. */ diff --git a/core/java/android/app/IActivityContainerCallback.aidl b/core/java/android/app/IActivityContainerCallback.aidl index 7f6d2c3..99d0a6f 100644 --- a/core/java/android/app/IActivityContainerCallback.aidl +++ b/core/java/android/app/IActivityContainerCallback.aidl @@ -21,4 +21,5 @@ import android.os.IBinder; /** @hide */ interface IActivityContainerCallback { oneway void setVisible(IBinder container, boolean visible); + oneway void onAllActivitiesComplete(IBinder container); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 8434c2a..b630278 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -176,7 +176,8 @@ public interface IActivityManager extends IInterface { public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException; + IUiAutomationConnection connection, int userId, + String abiOverride) throws RemoteException; public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) throws RemoteException; @@ -428,6 +429,9 @@ public interface IActivityManager extends IInterface { public IBinder getHomeActivityToken() throws RemoteException; /** @hide */ + public void startLockTaskModeOnCurrent() throws RemoteException; + + /** @hide */ public void startLockTaskMode(int taskId) throws RemoteException; /** @hide */ @@ -437,6 +441,9 @@ public interface IActivityManager extends IInterface { public void stopLockTaskMode() throws RemoteException; /** @hide */ + public void stopLockTaskModeOnCurrent() throws RemoteException; + + /** @hide */ public boolean isInLockTaskMode() throws RemoteException; /** @hide */ @@ -743,4 +750,6 @@ public interface IActivityManager extends IInterface { int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218; int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219; int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220; + int START_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+221; + int STOP_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+222; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b78b9c9..c583998 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -188,6 +188,9 @@ public class Instrumentation { endPerformanceSnapshot(); } if (mPerfMetrics != null) { + if (results == null) { + results = new Bundle(); + } results.putAll(mPerfMetrics); } if (mUiAutomation != null) { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 3ae8bfc..065e88d 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -16,8 +16,8 @@ package android.app; +import android.text.TextUtils; import android.util.ArrayMap; -import com.android.internal.util.ArrayUtils; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -52,6 +52,8 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; final class IntentReceiverLeaked extends AndroidRuntimeException { @@ -79,6 +81,8 @@ public final class LoadedApk { final String mPackageName; private final String mAppDir; private final String mResDir; + private final String[] mSplitAppDirs; + private final String[] mSplitResDirs; private final String[] mOverlayDirs; private final String[] mSharedLibraries; private final String mDataDir; @@ -116,13 +120,14 @@ public final class LoadedApk { public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { + final int myUid = Process.myUid(); mActivityThread = activityThread; mApplicationInfo = aInfo; mPackageName = aInfo.packageName; mAppDir = aInfo.sourceDir; - final int myUid = Process.myUid(); - mResDir = aInfo.uid == myUid ? aInfo.sourceDir - : aInfo.publicSourceDir; + mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; + mSplitAppDirs = aInfo.splitSourceDirs; + mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs; mOverlayDirs = aInfo.resourceDirs; if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid), @@ -149,6 +154,8 @@ public final class LoadedApk { mPackageName = "android"; mAppDir = null; mResDir = null; + mSplitAppDirs = null; + mSplitResDirs = null; mOverlayDirs = null; mSharedLibraries = null; mDataDir = null; @@ -214,53 +221,6 @@ public final class LoadedApk { return ai.sharedLibraryFiles; } - /** - * Combines two arrays (of library names) such that they are - * concatenated in order but are devoid of duplicates. The - * result is a single string with the names of the libraries - * separated by colons, or <code>null</code> if both lists - * were <code>null</code> or empty. - * - * @param list1 null-ok; the first list - * @param list2 null-ok; the second list - * @return null-ok; the combination - */ - private static String combineLibs(String[] list1, String[] list2) { - StringBuilder result = new StringBuilder(300); - boolean first = true; - - if (list1 != null) { - for (String s : list1) { - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - // Only need to check for duplicates if list1 was non-empty. - boolean dupCheck = !first; - - if (list2 != null) { - for (String s : list2) { - if (dupCheck && ArrayUtils.contains(list1, s)) { - continue; - } - - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - return result.toString(); - } - public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { @@ -268,8 +228,15 @@ public final class LoadedApk { } if (mIncludeCode && !mPackageName.equals("android")) { - String zip = mAppDir; - String libraryPath = mLibDir; + final ArrayList<String> zipPaths = new ArrayList<>(); + final ArrayList<String> libPaths = new ArrayList<>(); + + zipPaths.add(mAppDir); + if (mSplitAppDirs != null) { + Collections.addAll(zipPaths, mSplitAppDirs); + } + + libPaths.add(mLibDir); /* * The following is a bit of a hack to inject @@ -280,50 +247,70 @@ public final class LoadedApk { * concatenation of both apps' shared library lists. */ - String instrumentationAppDir = - mActivityThread.mInstrumentationAppDir; - String instrumentationAppLibraryDir = - mActivityThread.mInstrumentationAppLibraryDir; - String instrumentationAppPackage = - mActivityThread.mInstrumentationAppPackage; - String instrumentedAppDir = - mActivityThread.mInstrumentedAppDir; - String instrumentedAppLibraryDir = - mActivityThread.mInstrumentedAppLibraryDir; + String instrumentationPackageName = mActivityThread.mInstrumentationPackageName; + String instrumentationAppDir = mActivityThread.mInstrumentationAppDir; + String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs; + String instrumentationLibDir = mActivityThread.mInstrumentationLibDir; + + String instrumentedAppDir = mActivityThread.mInstrumentedAppDir; + String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs; + String instrumentedLibDir = mActivityThread.mInstrumentedLibDir; String[] instrumentationLibs = null; if (mAppDir.equals(instrumentationAppDir) || mAppDir.equals(instrumentedAppDir)) { - zip = instrumentationAppDir + ":" + instrumentedAppDir; - libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir; - if (! instrumentedAppDir.equals(instrumentationAppDir)) { - instrumentationLibs = - getLibrariesFor(instrumentationAppPackage); + zipPaths.clear(); + zipPaths.add(instrumentationAppDir); + if (instrumentationSplitAppDirs != null) { + Collections.addAll(zipPaths, instrumentationSplitAppDirs); + } + zipPaths.add(instrumentedAppDir); + if (instrumentedSplitAppDirs != null) { + Collections.addAll(zipPaths, instrumentedSplitAppDirs); + } + + libPaths.clear(); + libPaths.add(instrumentationLibDir); + libPaths.add(instrumentedLibDir); + + if (!instrumentedAppDir.equals(instrumentationAppDir)) { + instrumentationLibs = getLibrariesFor(instrumentationPackageName); } } - if ((mSharedLibraries != null) || - (instrumentationLibs != null)) { - zip = - combineLibs(mSharedLibraries, instrumentationLibs) - + ':' + zip; + if (mSharedLibraries != null) { + for (String lib : mSharedLibraries) { + if (!zipPaths.contains(lib)) { + zipPaths.add(0, lib); + } + } } + if (instrumentationLibs != null) { + for (String lib : instrumentationLibs) { + if (!zipPaths.contains(lib)) { + zipPaths.add(0, lib); + } + } + } + + final String zip = TextUtils.join(File.pathSeparator, zipPaths); + final String lib = TextUtils.join(File.pathSeparator, libPaths); + /* * With all the combination done (if necessary, actually * create the class loader. */ if (ActivityThread.localLOGV) - Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath); + Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + lib); // Temporarily disable logging of disk reads on the Looper thread // as this is early and necessary. StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); - mClassLoader = - ApplicationLoaders.getDefault().getClassLoader( - zip, libraryPath, mBaseClassLoader); + mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib, + mBaseClassLoader); initializeJavaContextClassLoader(); StrictMode.setThreadPolicy(oldPolicy); @@ -469,6 +456,14 @@ public final class LoadedApk { return mResDir; } + public String[] getSplitAppDirs() { + return mSplitAppDirs; + } + + public String[] getSplitResDirs() { + return mSplitResDirs; + } + public String[] getOverlayDirs() { return mOverlayDirs; } @@ -487,7 +482,7 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, mOverlayDirs, + mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this); } return mResources; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index debe68a..84f7e5f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2918,16 +2918,16 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a large image attachment. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>BigPictureStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.BigPictureStyle( - * new Notification.Builder() - * .setContentTitle("New photo from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_post) - * .setLargeIcon(aBitmap)) - * .bigPicture(aBigBitmap) - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New photo from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_post) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.BigPictureStyle() + * .bigPicture(aBigBitmap)) + * .build(); * </pre> * * @see Notification#bigContentView @@ -3014,16 +3014,16 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a lot of text. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>BigTextStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.BigTextStyle( - * new Notification.Builder() - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .setLargeIcon(aBitmap)) - * .bigText(aVeryLongString) - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New mail from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.BigTextStyle() + * .bigText(aVeryLongString)) + * .build(); * </pre> * * @see Notification#bigContentView @@ -3108,19 +3108,19 @@ public class Notification implements Parcelable /** * Helper class for generating large-format notifications that include a list of (up to 5) strings. * - * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: + * Here's how you'd set the <code>InboxStyle</code> on a notification: * <pre class="prettyprint"> - * Notification noti = new Notification.InboxStyle( - * new Notification.Builder() - * .setContentTitle("5 New mails from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .setLargeIcon(aBitmap)) - * .addLine(str1) - * .addLine(str2) - * .setContentTitle("") - * .setSummaryText("+3 more") - * .build(); + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("5 New mails from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .setLargeIcon(aBitmap) + * .setStyle(new Notification.InboxStyle() + * .addLine(str1) + * .addLine(str2) + * .setContentTitle("") + * .setSummaryText("+3 more")) + * .build(); * </pre> * * @see Notification#bigContentView diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index a67faa0..3c13115 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -149,9 +149,9 @@ public class ResourcesManager { * @param compatInfo the compability info. Must not be null. * @param token the application token for determining stack bounds. */ - public Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, - int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, - IBinder token) { + public Resources getTopLevelResources(String resDir, String[] splitResDirs, + String[] overlayDirs, String[] libDirs, int displayId, + Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { final float scale = compatInfo.applicationScale; ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token); Resources r; @@ -182,6 +182,14 @@ public class ResourcesManager { return null; } + if (splitResDirs != null) { + for (String splitResDir : splitResDirs) { + if (assets.addAssetPath(splitResDir) == 0) { + return null; + } + } + } + if (overlayDirs != null) { for (String idmapPath : overlayDirs) { assets.addOverlayPath(idmapPath); diff --git a/core/java/android/app/SharedElementListener.java b/core/java/android/app/SharedElementListener.java index e03e42e..14fbfab 100644 --- a/core/java/android/app/SharedElementListener.java +++ b/core/java/android/app/SharedElementListener.java @@ -108,7 +108,7 @@ public abstract class SharedElementListener { * @param names The names of all shared elements transferred from the calling Activity * to the started Activity. * @param sharedElements The mapping of shared element names to Views. The best guess - * will be filled into sharedElements based on the View names. + * will be filled into sharedElements based on the transitionNames. */ public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {} } diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 1015514..ca40436 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -166,6 +166,40 @@ public class DeviceAdminReceiver extends BroadcastReceiver { = "android.app.action.ACTION_PASSWORD_EXPIRING"; /** + * Action sent to a device administrator to notify that the device is entering + * or exiting lock task mode from an authorized package. The extra + * {@link #EXTRA_LOCK_TASK_ENTERING} will describe whether entering or exiting + * the mode. If entering, the extra {@link #EXTRA_LOCK_TASK_PACKAGE} will describe + * the authorized package using lock task mode. + * + * @see DevicePolicyManager#isLockTaskPermitted + * + * <p>The calling device admin must be the device owner or profile + * owner to receive this broadcast. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCK_TASK_CHANGED + = "android.app.action.ACTION_LOCK_TASK_CHANGED"; + + /** + * A boolean describing whether the device is currently entering or exiting + * lock task mode. + * + * @see #ACTION_LOCK_TASK_CHANGED + */ + public static final String EXTRA_LOCK_TASK_ENTERING = + "android.app.extra.LOCK_TASK_ENTERING"; + + /** + * A boolean describing whether the device is currently entering or exiting + * lock task mode. + * + * @see #ACTION_LOCK_TASK_CHANGED + */ + public static final String EXTRA_LOCK_TASK_PACKAGE = + "android.app.extra.LOCK_TASK_PACKAGE"; + + /** * Broadcast Action: This broadcast is sent to indicate that provisioning of a managed profile * or managed device has completed successfully. * @@ -222,6 +256,12 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * Called after the administrator is first enabled, as a result of * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you * can use {@link DevicePolicyManager} to set your desired policies. + * + * <p> If the admin is activated by a device owner, then the intent + * may contain private extras that are relevant to user setup. + * {@see DevicePolicyManager#createAndInitializeUser(ComponentName, String, String, + * ComponentName, Intent)} + * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. */ @@ -335,6 +375,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called when a device is entering or exiting lock task mode by a package + * authorized by {@link DevicePolicyManager#isLockTaskPermitted(String)} + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param isEnteringLockTask Whether the device is entering or exiting lock task mode. + * @param pkg If entering, the authorized package using lock task mode, otherwise null. + */ + public void onLockTaskModeChanged(Context context, Intent intent, boolean isEnteringLockTask, + String pkg) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -363,6 +416,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onPasswordExpiring(context, intent); } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { onProfileProvisioningComplete(context, intent); + } else if (ACTION_LOCK_TASK_CHANGED.equals(action)) { + boolean isEntering = intent.getBooleanExtra(EXTRA_LOCK_TASK_ENTERING, false); + String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE); + onLockTaskModeChanged(context, intent, isEntering, pkg); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 157b8ec..4351f9d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -25,6 +26,9 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.RestrictionsManager; +import android.media.AudioService; +import android.net.ProxyInfo; import android.os.Bundle; import android.os.Handler; import android.os.Process; @@ -34,6 +38,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.trust.TrustAgentService; import android.util.Log; import com.android.org.conscrypt.TrustedCertificateStore; @@ -81,6 +86,20 @@ public class DevicePolicyManager { } /** + * Activity action: Used to indicate that the receiving activity is being started as part of the + * managed profile provisioning flow. This intent is typically sent to a mobile device + * management application (mdm) after the first part of the provisioning process is complete in + * the expectation that this app will (after optionally showing it's own UI) ultimately call + * {@link #ACTION_PROVISION_MANAGED_PROFILE} to complete the creation of the managed profile. + * + * <p> The intent may contain the extras {@link #EXTRA_PROVISIONING_TOKEN} and + * {@link #EXTRA_PROVISIONING_EMAIL_ADDRESS}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEND_PROVISIONING_VALUES + = "android.app.action.ACTION_SEND_PROVISIONING_VALUES"; + + /** * Activity action: Starts the provisioning flow which sets up a managed profile. * * <p>A managed profile allows data separation for example for the usage of a @@ -110,6 +129,17 @@ public class DevicePolicyManager { = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE"; /** + * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the + * user has already consented to the creation of the managed profile. + * The intent must contain the extras + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and + * {@link #EXTRA_PROVISIONING_TOKEN} + * @hide + */ + public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED + = "android.app.action.ACTION_PROVISIONING_USER_HAS_CONSENTED"; + + /** * 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}. @@ -118,6 +148,18 @@ public class DevicePolicyManager { = "android.app.extra.deviceAdminPackageName"; /** + * An int extra used to identify that during the current setup process the user has already + * consented to setting up a managed profile. This is typically received by + * a mobile device management application when it is started with + * {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent + * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. The + * token indicates that steps asking for user consent can be skipped as the user has previously + * consented. + */ + public static final String EXTRA_PROVISIONING_TOKEN + = "android.app.extra.token"; + + /** * 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} @@ -126,6 +168,17 @@ public class DevicePolicyManager { = "android.app.extra.defaultManagedProfileName"; /** + * A String extra holding the email address of the profile that is created during managed + * profile provisioning. This is typically received by a mobile management application when it + * is started with {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent + * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. It + * is eventually passed on in an intent + * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}. + */ + public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS + = "android.app.extra.ManagedProfileEmailAddress"; + + /** * 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 @@ -183,15 +236,16 @@ public class DevicePolicyManager { public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"; /** - * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user. + * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a + * managed profile to its parent. */ - public static int FLAG_TO_PRIMARY_USER = 0x0001; + public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001; /** - * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed - * profile. + * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the + * parent to its managed profile. */ - public static int FLAG_TO_MANAGED_PROFILE = 0x0002; + public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; /** * Return true if the given administrator component is currently @@ -1236,6 +1290,32 @@ public class DevicePolicyManager { } /** + * Set a network-independent global HTTP proxy. This is not normally what you want + * for typical HTTP proxies - they are generally network dependent. However if you're + * 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. + * + * <p>This method requires the caller to be the device owner. + * + * <p>This proxy is only a recommendation and it is possible that some apps will ignore it. + * @see ProxyInfo + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param proxyInfo The a {@link ProxyInfo} object defining the new global + * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + */ + public void setRecommendedGlobalProxy(ComponentName admin, ProxyInfo proxyInfo) { + if (mService != null) { + try { + mService.setRecommendedGlobalProxy(admin, proxyInfo); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Returns the component name setting the global proxy. * @return ComponentName object of the device admin that set the global proxy, or * null if no admin has set the proxy. @@ -1317,7 +1397,7 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3; /** - * Ignore trust agent state on secure keyguard screens + * Ignore {@link TrustAgentService} state on secure keyguard screens * (e.g. PIN/Pattern/Password). */ public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4; @@ -1427,12 +1507,11 @@ public class DevicePolicyManager { * * @return false if the certBuffer cannot be parsed or installation is * interrupted, otherwise true - * @hide */ - public boolean installCaCert(byte[] certBuffer) { + public boolean installCaCert(ComponentName who, byte[] certBuffer) { if (mService != null) { try { - return mService.installCaCert(certBuffer); + return mService.installCaCert(who, certBuffer); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1442,13 +1521,14 @@ public class DevicePolicyManager { /** * Uninstalls the given certificate from the list of User CAs, if present. - * - * @hide */ - public void uninstallCaCert(byte[] certBuffer) { + public void uninstallCaCert(ComponentName who, byte[] certBuffer) { if (mService != null) { try { - mService.uninstallCaCert(certBuffer); + final String alias = getCaCertAlias(certBuffer); + mService.uninstallCaCert(who, alias); + } catch (CertificateException e) { + Log.w(TAG, "Unable to parse certificate", e); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1457,10 +1537,8 @@ public class DevicePolicyManager { /** * Returns whether there are any user-installed CA certificates. - * - * @hide */ - public static boolean hasAnyCaCertsInstalled() { + public boolean hasAnyCaCertsInstalled() { TrustedCertificateStore certStore = new TrustedCertificateStore(); Set<String> aliases = certStore.userAliases(); return aliases != null && !aliases.isEmpty(); @@ -1468,18 +1546,10 @@ public class DevicePolicyManager { /** * Returns whether this certificate has been installed as a User CA. - * - * @hide */ public boolean hasCaCertInstalled(byte[] certBuffer) { - TrustedCertificateStore certStore = new TrustedCertificateStore(); - String alias; - byte[] pemCert; try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(certBuffer)); - return certStore.getCertificateAlias(cert) != null; + return getCaCertAlias(certBuffer) != null; } catch (CertificateException ce) { Log.w(TAG, "Could not parse certificate", ce); } @@ -1487,6 +1557,17 @@ public class DevicePolicyManager { } /** + * Returns the alias of a given CA certificate in the certificate store, or null if it + * doesn't exist. + */ + private static String getCaCertAlias(byte[] certBuffer) throws CertificateException { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final X509Certificate cert = (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(certBuffer)); + return new TrustedCertificateStore().getCertificateAlias(cert); + } + + /** * Called by an application that is administering the device to disable all cameras * on the device. After setting this, no applications will be able to access any cameras * on the device. @@ -1754,6 +1835,25 @@ public class DevicePolicyManager { return isDeviceOwnerApp(packageName); } + /** + * Clears the current device owner. The caller must be the device owner. + * + * This function should be used cautiously as once it is called it cannot + * be undone. The device owner can only be set as a part of device setup + * before setup completes. + * + * @param packageName The package name of the device owner. + */ + public void clearDeviceOwnerApp(String packageName) { + if (mService != null) { + try { + mService.clearDeviceOwner(packageName); + } catch (RemoteException re) { + Log.w(TAG, "Failed to clear device owner"); + } + } + } + /** @hide */ public String getDeviceOwner() { if (mService != null) { @@ -1961,17 +2061,18 @@ public class DevicePolicyManager { } /** - * Called by a profile owner to forward intents sent from the managed profile to the owner, or - * from the owner to the managed profile. - * If an intent matches this intent filter, then activities belonging to the other user can - * respond to this intent. + * Called by the profile owner so that some intents sent in the managed profile can also be + * resolved in the parent, or vice versa. * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param filter if an intent matches this IntentFilter, then it can be forwarded. + * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the + * other profile + * @param flags {@link DevicePolicyManager#FLAG_MANAGED_CAN_ACCESS_PARENT} and + * {@link DevicePolicyManager#FLAG_PARENT_CAN_ACCESS_MANAGED} are supported. */ - public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) { + public void addCrossProfileIntentFilter(ComponentName admin, IntentFilter filter, int flags) { if (mService != null) { try { - mService.addForwardingIntentFilter(admin, filter, flags); + mService.addCrossProfileIntentFilter(admin, filter, flags); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1979,14 +2080,14 @@ public class DevicePolicyManager { } /** - * Called by a profile owner to remove the forwarding intent filters from the current user - * and from the owner. + * Called by a profile owner to remove the cross-profile intent filters from the managed profile + * and from the parent. * @param admin Which {@link DeviceAdminReceiver} this request is associated with. */ - public void clearForwardingIntentFilters(ComponentName admin) { + public void clearCrossProfileIntentFilters(ComponentName admin) { if (mService != null) { try { - mService.clearForwardingIntentFilters(admin); + mService.clearCrossProfileIntentFilters(admin); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2014,6 +2115,41 @@ public class DevicePolicyManager { } /** + * Called by a device owner to create a user with the specified name. The UserHandle returned + * by this method should not be persisted as user handles are recycled as users are removed and + * created. If you need to persist an identifier for this user, use + * {@link UserManager#getSerialNumberForUser}. The new user will be started in the background + * immediately. + * + * <p> profileOwnerComponent is the {@link DeviceAdminReceiver} to be the profile owner as well + * as registered as an active admin on the new user. The profile owner package will be + * installed on the new user if it already is installed on the device. + * + * <p>If the optionalInitializeData is not null, then the extras will be passed to the + * profileOwnerComponent when onEnable is called. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param name the user's name + * @param ownerName the human readable name of the organisation associated with this DPM. + * @param profileOwnerComponent The {@link DeviceAdminReceiver} that will be an active admin on + * the user. + * @param adminExtras Extras that will be passed to onEnable of the admin receiver + * on the new user. + * @see UserHandle + * @return the UserHandle object for the created user, or null if the user could not be created. + */ + public UserHandle createAndInitializeUser(ComponentName admin, String name, String ownerName, + ComponentName profileOwnerComponent, Bundle adminExtras) { + try { + return mService.createAndInitializeUser(admin, name, ownerName, profileOwnerComponent, + adminExtras); + } catch (RemoteException re) { + Log.w(TAG, "Could not create a user", re); + } + return null; + } + + /** * Called by a device owner to remove a user and all associated data. The primary user can * not be removed. * @@ -2161,45 +2297,6 @@ public class DevicePolicyManager { } /** - * Called by profile or device owner to re-enable a system app that was disabled by default - * when the managed profile was created. This should only be called from a profile or device - * owner running within a managed profile. - * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName The package to be re-enabled in the current profile. - */ - public void enableSystemApp(ComponentName admin, String packageName) { - if (mService != null) { - try { - mService.enableSystemApp(admin, packageName); - } catch (RemoteException e) { - Log.w(TAG, "Failed to install package: " + packageName); - } - } - } - - /** - * Called by profile or device owner to re-enable system apps by intent that were disabled - * by default when the managed profile was created. This should only be called from a profile - * or device owner running within a managed profile. - * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param intent An intent matching the app(s) to be installed. All apps that resolve for this - * intent will be re-enabled in the current profile. - * @return int The number of activities that matched the intent and were installed. - */ - public int enableSystemApp(ComponentName admin, Intent intent) { - if (mService != null) { - try { - return mService.enableSystemAppWithIntent(admin, intent); - } catch (RemoteException e) { - Log.w(TAG, "Failed to install packages matching filter: " + intent); - } - } - return 0; - } - - /** * Called by a profile owner to disable account management for a specific type of account. * * <p>The calling device admin must be a profile owner. If it is not, a @@ -2247,15 +2344,20 @@ public class DevicePolicyManager { } /** - * Sets which components may enter lock task mode. + * Sets which packages may enter lock task mode. + * + * <p>Any packages that shares uid with an allowed package will also be allowed + * to activate lock task. + * + * This function can only be called by the device owner. + * @param packages The list of packages allowed to enter lock task mode * - * This function can only be called by the device owner or the profile owner. - * @param components The list of components allowed to enter lock task mode + * @see Activity#startLockTask() */ - public void setLockTaskComponents(ComponentName[] components) throws SecurityException { + public void setLockTaskPackages(String[] packages) throws SecurityException { if (mService != null) { try { - mService.setLockTaskComponents(components); + mService.setLockTaskPackages(packages); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2263,13 +2365,13 @@ public class DevicePolicyManager { } /** - * This function returns the list of components allowed to start the lock task mode. + * This function returns the list of packages allowed to start the lock task mode. * @hide */ - public ComponentName[] getLockTaskComponents() { + public String[] getLockTaskPackages() { if (mService != null) { try { - return mService.getLockTaskComponents(); + return mService.getLockTaskPackages(); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2280,12 +2382,12 @@ public class DevicePolicyManager { /** * This function lets the caller know whether the given component is allowed to start the * lock task mode. - * @param component The component to check + * @param pkg The package to check */ - public boolean isLockTaskPermitted(ComponentName component) { + public boolean isLockTaskPermitted(String pkg) { if (mService != null) { try { - return mService.isLockTaskPermitted(component); + return mService.isLockTaskPermitted(pkg); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2330,4 +2432,56 @@ public class DevicePolicyManager { } } + /** + * Designates a specific broadcast receiver component as the provider for + * making permission requests of a local or remote administrator of the user. + * <p/> + * Only a profile owner can designate the restrictions provider. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param receiver The component name of a BroadcastReceiver that handles the + * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null, + * it removes the restrictions provider previously assigned. + */ + public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) { + if (mService != null) { + try { + mService.setRestrictionsProvider(admin, receiver); + } catch (RemoteException re) { + Log.w(TAG, "Failed to set permission provider on device policy service"); + } + } + } + + /** + * Called by profile or device owners to set the master volume mute on or off. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param on {@code true} to mute master volume, {@code false} to turn mute off. + */ + public void setMasterVolumeMuted(ComponentName admin, boolean on) { + if (mService != null) { + try { + mService.setMasterVolumeMuted(admin, on); + } catch (RemoteException re) { + Log.w(TAG, "Failed to setMasterMute on device policy service"); + } + } + } + + /** + * Called by profile or device owners to check whether the master volume mute is on or off. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return {@code true} if master volume is muted, {@code false} if it's not. + */ + public boolean isMasterVolumeMuted(ComponentName admin) { + if (mService != null) { + try { + return mService.isMasterVolumeMuted(admin); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get isMasterMute on device policy service"); + } + } + return false; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3c08c14..5333ea6 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -20,6 +20,7 @@ package android.app.admin; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.net.ProxyInfo; import android.os.Bundle; import android.os.RemoteCallback; import android.os.UserHandle; @@ -78,6 +79,7 @@ interface IDevicePolicyManager { ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle); ComponentName getGlobalProxyAdmin(int userHandle); + void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle); boolean getStorageEncryption(in ComponentName who, int userHandle); @@ -106,14 +108,15 @@ interface IDevicePolicyManager { boolean isDeviceOwner(String packageName); String getDeviceOwner(); String getDeviceOwnerName(); + void clearDeviceOwner(String packageName); boolean setProfileOwner(String packageName, String ownerName, int userHandle); String getProfileOwner(int userHandle); String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); - boolean installCaCert(in byte[] certBuffer); - void uninstallCaCert(in byte[] certBuffer); + boolean installCaCert(in ComponentName admin, in byte[] certBuffer); + void uninstallCaCert(in ComponentName admin, in String alias); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); @@ -121,27 +124,33 @@ interface IDevicePolicyManager { void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); Bundle getApplicationRestrictions(in ComponentName who, in String packageName); + void setRestrictionsProvider(in ComponentName who, in ComponentName provider); + ComponentName getRestrictionsProvider(int userHandle); + void setUserRestriction(in ComponentName who, in String key, boolean enable); - void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); - void clearForwardingIntentFilters(in ComponentName admin); + void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); + void clearCrossProfileIntentFilters(in ComponentName admin); boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked); int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked); boolean isApplicationBlocked(in ComponentName admin, in String packageName); UserHandle createUser(in ComponentName who, in String name); + UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras); boolean removeUser(in ComponentName who, in UserHandle userHandle); - void enableSystemApp(in ComponentName admin, in String packageName); - int enableSystemAppWithIntent(in ComponentName admin, in Intent intent); - void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); String[] getAccountTypesWithManagementDisabled(); - void setLockTaskComponents(in ComponentName[] components); - ComponentName[] getLockTaskComponents(); - boolean isLockTaskPermitted(in ComponentName component); + void setLockTaskPackages(in String[] packages); + String[] getLockTaskPackages(); + boolean isLockTaskPermitted(in String pkg); void setGlobalSetting(in ComponentName who, in String setting, in String value); void setSecureSetting(in ComponentName who, in String setting, in String value); + + void setMasterVolumeMuted(in ComponentName admin, boolean on); + boolean isMasterVolumeMuted(in ComponentName admin); + + void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId); } diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java new file mode 100644 index 0000000..4631323 --- /dev/null +++ b/core/java/android/app/backup/BackupTransport.java @@ -0,0 +1,522 @@ +/* + * 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.backup; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.backup.IBackupTransport; + +/** + * Concrete class that provides a stable-API bridge between IBackupTransport + * and its implementations. + * + * @hide + */ +public class BackupTransport { + // Zero return always means things are okay. If returned from + // getNextFullRestoreDataChunk(), it means that no data could be delivered at + // this time, but the restore is still running and the caller should simply + // retry. + public static final int TRANSPORT_OK = 0; + + // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that + // we've delivered the entire data stream for the current restore target. + public static final int NO_MORE_DATA = -1; + + // Result codes that indicate real errors are negative and not -1 + public static final int TRANSPORT_ERROR = -1000; + public static final int TRANSPORT_NOT_INITIALIZED = -1001; + public static final int TRANSPORT_PACKAGE_REJECTED = -1002; + public static final int AGENT_ERROR = -1003; + public static final int AGENT_UNKNOWN = -1004; + + IBackupTransport mBinderImpl = new TransportImpl(); + /** @hide */ + public IBinder getBinder() { + return mBinderImpl.asBinder(); + } + + // ------------------------------------------------------------------------------------ + // Transport self-description and general configuration interfaces + // + + /** + * Ask the transport for the name under which it should be registered. This will + * typically be its host service's component name, but need not be. + */ + public String name() { + throw new UnsupportedOperationException("Transport name() not implemented"); + } + + /** + * Ask the transport for an Intent that can be used to launch any internal + * configuration Activity that it wishes to present. For example, the transport + * may offer a UI for allowing the user to supply login credentials for the + * transport's off-device backend. + * + * If the transport does not supply any user-facing configuration UI, it should + * return null from this method. + * + * @return An Intent that can be passed to Context.startActivity() in order to + * launch the transport's configuration UI. This method will return null + * if the transport does not offer any user-facing configuration UI. + */ + public Intent configurationIntent() { + return null; + } + + /** + * On demand, supply a one-line string that can be shown to the user that + * describes the current backend destination. For example, a transport that + * can potentially associate backup data with arbitrary user accounts should + * include the name of the currently-active account here. + * + * @return A string describing the destination to which the transport is currently + * sending data. This method should not return null. + */ + public String currentDestinationString() { + throw new UnsupportedOperationException( + "Transport currentDestinationString() not implemented"); + } + + /** + * Ask the transport where, on local device storage, to keep backup state blobs. + * This is per-transport so that mock transports used for testing can coexist with + * "live" backup services without interfering with the live bookkeeping. The + * returned string should be a name that is expected to be unambiguous among all + * available backup transports; the name of the class implementing the transport + * is a good choice. + * + * @return A unique name, suitable for use as a file or directory name, that the + * Backup Manager could use to disambiguate state files associated with + * different backup transports. + */ + public String transportDirName() { + throw new UnsupportedOperationException( + "Transport transportDirName() not implemented"); + } + + // ------------------------------------------------------------------------------------ + // Device-level operations common to both key/value and full-data storage + + /** + * Initialize the server side storage for this device, erasing all stored data. + * The transport may send the request immediately, or may buffer it. After + * this is called, {@link #finishBackup} will be called to ensure the request + * is sent and received successfully. + * + * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure). + */ + public int initializeDevice() { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Erase the given application's data from the backup destination. This clears + * out the given package's data from the current backup set, making it as though + * the app had never yet been backed up. After this is called, {@link finishBackup} + * must be called to ensure that the operation is recorded successfully. + * + * @return the same error codes as {@link #performBackup}. + */ + public int clearBackupData(PackageInfo packageInfo) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Finish sending application data to the backup destination. This must be + * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData} + * to ensure that all data is sent and the operation properly finalized. Only when this + * method returns true can a backup be assumed to have succeeded. + * + * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}. + */ + public int finishBackup() { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Key/value incremental backup support interfaces + + /** + * Verify that this is a suitable time for a key/value backup pass. This should return zero + * if a backup is reasonable right now, some positive value otherwise. This method + * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair. + * + * <p>If this is not a suitable time for a backup, the transport should return a + * backoff delay, in milliseconds, after which the Backup Manager should try again. + * + * @return Zero if this is a suitable time for a backup pass, or a positive time delay + * in milliseconds to suggest deferring the backup pass for a while. + */ + public long requestBackupTime() { + return 0; + } + + /** + * Send one application's key/value data update to the backup destination. The + * transport may send the data immediately, or may buffer it. After this is called, + * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully. + * + * @param packageInfo The identity of the application whose data is being backed up. + * This specifically includes the signature list for the package. + * @param data The data stream that resulted from invoking the application's + * BackupService.doBackup() method. This may be a pipe rather than a file on + * persistent media, so it may not be seekable. + * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account + * must be erased prior to the storage of the data provided here. The purpose of this + * is to provide a guarantee that no stale data exists in the restore set when the + * device begins providing incremental backups. + * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or + * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has + * become lost due to inactivity purge or some other reason and needs re-initializing) + */ + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Key/value dataset restore interfaces + + /** + * Get the set of all backups currently available over this transport. + * + * @return Descriptions of the set of restore images available for this device, + * or null if an error occurred (the attempt should be rescheduled). + **/ + public RestoreSet[] getAvailableRestoreSets() { + return null; + } + + /** + * Get the identifying token of the backup set currently being stored from + * this device. This is used in the case of applications wishing to restore + * their last-known-good data. + * + * @return A token that can be passed to {@link #startRestore}, or 0 if there + * is no backup set available corresponding to the current device state. + */ + public long getCurrentRestoreSet() { + return 0; + } + + /** + * Start restoring application data from backup. After calling this function, + * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData} + * to walk through the actual application data. + * + * @param token A backup token as returned by {@link #getAvailableRestoreSets} + * or {@link #getCurrentRestoreSet}. + * @param packages List of applications to restore (if data is available). + * Application data will be restored in the order given. + * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call + * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR} + * (an error occurred, the restore should be aborted and rescheduled). + */ + public int startRestore(long token, PackageInfo[] packages) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Get the package name of the next application with data in the backup store, plus + * a description of the structure of the restored archive: either TYPE_KEY_VALUE for + * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream. + * + * <p>If the package name in the returned RestoreDescription object is the singleton + * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available + * in the current restore session: all packages described in startRestore() have been + * processed. + * + * <p>If this method returns {@code null}, it means that a transport-level error has + * occurred and the entire restore operation should be abandoned. + * + * @return A RestoreDescription object containing the name of one of the packages + * supplied to {@link #startRestore} plus an indicator of the data type of that + * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that + * no more packages can be restored in this session; or {@code null} to indicate + * a transport-level error. + */ + public RestoreDescription nextRestorePackage() { + return null; + } + + /** + * Get the data for the application returned by {@link #nextRestorePackage}, if that + * method reported {@link RestoreDescription#TYPE_KEY_VALUE} as its delivery type. + * If the package has only TYPE_FULL_STREAM data, then this method will return an + * error. + * + * @param data An open, writable file into which the key/value backup data should be stored. + * @return the same error codes as {@link #startRestore}. + */ + public int getRestoreData(ParcelFileDescriptor outFd) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * End a restore session (aborting any in-process data transfer as necessary), + * freeing any resources and connections used during the restore process. + */ + public void finishRestore() { + throw new UnsupportedOperationException( + "Transport finishRestore() not implemented"); + } + + // ------------------------------------------------------------------------------------ + // Full backup interfaces + + /** + * Verify that this is a suitable time for a full-data backup pass. This should return zero + * if a backup is reasonable right now, some positive value otherwise. This method + * will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair. + * + * <p>If this is not a suitable time for a backup, the transport should return a + * backoff delay, in milliseconds, after which the Backup Manager should try again. + * + * @return Zero if this is a suitable time for a backup pass, or a positive time delay + * in milliseconds to suggest deferring the backup pass for a while. + * + * @see #requestBackupTime() + */ + public long requestFullBackupTime() { + return 0; + } + + /** + * Begin the process of sending an application's full-data archive to the backend. + * The description of the package whose data will be delivered is provided, as well as + * the socket file descriptor on which the transport will receive the data itself. + * + * <p>If the package is not eligible for backup, the transport should return + * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will + * simply proceed with the next candidate if any, or finish the full backup operation + * if all apps have been processed. + * + * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this + * method, the OS will proceed to call {@link #sendBackupData()} one or more times + * to deliver the application's data as a streamed tarball. The transport should not + * read() from the socket except as instructed to via the {@link #sendBackupData(int)} + * method. + * + * <p>After all data has been delivered to the transport, the system will call + * {@link #finishBackup()}. At this point the transport should commit the data to + * its datastore, if appropriate, and close the socket that had been provided in + * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}. + * + * @param targetPackage The package whose data is to follow. + * @param socket The socket file descriptor through which the data will be provided. + * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still + * close this file descriptor now; otherwise it should be cached for use during + * succeeding calls to {@link #sendBackupData(int)}, and closed in response to + * {@link #finishBackup()}. + * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not + * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering + * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes + * performing a backup at this time. + */ + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { + return BackupTransport.TRANSPORT_PACKAGE_REJECTED; + } + + /** + * Tells the transport to read {@code numBytes} bytes of data from the socket file + * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} + * call, and deliver those bytes to the datastore. + * + * @param numBytes The number of bytes of tarball data available to be read from the + * socket. + * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to + * indicate a fatal error situation. If an error is returned, the system will + * call finishBackup() and stop attempting backups until after a backoff and retry + * interval. + */ + public int sendBackupData(int numBytes) { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Full restore interfaces + + /** + * Ask the transport to provide data for the "current" package being restored. This + * is the package that was just reported by {@link #nextRestorePackage()} as having + * {@link RestoreDescription#TYPE_FULL_STREAM} data. + * + * The transport writes some data to the socket supplied to this call, and returns + * the number of bytes written. The system will then read that many bytes and + * stream them to the application's agent for restore, then will call this method again + * to receive the next chunk of the archive. This sequence will be repeated until the + * transport returns zero indicating that all of the package's data has been delivered + * (or returns a negative value indicating some sort of hard error condition at the + * transport level). + * + * <p>After this method returns zero, the system will then call + * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * application, and the sequence begins again. + * + * <p>The transport should always close this socket when returning from this method. + * Do not cache this socket across multiple calls or you may leak file descriptors. + * + * @param socket The file descriptor that the transport will use for delivering the + * streamed archive. The transport must close this socket in all cases when returning + * from this method. + * @return {@link #NO_MORE_DATA} when no more data for the current package is available. + * A positive value indicates the presence of that many bytes to be delivered to the app. + * A value of zero indicates that no data was deliverable at this time, but the restore + * is still running and the caller should retry. {@link #TRANSPORT_PACKAGE_REJECTED} + * means that the current package's restore operation should be aborted, but that + * the transport itself is still in a good state and so a multiple-package restore + * sequence can still be continued. Any other negative return value is treated as a + * fatal error condition that aborts all further restore operations on the current dataset. + */ + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { + return 0; + } + + /** + * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} + * data for restore, it will invoke this method to tell the transport that it should + * abandon the data download for the current package. The OS will then either call + * {@link #nextRestorePackage()} again to move on to restoring the next package in the + * set being iterated over, or will call {@link #finishRestore()} to shut down the restore + * operation. + * + * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the + * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious + * transport-level failure. If the transport reports an error here, the entire restore + * operation will immediately be finished with no further attempts to restore app data. + */ + public int abortFullRestore() { + return BackupTransport.TRANSPORT_OK; + } + + /** + * Bridge between the actual IBackupTransport implementation and the stable API. If the + * binder interface needs to change, we use this layer to translate so that we can + * (if appropriate) decouple those framework-side changes from the BackupTransport + * implementations. + */ + class TransportImpl extends IBackupTransport.Stub { + + @Override + public String name() throws RemoteException { + return BackupTransport.this.name(); + } + + @Override + public Intent configurationIntent() throws RemoteException { + return BackupTransport.this.configurationIntent(); + } + + @Override + public String currentDestinationString() throws RemoteException { + return BackupTransport.this.currentDestinationString(); + } + + @Override + public String transportDirName() throws RemoteException { + return BackupTransport.this.transportDirName(); + } + + @Override + public long requestBackupTime() throws RemoteException { + return BackupTransport.this.requestBackupTime(); + } + + @Override + public int initializeDevice() throws RemoteException { + return BackupTransport.this.initializeDevice(); + } + + @Override + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) + throws RemoteException { + return BackupTransport.this.performBackup(packageInfo, inFd); + } + + @Override + public int clearBackupData(PackageInfo packageInfo) throws RemoteException { + return BackupTransport.this.clearBackupData(packageInfo); + } + + @Override + public int finishBackup() throws RemoteException { + return BackupTransport.this.finishBackup(); + } + + @Override + public RestoreSet[] getAvailableRestoreSets() throws RemoteException { + return BackupTransport.this.getAvailableRestoreSets(); + } + + @Override + public long getCurrentRestoreSet() throws RemoteException { + return BackupTransport.this.getCurrentRestoreSet(); + } + + @Override + public int startRestore(long token, PackageInfo[] packages) throws RemoteException { + return BackupTransport.this.startRestore(token, packages); + } + + @Override + public RestoreDescription nextRestorePackage() throws RemoteException { + return BackupTransport.this.nextRestorePackage(); + } + + @Override + public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { + return BackupTransport.this.getRestoreData(outFd); + } + + @Override + public void finishRestore() throws RemoteException { + BackupTransport.this.finishRestore(); + } + + @Override + public long requestFullBackupTime() throws RemoteException { + return BackupTransport.this.requestFullBackupTime(); + } + + @Override + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException { + return BackupTransport.this.performFullBackup(targetPackage, socket); + } + + @Override + public int sendBackupData(int numBytes) throws RemoteException { + return BackupTransport.this.sendBackupData(numBytes); + } + + @Override + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { + return BackupTransport.this.getNextFullRestoreDataChunk(socket); + } + + @Override + public int abortFullRestore() { + return BackupTransport.this.abortFullRestore(); + } + } +} diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index c629a2e..72bc4f0 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -168,7 +168,15 @@ interface IBackupManager { */ void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem, - in String[] packageNames); + boolean doCompress, in String[] packageNames); + + /** + * Perform a full-dataset backup of the given applications via the currently active + * transport. + * + * @param packageNames The package names of the apps whose data are to be backed up. + */ + void fullTransportBackup(in String[] packageNames); /** * Restore device content from the data stream passed through the given socket. The diff --git a/core/java/android/app/backup/RestoreDescription.aidl b/core/java/android/app/backup/RestoreDescription.aidl new file mode 100644 index 0000000..9cbea78 --- /dev/null +++ b/core/java/android/app/backup/RestoreDescription.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.app.backup; + +parcelable RestoreDescription; diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java new file mode 100644 index 0000000..0fb4355 --- /dev/null +++ b/core/java/android/app/backup/RestoreDescription.java @@ -0,0 +1,101 @@ +/* + * 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.backup; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Description of the available restore data for a given package. Returned by a + * BackupTransport in response to a request about the next available restorable + * package. + * + * @see BackupTransport#nextRestorePackage() + * + * @hide + */ +public class RestoreDescription implements Parcelable { + private final String mPackageName; + private final int mDataType; + + private static final String NO_MORE_PACKAGES_SENTINEL = ""; + + /** + * Return this constant RestoreDescription from BackupTransport.nextRestorePackage() + * to indicate that no more package data is available in the current restore operation. + */ + public static final RestoreDescription NO_MORE_PACKAGES = + new RestoreDescription(NO_MORE_PACKAGES_SENTINEL, 0); + + // --------------------------------------- + // Data type identifiers + + /** This package's restore data is an original-style key/value dataset */ + public static final int TYPE_KEY_VALUE = 1; + + /** This package's restore data is a tarball-type full data stream */ + public static final int TYPE_FULL_STREAM = 2; + + // --------------------------------------- + // API + + public RestoreDescription(String packageName, int dataType) { + mPackageName = packageName; + mDataType = dataType; + } + + public String getPackageName() { + return mPackageName; + } + + public int getDataType() { + return mDataType; + } + + // --------------------------------------- + // Parcelable implementation - not used by transport + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mPackageName); + out.writeInt(mDataType); + } + + public static final Parcelable.Creator<RestoreDescription> CREATOR + = new Parcelable.Creator<RestoreDescription>() { + public RestoreDescription createFromParcel(Parcel in) { + final RestoreDescription unparceled = new RestoreDescription(in); + return (NO_MORE_PACKAGES_SENTINEL.equals(unparceled.mPackageName)) + ? NO_MORE_PACKAGES + : unparceled; + } + + public RestoreDescription[] newArray(int size) { + return new RestoreDescription[size]; + } + }; + + private RestoreDescription(Parcel in) { + mPackageName = in.readString(); + mDataType = in.readInt(); + } +} diff --git a/core/java/android/app/maintenance/IIdleCallback.aidl b/core/java/android/app/maintenance/IIdleCallback.aidl deleted file mode 100644 index 582dede..0000000 --- a/core/java/android/app/maintenance/IIdleCallback.aidl +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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 deleted file mode 100644 index 54abccd..0000000 --- a/core/java/android/app/maintenance/IIdleService.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 deleted file mode 100644 index 2331b81..0000000 --- a/core/java/android/app/maintenance/IdleService.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package 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/app/maintenance/package.html b/core/java/android/app/maintenance/package.html deleted file mode 100644 index 1c9bf9d..0000000 --- a/core/java/android/app/maintenance/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body> - {@hide} -</body> -</html> diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7b709ac..5175490 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -90,6 +90,11 @@ public final class BluetoothA2dp implements BluetoothProfile { public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = + "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; + /** * A2DP sink device is streaming music. This state can be one of * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java new file mode 100644 index 0000000..2e27345 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -0,0 +1,432 @@ +/* + * 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.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the public APIs to control the Bluetooth A2DP Sink + * profile. + * + *<p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothA2dpSink proxy object. + * + * @hide + */ +public final class BluetoothA2dpSink implements BluetoothProfile { + private static final String TAG = "BluetoothA2dpSink"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in connection state of the A2DP Sink + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; + + /** + * Intent used to broadcast the change in the Playing state of the A2DP Sink + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + public static final String ACTION_PLAYING_STATE_CHANGED = + "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED"; + + /** + * A2DP sink device is streaming music. This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + */ + public static final int STATE_PLAYING = 10; + + /** + * A2DP sink device is NOT streaming music. This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + */ + public static final int STATE_NOT_PLAYING = 11; + + /** + * Intent used to broadcast the change in the Playing state of the A2DP Sink + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + public static final String ACTION_AUDIO_CONFIG_CHANGED = + "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED"; + + /** + * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent. + * + * This extra represents the current audio configuration of the A2DP source device. + * {@see BluetoothAudioConfig} + */ + public static final String EXTRA_AUDIO_CONFIG + = "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG"; + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothA2dpSink mService; + private BluetoothAdapter mAdapter; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + doBind(); + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + /** + * Create a BluetoothA2dp proxy object for interacting with the local + * Bluetooth A2DP service. + * + */ + /*package*/ BluetoothA2dpSink(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothA2dpSink.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, + android.os.Process.myUserHandle())) { + Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent); + return false; + } + return true; + } + + /*package*/ void close() { + mServiceListener = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + + public void finalize() { + close(); + } + /** + * Initiate connection to a profile of the remote bluetooth device. + * + * <p> Currently, the system supports only 1 connection to the + * A2DP profile. The API will automatically disconnect connected + * devices before connecting. + * + * <p> This API returns false in scenarios like the profile on the + * device is already connected or Bluetooth is not turned on. + * When this API returns true, it is guaranteed that + * connection state intent for the profile will be broadcasted with + * the state. Users can get the connection state of the profile + * from this intent. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.connect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Initiate disconnection from a profile + * + * <p> This API will return false in scenarios like the profile on the + * Bluetooth device is not in connected state etc. When this API returns, + * true, it is guaranteed that the connection state change + * intent will be broadcasted with the state. Users can get the + * disconnection state of the profile from this intent. + * + * <p> If the disconnection is initiated by a remote device, the state + * will transition from {@link #STATE_CONNECTED} to + * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the + * host (local) device the state will transition from + * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to + * state {@link #STATE_DISCONNECTED}. The transition to + * {@link #STATE_DISCONNECTING} can be used to distinguish between the + * two scenarios. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * {@inheritDoc} + */ + public List<BluetoothDevice> getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * {@inheritDoc} + */ + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * {@inheritDoc} + */ + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getState(" + device + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Get the current audio configuration for the A2DP source device, + * or null if the device has no audio configuration + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param device Remote bluetooth device. + * @return audio configuration for the device, or null + * + * {@see BluetoothAudioConfig} + */ + public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { + if (VDBG) log("getAudioConfig(" + device + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.getAudioConfig(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } + + /** + * Helper for converting a state to a string. + * + * For debug use only - strings are not internationalized. + * @hide + */ + public static String stateToString(int state) { + switch (state) { + case STATE_DISCONNECTED: + return "disconnected"; + case STATE_CONNECTING: + return "connecting"; + case STATE_CONNECTED: + return "connected"; + case STATE_DISCONNECTING: + return "disconnecting"; + case STATE_PLAYING: + return "playing"; + case STATE_NOT_PLAYING: + return "not playing"; + default: + return "<unknown state " + state + ">"; + } + } + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothA2dpSink.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK, + BluetoothA2dpSink.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP_SINK); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 42c2aeb..ba42f51 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2009-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. @@ -1390,6 +1390,12 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.A2DP) { BluetoothA2dp a2dp = new BluetoothA2dp(context, listener); return true; + } else if (profile == BluetoothProfile.A2DP_SINK) { + BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener); + return true; + } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) { + BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener); + return true; } else if (profile == BluetoothProfile.INPUT_DEVICE) { BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener); return true; @@ -1402,6 +1408,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.MAP) { BluetoothMap map = new BluetoothMap(context, listener); return true; + } else if (profile == BluetoothProfile.HEADSET_CLIENT) { + BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener); + return true; } else { return false; } @@ -1430,6 +1439,14 @@ public final class BluetoothAdapter { BluetoothA2dp a2dp = (BluetoothA2dp)proxy; a2dp.close(); break; + case BluetoothProfile.A2DP_SINK: + BluetoothA2dpSink a2dpSink = (BluetoothA2dpSink)proxy; + a2dpSink.close(); + break; + case BluetoothProfile.AVRCP_CONTROLLER: + BluetoothAvrcpController avrcp = (BluetoothAvrcpController)proxy; + avrcp.close(); + break; case BluetoothProfile.INPUT_DEVICE: BluetoothInputDevice iDev = (BluetoothInputDevice)proxy; iDev.close(); @@ -1454,6 +1471,10 @@ public final class BluetoothAdapter { BluetoothMap map = (BluetoothMap)proxy; map.close(); break; + case BluetoothProfile.HEADSET_CLIENT: + BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient)proxy; + headsetClient.close(); + break; } } diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/android/bluetooth/BluetoothAudioConfig.aidl index 4c276b7..63be5cf 100644 --- a/core/java/com/android/internal/backup/BackupConstants.java +++ b/core/java/android/bluetooth/BluetoothAudioConfig.aidl @@ -14,15 +14,6 @@ * limitations under the License. */ -package com.android.internal.backup; +package android.bluetooth; -/** - * Constants used internally between the backup manager and its transports - */ -public class BackupConstants { - public static final int TRANSPORT_OK = 0; - public static final int TRANSPORT_ERROR = 1; - public static final int TRANSPORT_NOT_INITIALIZED = 2; - public static final int AGENT_ERROR = 3; - public static final int AGENT_UNKNOWN = 4; -} +parcelable BluetoothAudioConfig; diff --git a/core/java/android/bluetooth/BluetoothAudioConfig.java b/core/java/android/bluetooth/BluetoothAudioConfig.java new file mode 100644 index 0000000..03176b9 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothAudioConfig.java @@ -0,0 +1,111 @@ +/* + * 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 android.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents the audio configuration for a Bluetooth A2DP source device. + * + * {@see BluetoothA2dpSink} + * + * {@hide} + */ +public final class BluetoothAudioConfig implements Parcelable { + + private final int mSampleRate; + private final int mChannelConfig; + private final int mAudioFormat; + + public BluetoothAudioConfig(int sampleRate, int channelConfig, int audioFormat) { + mSampleRate = sampleRate; + mChannelConfig = channelConfig; + mAudioFormat = audioFormat; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothAudioConfig) { + BluetoothAudioConfig bac = (BluetoothAudioConfig)o; + return (bac.mSampleRate == mSampleRate && + bac.mChannelConfig == mChannelConfig && + bac.mAudioFormat == mAudioFormat); + } + return false; + } + + @Override + public int hashCode() { + return mSampleRate | (mChannelConfig << 24) | (mAudioFormat << 28); + } + + @Override + public String toString() { + return "{mSampleRate:" + mSampleRate + ",mChannelConfig:" + mChannelConfig + + ",mAudioFormat:" + mAudioFormat + "}"; + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<BluetoothAudioConfig> CREATOR = + new Parcelable.Creator<BluetoothAudioConfig>() { + public BluetoothAudioConfig createFromParcel(Parcel in) { + int sampleRate = in.readInt(); + int channelConfig = in.readInt(); + int audioFormat = in.readInt(); + return new BluetoothAudioConfig(sampleRate, channelConfig, audioFormat); + } + public BluetoothAudioConfig[] newArray(int size) { + return new BluetoothAudioConfig[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSampleRate); + out.writeInt(mChannelConfig); + out.writeInt(mAudioFormat); + } + + /** + * Returns the sample rate in samples per second + * @return sample rate + */ + public int getSampleRate() { + return mSampleRate; + } + + /** + * Returns the channel configuration (either {@link android.media.AudioFormat#CHANNEL_IN_MONO} + * or {@link android.media.AudioFormat#CHANNEL_IN_STEREO}) + * @return channel configuration + */ + public int getChannelConfig() { + return mChannelConfig; + } + + /** + * Returns the channel audio format (either {@link android.media.AudioFormat#ENCODING_PCM_16BIT} + * or {@link android.media.AudioFormat#ENCODING_PCM_8BIT} + * @return audio format + */ + public int getAudioFormat() { + return mAudioFormat; + } +} diff --git a/core/java/android/bluetooth/BluetoothAvrcp.java b/core/java/android/bluetooth/BluetoothAvrcp.java new file mode 100644 index 0000000..44fe1b7 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothAvrcp.java @@ -0,0 +1,93 @@ +/* + * 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.bluetooth; + +/** + * This class contains constants for Bluetooth AVRCP profile. + * + * {@hide} + */ +public final class BluetoothAvrcp { + + /* + * State flags for Passthrough commands + */ + public static final int PASSTHROUGH_STATE_PRESS = 0; + public static final int PASSTHROUGH_STATE_RELEASE = 1; + + /* + * Operation IDs for Passthrough commands + */ + public static final int PASSTHROUGH_ID_SELECT = 0x00; /* select */ + public static final int PASSTHROUGH_ID_UP = 0x01; /* up */ + public static final int PASSTHROUGH_ID_DOWN = 0x02; /* down */ + public static final int PASSTHROUGH_ID_LEFT = 0x03; /* left */ + public static final int PASSTHROUGH_ID_RIGHT = 0x04; /* right */ + public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05; /* right-up */ + public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06; /* right-down */ + public static final int PASSTHROUGH_ID_LEFT_UP = 0x07; /* left-up */ + public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08; /* left-down */ + public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09; /* root menu */ + public static final int PASSTHROUGH_ID_SETUP_MENU = 0x0A; /* setup menu */ + public static final int PASSTHROUGH_ID_CONT_MENU = 0x0B; /* contents menu */ + public static final int PASSTHROUGH_ID_FAV_MENU = 0x0C; /* favorite menu */ + public static final int PASSTHROUGH_ID_EXIT = 0x0D; /* exit */ + public static final int PASSTHROUGH_ID_0 = 0x20; /* 0 */ + public static final int PASSTHROUGH_ID_1 = 0x21; /* 1 */ + public static final int PASSTHROUGH_ID_2 = 0x22; /* 2 */ + public static final int PASSTHROUGH_ID_3 = 0x23; /* 3 */ + public static final int PASSTHROUGH_ID_4 = 0x24; /* 4 */ + public static final int PASSTHROUGH_ID_5 = 0x25; /* 5 */ + public static final int PASSTHROUGH_ID_6 = 0x26; /* 6 */ + public static final int PASSTHROUGH_ID_7 = 0x27; /* 7 */ + public static final int PASSTHROUGH_ID_8 = 0x28; /* 8 */ + public static final int PASSTHROUGH_ID_9 = 0x29; /* 9 */ + public static final int PASSTHROUGH_ID_DOT = 0x2A; /* dot */ + public static final int PASSTHROUGH_ID_ENTER = 0x2B; /* enter */ + public static final int PASSTHROUGH_ID_CLEAR = 0x2C; /* clear */ + public static final int PASSTHROUGH_ID_CHAN_UP = 0x30; /* channel up */ + public static final int PASSTHROUGH_ID_CHAN_DOWN = 0x31; /* channel down */ + public static final int PASSTHROUGH_ID_PREV_CHAN = 0x32; /* previous channel */ + public static final int PASSTHROUGH_ID_SOUND_SEL = 0x33; /* sound select */ + public static final int PASSTHROUGH_ID_INPUT_SEL = 0x34; /* input select */ + public static final int PASSTHROUGH_ID_DISP_INFO = 0x35; /* display information */ + public static final int PASSTHROUGH_ID_HELP = 0x36; /* help */ + public static final int PASSTHROUGH_ID_PAGE_UP = 0x37; /* page up */ + public static final int PASSTHROUGH_ID_PAGE_DOWN = 0x38; /* page down */ + public static final int PASSTHROUGH_ID_POWER = 0x40; /* power */ + public static final int PASSTHROUGH_ID_VOL_UP = 0x41; /* volume up */ + public static final int PASSTHROUGH_ID_VOL_DOWN = 0x42; /* volume down */ + public static final int PASSTHROUGH_ID_MUTE = 0x43; /* mute */ + public static final int PASSTHROUGH_ID_PLAY = 0x44; /* play */ + public static final int PASSTHROUGH_ID_STOP = 0x45; /* stop */ + public static final int PASSTHROUGH_ID_PAUSE = 0x46; /* pause */ + public static final int PASSTHROUGH_ID_RECORD = 0x47; /* record */ + public static final int PASSTHROUGH_ID_REWIND = 0x48; /* rewind */ + public static final int PASSTHROUGH_ID_FAST_FOR = 0x49; /* fast forward */ + public static final int PASSTHROUGH_ID_EJECT = 0x4A; /* eject */ + public static final int PASSTHROUGH_ID_FORWARD = 0x4B; /* forward */ + public static final int PASSTHROUGH_ID_BACKWARD = 0x4C; /* backward */ + public static final int PASSTHROUGH_ID_ANGLE = 0x50; /* angle */ + public static final int PASSTHROUGH_ID_SUBPICT = 0x51; /* subpicture */ + public static final int PASSTHROUGH_ID_F1 = 0x71; /* F1 */ + public static final int PASSTHROUGH_ID_F2 = 0x72; /* F2 */ + public static final int PASSTHROUGH_ID_F3 = 0x73; /* F3 */ + public static final int PASSTHROUGH_ID_F4 = 0x74; /* F4 */ + public static final int PASSTHROUGH_ID_F5 = 0x75; /* F5 */ + public static final int PASSTHROUGH_ID_VENDOR = 0x7E; /* vendor unique */ + public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80; +} diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java new file mode 100644 index 0000000..b53a8fc --- /dev/null +++ b/core/java/android/bluetooth/BluetoothAvrcpController.java @@ -0,0 +1,260 @@ +/* + * 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.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the public APIs to control the Bluetooth AVRCP Controller + * profile. + * + *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothAvrcpController proxy object. + * + * {@hide} + */ +public final class BluetoothAvrcpController implements BluetoothProfile { + private static final String TAG = "BluetoothAvrcpController"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in connection state of the AVRCP Controller + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.acrcp-controller.profile.action.CONNECTION_STATE_CHANGED"; + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothAvrcpController mService; + private BluetoothAdapter mAdapter; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + doBind(); + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothAvrcpController proxy object for interacting with the local + * Bluetooth AVRCP service. + * + */ + /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothAvrcpController.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, + android.os.Process.myUserHandle())) { + Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent); + return false; + } + return true; + } + + /*package*/ void close() { + mServiceListener = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + + public void finalize() { + close(); + } + + /** + * {@inheritDoc} + */ + public List<BluetoothDevice> getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * {@inheritDoc} + */ + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * {@inheritDoc} + */ + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getState(" + device + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { + if (DBG) Log.d(TAG, "sendPassThroughCmd"); + if (mService != null && isEnabled()) { + try { + mService.sendPassThroughCmd(device, keyCode, keyState); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in sendPassThroughCmd()", e); + return; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + } + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothAvrcpController.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER, + BluetoothAvrcpController.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 7f8d0ab..64d80a0 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -874,6 +874,26 @@ public final class BluetoothDevice implements Parcelable { } /** + * Returns whether there is an open connection to this device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return True if there is at least one open connection to this device. + * @hide + */ + public boolean isConnected() { + if (sService == null) { + // BT is not enabled, we cannot be connected. + return false; + } + try { + return sService.isConnected(this); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** * Get the Bluetooth class of the remote device. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java new file mode 100644 index 0000000..ff4ebee --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -0,0 +1,1167 @@ +/* + * 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.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Public API to control Hands Free Profile (HFP role only). + * <p> + * This class defines methods that shall be used by application to manage profile + * connection, calls states and calls actions. + * <p> + * + * @hide + * */ +public final class BluetoothHeadsetClient implements BluetoothProfile { + private static final String TAG = "BluetoothHeadsetClient"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent sent whenever connection to remote changes. + * + * <p>It includes two extras: + * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code> + * and <code>BluetoothProfile.EXTRA_STATE</code>, which + * are mandatory. + * <p>There are also non mandatory feature extras: + * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, + * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, + * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, + * {@link #EXTRA_AG_FEATURE_ECC}, + * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, + * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, + * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, + * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, + * {@link #EXTRA_AG_FEATURE_MERGE}, + * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, + * sent as boolean values only when <code>EXTRA_STATE</code> + * is set to <code>STATE_CONNECTED</code>.</p> + * + * <p>Note that features supported by AG are being sent as + * booleans with value <code>true</code>, + * and not supported ones are <strong>not</strong> being sent at all.</p> + */ + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; + + /** + * Intent sent whenever audio state changes. + * + * <p>It includes two mandatory extras: + * {@link BluetoothProfile.EXTRA_STATE}, + * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE}, + * with possible values: + * {@link #STATE_AUDIO_CONNECTING}, + * {@link #STATE_AUDIO_CONNECTED}, + * {@link #STATE_AUDIO_DISCONNECTED}</p> + * <p>When <code>EXTRA_STATE</code> is set + * to </code>STATE_AUDIO_CONNECTED</code>, + * it also includes {@link #EXTRA_AUDIO_WBS} + * indicating wide band speech support.</p> + */ + public static final String ACTION_AUDIO_STATE_CHANGED = + "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; + + /** + * Intent sending updates of the Audio Gateway state. + * Each extra is being sent only when value it + * represents has been changed recently on AG. + * <p>It can contain one or more of the following extras: + * {@link #EXTRA_NETWORK_STATUS}, + * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, + * {@link #EXTRA_NETWORK_ROAMING}, + * {@link #EXTRA_BATTERY_LEVEL}, + * {@link #EXTRA_OPERATOR_NAME}, + * {@link #EXTRA_VOICE_RECOGNITION}, + * {@link #EXTRA_IN_BAND_RING}</p> + */ + public static final String ACTION_AG_EVENT = + "android.bluetooth.headsetclient.profile.action.AG_EVENT"; + + /** + * Intent sent whenever state of a call changes. + * + * <p>It includes: + * {@link #EXTRA_CALL}, + * with value of {@link BluetoothHeadsetClientCall} instance, + * representing actual call state.</p> + */ + public static final String ACTION_CALL_CHANGED = + "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; + + /** + * Intent that notifies about the result of the last issued action. + * Please note that not every action results in explicit action result code being sent. + * Instead other notifications about new Audio Gateway state might be sent, + * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value + * when for example user started voice recognition from HF unit. + */ + public static final String ACTION_RESULT = + "android.bluetooth.headsetclient.profile.action.RESULT"; + + /** + * Intent that notifies about the number attached to the last voice tag + * recorded on AG. + * + * <p>It contains: + * {@link #EXTRA_NUMBER}, + * with a <code>String</code> value representing phone number.</p> + */ + public static final String ACTION_LAST_VTAG = + "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; + + public static final int STATE_AUDIO_DISCONNECTED = 0; + public static final int STATE_AUDIO_CONNECTING = 1; + public static final int STATE_AUDIO_CONNECTED = 2; + + /** + * Extra with information if connected audio is WBS. + * <p>Possible values: <code>true</code>, + * <code>false</code>.</p> + */ + public static final String EXTRA_AUDIO_WBS = + "android.bluetooth.headsetclient.extra.AUDIO_WBS"; + + /** + * Extra for AG_EVENT indicates network status. + * <p>Value: 0 - network unavailable, + * 1 - network available </p> + */ + public static final String EXTRA_NETWORK_STATUS = + "android.bluetooth.headsetclient.extra.NETWORK_STATUS"; + /** + * Extra for AG_EVENT intent indicates network signal strength. + * <p>Value: <code>Integer</code> representing signal strength.</p> + */ + public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = + "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH"; + /** + * Extra for AG_EVENT intent indicates roaming state. + * <p>Value: 0 - no roaming + * 1 - active roaming</p> + */ + public static final String EXTRA_NETWORK_ROAMING = + "android.bluetooth.headsetclient.extra.NETWORK_ROAMING"; + /** + * Extra for AG_EVENT intent indicates the battery level. + * <p>Value: <code>Integer</code> representing signal strength.</p> + */ + public static final String EXTRA_BATTERY_LEVEL = + "android.bluetooth.headsetclient.extra.BATTERY_LEVEL"; + /** + * Extra for AG_EVENT intent indicates operator name. + * <p>Value: <code>String</code> representing operator name.</p> + */ + public static final String EXTRA_OPERATOR_NAME = + "android.bluetooth.headsetclient.extra.OPERATOR_NAME"; + /** + * Extra for AG_EVENT intent indicates voice recognition state. + * <p>Value: + * 0 - voice recognition stopped, + * 1 - voice recognition started.</p> + */ + public static final String EXTRA_VOICE_RECOGNITION = + "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION"; + /** + * Extra for AG_EVENT intent indicates in band ring state. + * <p>Value: + * 0 - in band ring tone not supported, or + * 1 - in band ring tone supported.</p> + */ + public static final String EXTRA_IN_BAND_RING = + "android.bluetooth.headsetclient.extra.IN_BAND_RING"; + + /** + * Extra for AG_EVENT intent indicates subscriber info. + * <p>Value: <code>String</code> containing subscriber information.</p> + */ + public static final String EXTRA_SUBSCRIBER_INFO = + "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO"; + + /** + * Extra for AG_CALL_CHANGED intent indicates the + * {@link BluetoothHeadsetClientCall} object that has changed. + */ + public static final String EXTRA_CALL = + "android.bluetooth.headsetclient.extra.CALL"; + + /** + * Extra for ACTION_LAST_VTAG intent. + * <p>Value: <code>String</code> representing phone number + * corresponding to last voice tag recorded on AG</p> + */ + public static final String EXTRA_NUMBER = + "android.bluetooth.headsetclient.extra.NUMBER"; + + /** + * Extra for ACTION_RESULT intent that shows the result code of + * last issued action. + * <p>Possible results: + * {@link #ACTION_RESULT_OK}, + * {@link #ACTION_RESULT_ERROR}, + * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, + * {@link #ACTION_RESULT_ERROR_BUSY}, + * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, + * {@link #ACTION_RESULT_ERROR_DELAYED}, + * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, + * {@link #ACTION_RESULT_ERROR_CME}</p> + */ + public static final String EXTRA_RESULT_CODE = + "android.bluetooth.headsetclient.extra.RESULT_CODE"; + + /** + * Extra for ACTION_RESULT intent that shows the extended result code of + * last issued action. + * <p>Value: <code>Integer</code> - error code.</p> + */ + public static final String EXTRA_CME_CODE = + "android.bluetooth.headsetclient.extra.CME_CODE"; + + /* Extras for AG_FEATURES, extras type is boolean */ + // TODO verify if all of those are actually useful + /** + * AG feature: three way calling. + */ + public final static String EXTRA_AG_FEATURE_3WAY_CALLING = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; + /** + * AG feature: voice recognition. + */ + public final static String EXTRA_AG_FEATURE_VOICE_RECOGNITION = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; + /** + * AG feature: fetching phone number for voice tagging procedure. + */ + public final static String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; + /** + * AG feature: ability to reject incoming call. + */ + public final static String EXTRA_AG_FEATURE_REJECT_CALL = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; + /** + * AG feature: enhanced call handling (terminate specific call, private consultation). + */ + public final static String EXTRA_AG_FEATURE_ECC = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; + /** + * AG feature: response and hold. + */ + public final static String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; + /** + * AG call handling feature: accept held or waiting call in three way calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; + /** + * AG call handling feature: release held or waiting call in three way calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; + /** + * AG call handling feature: release active call and accept held or waiting call in three way + * calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; + /** + * AG call handling feature: merge two calls, held and active - multi party conference mode. + */ + public final static String EXTRA_AG_FEATURE_MERGE = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE"; + /** + * AG call handling feature: merge calls and disconnect from multi party + * conversation leaving peers connected to each other. + * Note that this feature needs to be supported by mobile network operator + * as it requires connection and billing transfer. + */ + public final static String EXTRA_AG_FEATURE_MERGE_AND_DETACH = + "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; + + /* Action result codes */ + public final static int ACTION_RESULT_OK = 0; + public final static int ACTION_RESULT_ERROR = 1; + public final static int ACTION_RESULT_ERROR_NO_CARRIER = 2; + public final static int ACTION_RESULT_ERROR_BUSY = 3; + public final static int ACTION_RESULT_ERROR_NO_ANSWER = 4; + public final static int ACTION_RESULT_ERROR_DELAYED = 5; + public final static int ACTION_RESULT_ERROR_BLACKLISTED = 6; + public final static int ACTION_RESULT_ERROR_CME = 7; + + /* Detailed CME error codes */ + public final static int CME_PHONE_FAILURE = 0; + public final static int CME_NO_CONNECTION_TO_PHONE = 1; + public final static int CME_OPERATION_NOT_ALLOWED = 3; + public final static int CME_OPERATION_NOT_SUPPORTED = 4; + public final static int CME_PHSIM_PIN_REQUIRED = 5; + public final static int CME_PHFSIM_PIN_REQUIRED = 6; + public final static int CME_PHFSIM_PUK_REQUIRED = 7; + public final static int CME_SIM_NOT_INSERTED = 10; + public final static int CME_SIM_PIN_REQUIRED = 11; + public final static int CME_SIM_PUK_REQUIRED = 12; + public final static int CME_SIM_FAILURE = 13; + public final static int CME_SIM_BUSY = 14; + public final static int CME_SIM_WRONG = 15; + public final static int CME_INCORRECT_PASSWORD = 16; + public final static int CME_SIM_PIN2_REQUIRED = 17; + public final static int CME_SIM_PUK2_REQUIRED = 18; + public final static int CME_MEMORY_FULL = 20; + public final static int CME_INVALID_INDEX = 21; + public final static int CME_NOT_FOUND = 22; + public final static int CME_MEMORY_FAILURE = 23; + public final static int CME_TEXT_STRING_TOO_LONG = 24; + public final static int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; + public final static int CME_DIAL_STRING_TOO_LONG = 26; + public final static int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; + public final static int CME_NO_NETWORK_SERVICE = 30; + public final static int CME_NETWORK_TIMEOUT = 31; + public final static int CME_EMERGENCY_SERVICE_ONLY = 32; + public final static int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; + public final static int CME_NOT_SUPPORTED_FOR_VOIP = 34; + public final static int CME_SIP_RESPONSE_CODE = 35; + public final static int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; + public final static int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; + public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; + public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; + public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; + public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; + public final static int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; + public final static int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; + public final static int CME_HIDDEN_KEY_REQUIRED = 48; + public final static int CME_EAP_NOT_SUPPORTED = 49; + public final static int CME_INCORRECT_PARAMETERS = 50; + + /* Action policy for other calls when accepting call */ + public static final int CALL_ACCEPT_NONE = 0; + public static final int CALL_ACCEPT_HOLD = 1; + public static final int CALL_ACCEPT_TERMINATE = 2; + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothHeadsetClient mService; + private BluetoothAdapter mAdapter; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + @Override + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + Intent intent = new Intent(IBluetoothHeadsetClient.class.getName()); + doBind(); + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothHeadsetClient proxy object. + */ + /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothHeadsetClient.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, + android.os.Process.myUserHandle())) { + Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent); + return false; + } + return true; + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothHeadsetClient will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + /*package*/ void close() { + if (VDBG) log("close()"); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + mServiceListener = null; + } + + /** + * Connects to remote device. + * + * Currently, the system supports only 1 connection. So, in case of the + * second connection, this implementation will disconnect already connected + * device automatically and will process the new one. + * + * @param device a remote device we want connect to + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} + * intent. + */ + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.connect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Disconnects remote device + * + * @param device a remote device we want disconnect + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} + * intent. + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Return the list of connected remote devices + * + * @return list of connected devices; empty list if nothing is connected. + */ + @Override + public List<BluetoothDevice> getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Returns list of remote devices in a particular state + * + * @param states collection of states + * @return list of devices that state matches the states listed in + * <code>states</code>; empty list if nothing matches the + * <code>states</code> + */ + @Override + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Returns state of the <code>device</code> + * + * @param device a remote device + * @return the state of connection of the device + */ + @Override + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Set priority of the profile + * + * The device should already be paired. + */ + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } + try { + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the priority of the profile. + */ + public int getPriority(BluetoothDevice device) { + if (VDBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return PRIORITY_OFF; + } + + /** + * Starts voice recognition. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_AG_EVENT} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean startVoiceRecognition(BluetoothDevice device) { + if (DBG) log("startVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.startVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Stops voice recognition. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_AG_EVENT} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean stopVoiceRecognition(BluetoothDevice device) { + if (DBG) log("stopVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.stopVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Returns list of all calls in any state. + * + * @param device remote device + * @return list of calls; empty list if none call exists + */ + public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { + if (DBG) log("getCurrentCalls()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getCurrentCalls(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } + + /** + * Returns list of current values of AG indicators. + * + * @param device remote device + * @return bundle of AG indicators; null if device is not in + * CONNECTED state + */ + public Bundle getCurrentAgEvents(BluetoothDevice device) { + if (DBG) log("getCurrentCalls()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getCurrentAgEvents(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } + + /** + * Accepts a call + * + * @param device remote device + * @param flag action policy while accepting a call. Possible values + * {@link #CALL_ACCEPT_NONE}, {@link #CALL_ACCEPT_HOLD}, + * {@link #CALL_ACCEPT_TERMINATE} + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + */ + public boolean acceptCall(BluetoothDevice device, int flag) { + if (DBG) log("acceptCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.acceptCall(device, flag); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Holds a call. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + */ + public boolean holdCall(BluetoothDevice device) { + if (DBG) log("holdCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.holdCall(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Rejects a call. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_REJECT_CALL}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean rejectCall(BluetoothDevice device) { + if (DBG) log("rejectCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.rejectCall(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Terminates a specified call. + * + * Works only when Extended Call Control is supported by Audio Gateway. + * + * @param device remote device + * @param index index of the call to be terminated + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ECC}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean terminateCall(BluetoothDevice device, int index) { + if (DBG) log("terminateCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.terminateCall(device, index); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Enters private mode with a specified call. + * + * Works only when Extended Call Control is supported by Audio Gateway. + * + * @param device remote device + * @param index index of the call to connect in private mode + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ECC}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean enterPrivateMode(BluetoothDevice device, int index) { + if (DBG) log("enterPrivateMode()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.enterPrivateMode(device, index); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Performs explicit call transfer. + * + * That means connect other calls and disconnect. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean explicitCallTransfer(BluetoothDevice device) { + if (DBG) log("explicitCallTransfer()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.explicitCallTransfer(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Redials last number from Audio Gateway. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean redial(BluetoothDevice device) { + if (DBG) log("redial()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.redial(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Places a call with specified number. + * + * @param device remote device + * @param number valid phone number + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean dial(BluetoothDevice device, String number) { + if (DBG) log("dial()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.dial(device, number); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Places a call to the number under specified memory location. + * + * @param device remote device + * @param location valid memory location + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean dialMemory(BluetoothDevice device, int location) { + if (DBG) log("dialMemory()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.dialMemory(device, location); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Sends DTMF code. + * + * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# + * + * @param device remote device + * @param code ASCII code + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_RESULT} intent; + */ + public boolean sendDTMF(BluetoothDevice device, byte code) { + if (DBG) log("sendDTMF()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.sendDTMF(device, code); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get a number corresponding to last voice tag recorded on AG. + * + * @param device remote device + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_LAST_VTAG} + * or {@link #ACTION_RESULT} intent; + * + * <p>Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. + * This method invocation will fail silently when feature is not supported.</p> + */ + public boolean getLastVoiceTagNumber(BluetoothDevice device) { + if (DBG) log("getLastVoiceTagNumber()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getLastVoiceTagNumber(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Accept the incoming connection. + */ + public boolean acceptIncomingConnect(BluetoothDevice device) { + if (DBG) log("acceptIncomingConnect"); + if (mService != null && isEnabled()) { + try { + return mService.acceptIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Reject the incoming connection. + */ + public boolean rejectIncomingConnect(BluetoothDevice device) { + if (DBG) log("rejectIncomingConnect"); + if (mService != null) { + try { + return mService.rejectIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Returns current audio state of Audio Gateway. + * + * Note: This is an internal function and shouldn't be exposed + */ + public int getAudioState(BluetoothDevice device) { + if (VDBG) log("getAudioState"); + if (mService != null && isEnabled()) { + try { + return mService.getAudioState(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; + } + + /** + * Initiates a connection of audio channel. + * + * It setup SCO channel with remote connected Handsfree AG device. + * + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} + * intent; + */ + public boolean connectAudio() { + if (mService != null && isEnabled()) { + try { + return mService.connectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnects audio channel. + * + * It tears down the SCO channel from remote AG device. + * + * @return <code>true</code> if command has been issued successfully; + * <code>false</code> otherwise; + * upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} + * intent; + */ + public boolean disconnectAudio() { + if (mService != null && isEnabled()) { + try { + return mService.disconnectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Get Audio Gateway features + * + * @param device remote device + * @return bundle of AG features; null if no service or + * AG not connected + */ + public Bundle getCurrentAgFeatures(BluetoothDevice device) { + if (mService != null && isEnabled()) { + try { + return mService.getCurrentAgFeatures(device); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return null; + } + + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothHeadsetClient.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT, + BluetoothHeadsetClient.this); + } + } + @Override + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl b/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl new file mode 100644 index 0000000..35f7923 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl @@ -0,0 +1,18 @@ +/* + * 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.bluetooth; + +parcelable BluetoothHeadsetClientCall; diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java new file mode 100644 index 0000000..a15bd97 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java @@ -0,0 +1,218 @@ +/* + * 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.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class represents a single call, its state and properties. + * It implements {@link Parcelable} for inter-process message passing. + * @hide + */ +public final class BluetoothHeadsetClientCall implements Parcelable { + + /* Call state */ + /** + * Call is active. + */ + public static final int CALL_STATE_ACTIVE = 0; + /** + * Call is in held state. + */ + public static final int CALL_STATE_HELD = 1; + /** + * Outgoing call that is being dialed right now. + */ + public static final int CALL_STATE_DIALING = 2; + /** + * Outgoing call that remote party has already been alerted about. + */ + public static final int CALL_STATE_ALERTING = 3; + /** + * Incoming call that can be accepted or rejected. + */ + public static final int CALL_STATE_INCOMING = 4; + /** + * Waiting call state when there is already an active call. + */ + public static final int CALL_STATE_WAITING = 5; + /** + * Call that has been held by response and hold + * (see Bluetooth specification for further references). + */ + public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6; + /** + * Call that has been already terminated and should not be referenced as a valid call. + */ + public static final int CALL_STATE_TERMINATED = 7; + + private final int mId; + private int mState; + private String mNumber; + private boolean mMultiParty; + private final boolean mOutgoing; + + /** + * Creates BluetoothHeadsetClientCall instance. + */ + public BluetoothHeadsetClientCall(int id, int state, String number, boolean multiParty, + boolean outgoing) { + mId = id; + mState = state; + mNumber = number != null ? number : ""; + mMultiParty = multiParty; + mOutgoing = outgoing; + } + + /** + * Sets call's state. + * + * <p>Note: This is an internal function and shouldn't be exposed</p> + * + * @param state new call state. + */ + public void setState(int state) { + mState = state; + } + + /** + * Sets call's number. + * + * <p>Note: This is an internal function and shouldn't be exposed</p> + * + * @param number String representing phone number. + */ + public void setNumber(String number) { + mNumber = number; + } + + /** + * Sets this call as multi party call. + * + * <p>Note: This is an internal function and shouldn't be exposed</p> + * + * @param multiParty if <code>true</code> sets this call as a part + * of multi party conference. + */ + public void setMultiParty(boolean multiParty) { + mMultiParty = multiParty; + } + + /** + * Gets call's Id. + * + * @return call id. + */ + public int getId() { + return mId; + } + + /** + * Gets call's current state. + * + * @return state of this particular phone call. + */ + public int getState() { + return mState; + } + + /** + * Gets call's number. + * + * @return string representing phone number. + */ + public String getNumber() { + return mNumber; + } + + /** + * Checks if call is an active call in a conference mode (aka multi party). + * + * @return <code>true</code> if call is a multi party call, + * <code>false</code> otherwise. + */ + public boolean isMultiParty() { + return mMultiParty; + } + + /** + * Checks if this call is an outgoing call. + * + * @return <code>true</code> if its outgoing call, + * <code>false</code> otherwise. + */ + public boolean isOutgoing() { + return mOutgoing; + } + + public String toString() { + StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mId: "); + builder.append(mId); + builder.append(", mState: "); + switch (mState) { + case CALL_STATE_ACTIVE: builder.append("ACTIVE"); break; + case CALL_STATE_HELD: builder.append("HELD"); break; + case CALL_STATE_DIALING: builder.append("DIALING"); break; + case CALL_STATE_ALERTING: builder.append("ALERTING"); break; + case CALL_STATE_INCOMING: builder.append("INCOMING"); break; + case CALL_STATE_WAITING: builder.append("WAITING"); break; + case CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: builder.append("HELD_BY_RESPONSE_AND_HOLD"); break; + case CALL_STATE_TERMINATED: builder.append("TERMINATED"); break; + default: builder.append(mState); break; + } + builder.append(", mNumber: "); + builder.append(mNumber); + builder.append(", mMultiParty: "); + builder.append(mMultiParty); + builder.append(", mOutgoing: "); + builder.append(mOutgoing); + builder.append("}"); + return builder.toString(); + } + + /** + * {@link Parcelable.Creator} interface implementation. + */ + public static final Parcelable.Creator<BluetoothHeadsetClientCall> CREATOR = + new Parcelable.Creator<BluetoothHeadsetClientCall>() { + @Override + public BluetoothHeadsetClientCall createFromParcel(Parcel in) { + return new BluetoothHeadsetClientCall(in.readInt(), in.readInt(), + in.readString(), in.readInt() == 1, in.readInt() == 1); + } + + @Override + public BluetoothHeadsetClientCall[] newArray(int size) { + return new BluetoothHeadsetClientCall[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mId); + out.writeInt(mState); + out.writeString(mNumber); + out.writeInt(mMultiParty ? 1 : 0); + out.writeInt(mOutgoing ? 1 : 0); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 1574090..1367405 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2010-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. @@ -104,6 +104,24 @@ public interface BluetoothProfile { public static final int MAP = 9; /** + * A2DP Sink Profile + * @hide + */ + public static final int A2DP_SINK = 10; + + /** + * AVRCP Controller Profile + * @hide + */ + public static final int AVRCP_CONTROLLER = 11; + + /** + * Headset Client - HFP HF Role + * @hide + */ + public static final int HEADSET_CLIENT = 16; + + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile * @hide diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 07db8cc..a45c6b8 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -58,6 +58,7 @@ interface IBluetooth boolean cancelBondProcess(in BluetoothDevice device); boolean removeBond(in BluetoothDevice device); int getBondState(in BluetoothDevice device); + boolean isConnected(in BluetoothDevice device); String getRemoteName(in BluetoothDevice device); int getRemoteType(in BluetoothDevice device); diff --git a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl new file mode 100644 index 0000000..b7c6476 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl @@ -0,0 +1,34 @@ +/* + * 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.bluetooth; + +import android.bluetooth.BluetoothAudioConfig; +import android.bluetooth.BluetoothDevice; + +/** + * APIs for Bluetooth A2DP sink service + * + * @hide + */ +interface IBluetoothA2dpSink { + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + BluetoothAudioConfig getAudioConfig(in BluetoothDevice device); +} diff --git a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl new file mode 100644 index 0000000..f917a50 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl @@ -0,0 +1,31 @@ +/* + * 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.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * APIs for Bluetooth AVRCP controller service + * + * @hide + */ +interface IBluetoothAvrcpController { + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + void sendPassThroughCmd(in BluetoothDevice device, int keyCode, int keyState); +} diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 00a0750..273d76d 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -35,8 +35,6 @@ interface IBluetoothGatt { void startScan(in int appIf, in boolean isServer); void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids); - void startScanWithUuidsScanParam(in int appIf, in boolean isServer, - in ParcelUuid[] ids, int scanWindow, int scanInterval); void startScanWithFilters(in int appIf, in boolean isServer, in ScanSettings settings, in List<ScanFilter> filters); void stopScan(in int appIf, in boolean isServer); diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl index bf9e0a7..2d8eed4 100644 --- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl @@ -22,7 +22,7 @@ import android.os.ParcelUuid; * Callback definitions for interacting with BLE / GATT * @hide */ -interface IBluetoothGattCallback { +oneway interface IBluetoothGattCallback { void onClientRegistered(in int status, in int clientIf); void onClientConnectionState(in int status, in int clientIf, in boolean connected, in String address); @@ -63,7 +63,7 @@ interface IBluetoothGattCallback { in int charInstId, in ParcelUuid charUuid, in byte[] value); void onReadRemoteRssi(in String address, in int rssi, in int status); - oneway void onAdvertiseStateChange(in int advertiseState, in int status); - oneway void onMultiAdvertiseCallback(in int status); + void onAdvertiseStateChange(in int advertiseState, in int status); + void onMultiAdvertiseCallback(in int status); void onConfigureMTU(in String address, in int mtu, in int status); } diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl new file mode 100644 index 0000000..e518b7d --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl @@ -0,0 +1,67 @@ +/* + * 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.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadsetClientCall; +import android.os.Bundle; + +/** + * API for Bluetooth Headset Client service (HFP HF Role) + * + * {@hide} + */ +interface IBluetoothHeadsetClient { + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + + boolean acceptIncomingConnect(in BluetoothDevice device); + boolean rejectIncomingConnect(in BluetoothDevice device); + + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); + + boolean startVoiceRecognition(in BluetoothDevice device); + boolean stopVoiceRecognition(in BluetoothDevice device); + + List<BluetoothHeadsetClientCall> getCurrentCalls(in BluetoothDevice device); + Bundle getCurrentAgEvents(in BluetoothDevice device); + + boolean acceptCall(in BluetoothDevice device, int flag); + boolean holdCall(in BluetoothDevice device); + boolean rejectCall(in BluetoothDevice device); + boolean terminateCall(in BluetoothDevice device, int index); + + boolean enterPrivateMode(in BluetoothDevice device, int index); + boolean explicitCallTransfer(in BluetoothDevice device); + + boolean redial(in BluetoothDevice device); + boolean dial(in BluetoothDevice device, String number); + boolean dialMemory(in BluetoothDevice device, int location); + + boolean sendDTMF(in BluetoothDevice device, byte code); + boolean getLastVoiceTagNumber(in BluetoothDevice device); + + int getAudioState(in BluetoothDevice device); + boolean connectAudio(); + boolean disconnectAudio(); + + Bundle getCurrentAgFeatures(in BluetoothDevice device); +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cdcfd2e..fd19b40 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2035,6 +2035,7 @@ public abstract class Context { AUDIO_SERVICE, MEDIA_ROUTER_SERVICE, TELEPHONY_SERVICE, + TELECOMM_SERVICE, CLIPBOARD_SERVICE, INPUT_METHOD_SERVICE, TEXT_SERVICES_MANAGER_SERVICE, @@ -2163,6 +2164,8 @@ public abstract class Context { * @see android.media.MediaRouter * @see #TELEPHONY_SERVICE * @see android.telephony.TelephonyManager + * @see #TELECOMM_SERVICE + * @see android.telecomm.TelecommManager * @see #INPUT_METHOD_SERVICE * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE @@ -2460,7 +2463,6 @@ public abstract class Context { * * @see #getSystemService * @see android.app.FingerprintManager - * @hide */ public static final String FINGERPRINT_SERVICE = "fingerprint"; @@ -2495,6 +2497,16 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.telecomm.TelecommManager} to manage telecomm-related features + * of the device. + * + * @see #getSystemService + * @see android.telecomm.TelecommManager + */ + public static final String TELECOMM_SERVICE = "telecomm"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.text.ClipboardManager} for accessing and modifying * the contents of the global clipboard. * @@ -2686,6 +2698,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.content.RestrictionsManager} for retrieving application restrictions + * and requesting permissions for restricted operations. + * @see #getSystemService + * @see android.content.RestrictionsManager + */ + public static final String RESTRICTIONS_SERVICE = "restrictions"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.app.AppOpsManager} for tracking application operations * on the device. * diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl new file mode 100644 index 0000000..b1c0a3a --- /dev/null +++ b/core/java/android/content/IRestrictionsManager.aidl @@ -0,0 +1,30 @@ +/* + * 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.content; + +import android.os.Bundle; + +/** + * Interface used by the RestrictionsManager + * @hide + */ +interface IRestrictionsManager { + Bundle getApplicationRestrictions(in String packageName); + boolean hasRestrictionsProvider(); + void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData); + void notifyPermissionResponse(in String packageName, in Bundle response); +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6e53a6f..e24dc84 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -55,6 +55,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; /** @@ -6821,72 +6822,12 @@ public class Intent implements Parcelable, Cloneable { if (other == null) { return false; } - if (mAction != other.mAction) { - if (mAction != null) { - if (!mAction.equals(other.mAction)) { - return false; - } - } else { - if (!other.mAction.equals(mAction)) { - return false; - } - } - } - if (mData != other.mData) { - if (mData != null) { - if (!mData.equals(other.mData)) { - return false; - } - } else { - if (!other.mData.equals(mData)) { - return false; - } - } - } - if (mType != other.mType) { - if (mType != null) { - if (!mType.equals(other.mType)) { - return false; - } - } else { - if (!other.mType.equals(mType)) { - return false; - } - } - } - if (mPackage != other.mPackage) { - if (mPackage != null) { - if (!mPackage.equals(other.mPackage)) { - return false; - } - } else { - if (!other.mPackage.equals(mPackage)) { - return false; - } - } - } - if (mComponent != other.mComponent) { - if (mComponent != null) { - if (!mComponent.equals(other.mComponent)) { - return false; - } - } else { - if (!other.mComponent.equals(mComponent)) { - return false; - } - } - } - if (mCategories != other.mCategories) { - if (mCategories != null) { - if (!mCategories.equals(other.mCategories)) { - return false; - } - } else { - if (!other.mCategories.equals(mCategories)) { - return false; - } - } - } + if (!Objects.equals(this.mAction, other.mAction)) return false; + if (!Objects.equals(this.mData, other.mData)) return false; + if (!Objects.equals(this.mType, other.mType)) return false; + if (!Objects.equals(this.mPackage, other.mPackage)) return false; + if (!Objects.equals(this.mComponent, other.mComponent)) return false; + if (!Objects.equals(this.mCategories, other.mCategories)) return false; return true; } @@ -7375,6 +7316,7 @@ public class Intent implements Parcelable, Cloneable { for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) { out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx)); } + out.endTag(null, TAG_CATEGORIES); } } diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 3ff53bf..62f88a9 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable { */ public static final int TYPE_MULTI_SELECT = 4; + /** + * A type of restriction. Use this for storing an integer value. The range of values + * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. + */ + public static final int TYPE_INTEGER = 5; + /** The type of restriction. */ - private int type; + private int mType; /** The unique key that identifies the restriction. */ - private String key; + private String mKey; /** The user-visible title of the restriction. */ - private String title; + private String mTitle; /** The user-visible secondary description of the restriction. */ - private String description; + private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] choices; + private String [] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] values; + private String [] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ - private String currentValue; + private String mCurrentValue; /* List of selected choices in the multi-select case. */ - private String[] currentValues; + private String[] mCurrentValues; /** * Constructor for {@link #TYPE_CHOICE} type. @@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the current value */ public RestrictionEntry(String key, String selectedString) { - this.key = key; - this.type = TYPE_CHOICE; - this.currentValue = selectedString; + this.mKey = key; + this.mType = TYPE_CHOICE; + this.mCurrentValue = selectedString; } /** @@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable { * @param selectedState whether this restriction is selected or not */ public RestrictionEntry(String key, boolean selectedState) { - this.key = key; - this.type = TYPE_BOOLEAN; + this.mKey = key; + this.mType = TYPE_BOOLEAN; setSelectedState(selectedState); } @@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable { * @param selectedStrings the list of values that are currently selected */ public RestrictionEntry(String key, String[] selectedStrings) { - this.key = key; - this.type = TYPE_MULTI_SELECT; - this.currentValues = selectedStrings; + this.mKey = key; + this.mType = TYPE_MULTI_SELECT; + this.mCurrentValues = selectedStrings; + } + + /** + * Constructor for {@link #TYPE_INTEGER} type. + * @param key the unique key for this restriction + * @param selectedInt the integer value of the restriction + */ + public RestrictionEntry(String key, int selectedInt) { + mKey = key; + mType = TYPE_INTEGER; + setIntValue(selectedInt); } /** @@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable { * @param type the type for this restriction. */ public void setType(int type) { - this.type = type; + this.mType = type; } /** @@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable { * @return the type for this restriction */ public int getType() { - return type; + return mType; } /** @@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable { * single string values. */ public String getSelectedString() { - return currentValue; + return mCurrentValue; } /** @@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable { * null otherwise. */ public String[] getAllSelectedStrings() { - return currentValues; + return mCurrentValues; } /** @@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable { * @return the current selected state of the entry. */ public boolean getSelectedState() { - return Boolean.parseBoolean(currentValue); + return Boolean.parseBoolean(mCurrentValue); + } + + /** + * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}. + * @return the integer value of the entry. + */ + public int getIntValue() { + return Integer.parseInt(mCurrentValue); + } + + /** + * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}. + * @param value the integer value to set. + */ + public void setIntValue(int value) { + mCurrentValue = Integer.toString(value); } /** @@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the string value to select. */ public void setSelectedString(String selectedString) { - currentValue = selectedString; + mCurrentValue = selectedString; } /** @@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable { * @param state the current selected state */ public void setSelectedState(boolean state) { - currentValue = Boolean.toString(state); + mCurrentValue = Boolean.toString(state); } /** @@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable { * @param allSelectedStrings the current list of selected values. */ public void setAllSelectedStrings(String[] allSelectedStrings) { - currentValues = allSelectedStrings; + mCurrentValues = allSelectedStrings; } /** @@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable { * @see #getAllSelectedStrings() */ public void setChoiceValues(String[] choiceValues) { - values = choiceValues; + mChoiceValues = choiceValues; } /** @@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceValues(Context context, int stringArrayResId) { - values = context.getResources().getStringArray(stringArrayResId); + mChoiceValues = context.getResources().getStringArray(stringArrayResId); } /** @@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of possible values. */ public String[] getChoiceValues() { - return values; + return mChoiceValues; } /** @@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceEntries(String[] choiceEntries) { - choices = choiceEntries; + mChoiceEntries = choiceEntries; } /** Sets a list of strings that will be presented as choices to the user. This is similar to @@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable { * @param stringArrayResId the resource id of a string array containing the possible entries. */ public void setChoiceEntries(Context context, int stringArrayResId) { - choices = context.getResources().getStringArray(stringArrayResId); + mChoiceEntries = context.getResources().getStringArray(stringArrayResId); } /** @@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of choices presented to the user. */ public String[] getChoiceEntries() { - return choices; + return mChoiceEntries; } /** @@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible description, null if none was set earlier. */ public String getDescription() { - return description; + return mDescription; } /** @@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable { * @param description the user-visible description string. */ public void setDescription(String description) { - this.description = description; + this.mDescription = description; } /** @@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable { * @return the key for the restriction. */ public String getKey() { - return key; + return mKey; } /** @@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible title for the entry, null if none was set earlier. */ public String getTitle() { - return title; + return mTitle; } /** @@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable { * @param title the user-visible title for the entry. */ public void setTitle(String title) { - this.title = title; + this.mTitle = title; } private boolean equalArrays(String[] one, String[] other) { @@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable { if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; // Make sure that either currentValue matches or currentValues matches. - return type == other.type && key.equals(other.key) + return mType == other.mType && mKey.equals(other.mKey) && - ((currentValues == null && other.currentValues == null - && currentValue != null && currentValue.equals(other.currentValue)) + ((mCurrentValues == null && other.mCurrentValues == null + && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) || - (currentValue == null && other.currentValue == null - && currentValues != null && equalArrays(currentValues, other.currentValues))); + (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); } @Override public int hashCode() { int result = 17; - result = 31 * result + key.hashCode(); - if (currentValue != null) { - result = 31 * result + currentValue.hashCode(); - } else if (currentValues != null) { - for (String value : currentValues) { + result = 31 * result + mKey.hashCode(); + if (mCurrentValue != null) { + result = 31 * result + mCurrentValue.hashCode(); + } else if (mCurrentValues != null) { + for (String value : mCurrentValues) { if (value != null) { result = 31 * result + value.hashCode(); } @@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable { } public RestrictionEntry(Parcel in) { - type = in.readInt(); - key = in.readString(); - title = in.readString(); - description = in.readString(); - choices = readArray(in); - values = readArray(in); - currentValue = in.readString(); - currentValues = readArray(in); + mType = in.readInt(); + mKey = in.readString(); + mTitle = in.readString(); + mDescription = in.readString(); + mChoiceEntries = readArray(in); + mChoiceValues = readArray(in); + mCurrentValue = in.readString(); + mCurrentValues = readArray(in); } @Override @@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(type); - dest.writeString(key); - dest.writeString(title); - dest.writeString(description); - writeArray(dest, choices); - writeArray(dest, values); - dest.writeString(currentValue); - writeArray(dest, currentValues); + dest.writeInt(mType); + dest.writeString(mKey); + dest.writeString(mTitle); + dest.writeString(mDescription); + writeArray(dest, mChoiceEntries); + writeArray(dest, mChoiceValues); + dest.writeString(mCurrentValue); + writeArray(dest, mCurrentValues); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}"; + return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java new file mode 100644 index 0000000..0dd0edd --- /dev/null +++ b/core/java/android/content/RestrictionsManager.java @@ -0,0 +1,344 @@ +/* + * 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.content; + +import android.app.admin.DevicePolicyManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for apps to query restrictions imposed by an entity that + * manages the user. Apps can also send permission requests to a local or remote + * device administrator to override default app-specific restrictions or any other + * operation that needs explicit authorization from the administrator. + * <p> + * Apps can expose a set of restrictions via a runtime receiver mechanism or via + * static meta data in the manifest. + * <p> + * If the user has an active restrictions provider, dynamic requests can be made in + * addition to the statically imposed restrictions. Dynamic requests are app-specific + * and can be expressed via a predefined set of templates. + * <p> + * The RestrictionsManager forwards the dynamic requests to the active + * restrictions provider. The restrictions provider can respond back to requests by calling + * {@link #notifyPermissionResponse(String, Bundle)}, when + * a response is received from the administrator of the device or user + * The response is relayed back to the application via a protected broadcast, + * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. + * <p> + * Static restrictions are specified by an XML file referenced by a meta-data attribute + * in the manifest. This enables applications as well as any web administration consoles + * to be able to read the template from the apk. + * <p> + * The syntax of the XML format is as follows: + * <pre> + * <restrictions> + * <restriction + * android:key="<key>" + * android:restrictionType="boolean|string|integer|multi-select|null" + * ... /> + * <restriction ... /> + * </restrictions> + * </pre> + * <p> + * The attributes for each restriction depend on the restriction type. + * + * @see RestrictionEntry + */ +public class RestrictionsManager { + + /** + * Broadcast intent delivered when a response is received for a permission + * request. The response is not available for later query, so the receiver + * must persist and/or immediately act upon the response. The application + * should not interrupt the user by coming to the foreground if it isn't + * currently in the foreground. It can post a notification instead, informing + * the user of a change in state. + * <p> + * For instance, if the user requested permission to make an in-app purchase, + * the app can post a notification that the request had been granted or denied, + * and allow the purchase to go through. + * <p> + * The broadcast Intent carries the following extra: + * {@link #EXTRA_RESPONSE_BUNDLE}. + */ + public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = + "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + + /** + * Protected broadcast intent sent to the active restrictions provider. The intent + * contains the following extras:<p> + * <ul> + * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting + * permission.</li> + * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li> + * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values + * for the request. + * </ul> + * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) + * @see #requestPermission(String, String, Bundle) + */ + public static final String ACTION_REQUEST_PERMISSION = + "android.intent.action.REQUEST_PERMISSION"; + + /** + * The package name of the application making the request. + */ + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + /** + * The template id that specifies what kind of a request it is and may indicate + * how the request is to be presented to the administrator. Must be either one of + * the predefined templates or a custom one specified by the application that the + * restrictions provider is familiar with. + */ + public static final String EXTRA_TEMPLATE_ID = "template_id"; + + /** + * A bundle containing the details about the request. The contents depend on the + * template id. + * @see #EXTRA_TEMPLATE_ID + */ + public static final String EXTRA_REQUEST_BUNDLE = "request_bundle"; + + /** + * Contains a response from the administrator for specific request. + * The bundle contains the following information, at least: + * <ul> + * <li>{@link #REQUEST_KEY_ID}: The request id.</li> + * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li> + * </ul> + * <p> + * And depending on what the request template was, the bundle will contain the actual + * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in + * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator + * approved the request, false otherwise. + */ + public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + + + /** + * Request template that presents a simple question, with a possible title and icon. + * <p> + * Required keys are + * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. + * <p> + * Optional keys are + * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, + * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. + */ + public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + + /** + * Key for request ID contained in the request bundle. + * <p> + * App-generated request id to identify the specific request when receiving + * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_ID = "android.req_template.req_id"; + + /** + * Key for request data contained in the request bundle. + * <p> + * Optional, typically used to identify the specific data that is being referred to, + * such as the unique identifier for a movie or book. This is not used for display + * purposes and is more like a cookie. This value is returned in the + * {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DATA = "android.req_template.data"; + + /** + * Key for request title contained in the request bundle. + * <p> + * Optional, typically used as the title of any notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_TITLE = "android.req_template.title"; + + /** + * Key for request message contained in the request bundle. + * <p> + * Required, shown as the actual message in a notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + + /** + * Key for request icon contained in the request bundle. + * <p> + * Optional, shown alongside the request message presented to the administrator + * who approves the request. + * <p> + * Type: Bitmap + */ + public static final String REQUEST_KEY_ICON = "android.req_template.icon"; + + /** + * Key for request approval button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the positive button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + + /** + * Key for request rejection button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the negative button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + + /** + * Key for requestor's name contained in the request bundle. This value is not specified by + * the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + + /** + * Key for requestor's device name contained in the request bundle. This value is not specified + * by the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + + /** + * Key for the response in the response bundle sent to the application, for a permission + * request. + * <p> + * Type: boolean + */ + public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + + private static final String TAG = "RestrictionsManager"; + + private final Context mContext; + private final IRestrictionsManager mService; + + /** + * @hide + */ + public RestrictionsManager(Context context, IRestrictionsManager service) { + mContext = context; + mService = service; + } + + /** + * Returns any available set of application-specific restrictions applicable + * to this application. + * @return + */ + public Bundle getApplicationRestrictions() { + try { + if (mService != null) { + return mService.getApplicationRestrictions(mContext.getPackageName()); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return null; + } + + /** + * Called by an application to check if permission requests can be made. If false, + * there is no need to request permission for an operation, unless a static + * restriction applies to that operation. + * @return + */ + public boolean hasRestrictionsProvider() { + try { + if (mService != null) { + return mService.hasRestrictionsProvider(); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return false; + } + + /** + * Called by an application to request permission for an operation. The contents of the + * request are passed in a Bundle that contains several pieces of data depending on the + * chosen request template. + * + * @param requestTemplate The request template to use. The template could be one of the + * predefined templates specified in this class or a custom template that the specific + * Restrictions Provider might understand. For custom templates, the template name should be + * namespaced to avoid collisions with predefined templates and templates specified by + * other Restrictions Provider vendors. + * @param requestData A Bundle containing the data corresponding to the specified request + * template. The keys for the data in the bundle depend on the kind of template chosen. + */ + public void requestPermission(String requestTemplate, Bundle requestData) { + try { + if (mService != null) { + mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Called by the Restrictions Provider when a response is available to be + * delivered to an application. + * @param packageName + * @param response + */ + public void notifyPermissionResponse(String packageName, Bundle response) { + try { + if (mService != null) { + mService.notifyPermissionResponse(packageName, response); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Parse and return the list of restrictions defined in the manifest for the specified + * package, if any. + * @param packageName The application for which to fetch the restrictions list. + * @return The list of RestrictionEntry objects created from the XML file specified + * in the manifest, or null if none was specified. + */ + public List<RestrictionEntry> getManifestRestrictions(String packageName) { + // TODO: + return null; + } +} diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index 6b4404d..46c9234 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -355,7 +355,14 @@ public interface SharedPreferences { /** * Registers a callback to be invoked when a change happens to a preference. - * + * + * <p class="caution"><strong>Caution:</strong> The preference manager does + * not currently store a strong reference to the listener. You must store a + * strong reference to the listener, or it will be susceptible to garbage + * collection. We recommend you keep a reference to the listener in the + * instance data of an object that will exist as long as you need the + * listener.</p> + * * @param listener The callback that will run. * @see #unregisterOnSharedPreferenceChangeListener */ diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 791e5aa..bcf1e87 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -104,6 +104,28 @@ public class ActivityInfo extends ComponentInfo public int documentLaunchMode; /** + * Constant corresponding to <code>persistRootOnly</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ROOT_ONLY = 0; + /** + * Constant corresponding to <code>doNotPersist</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int DO_NOT_PERSIST = 1; + /** + * Constant corresponding to <code>persistAcrossReboots</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ACROSS_REBOOTS = 2; + /** + * Value indicating how this activity is to be persisted across + * reboots for restoring in the Recents list. + * {@link android.R.attr#persistableMode} + */ + public int persistableMode; + + /** * The maximum number of tasks rooted at this activity that can be in the recent task list. * Refer to {@link android.R.attr#maxRecents}. */ @@ -230,11 +252,12 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_IMMERSIVE = 0x0800; /** - * Bit in {@link #flags} indicating that this activity is to be persisted across - * reboots for display in the Recents list. - * {@link android.R.attr#persistable} + * Bit in {@link #flags}: If set, a task rooted at this activity will have its + * baseIntent replaced by the activity immediately above this. Each activity may further + * relinquish its identity to the activity above it using this flag. Set from the + * android.R.attr#relinquishTaskIdentity attribute. */ - public static final int FLAG_PERSISTABLE = 0x1000; + public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000; /** * Bit in {@link #flags} indicating that tasks started with this activity are to be * removed from the recent list of tasks when the last activity in the task is finished. @@ -641,13 +664,23 @@ public class ActivityInfo extends ComponentInfo return theme != 0 ? theme : applicationInfo.theme; } + private String persistableModeToString() { + switch(persistableMode) { + case PERSIST_ROOT_ONLY: return "PERSIST_ROOT_ONLY"; + case DO_NOT_PERSIST: return "DO_NOT_PERSIST"; + case PERSIST_ACROSS_REBOOTS: return "PERSIST_ACROSS_REBOOTS"; + default: return "UNKNOWN=" + persistableMode; + } + } + public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); if (permission != null) { pw.println(prefix + "permission=" + permission); } pw.println(prefix + "taskAffinity=" + taskAffinity - + " targetActivity=" + targetActivity); + + " targetActivity=" + targetActivity + + " persistableMode=" + persistableModeToString()); if (launchMode != 0 || flags != 0 || theme != 0) { pw.println(prefix + "launchMode=" + launchMode + " flags=0x" + Integer.toHexString(flags) @@ -688,6 +721,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(softInputMode); dest.writeInt(uiOptions); dest.writeString(parentActivityName); + dest.writeInt(persistableMode); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -713,5 +747,6 @@ public class ActivityInfo extends ComponentInfo softInputMode = source.readInt(); uiOptions = source.readInt(); parentActivityName = source.readString(); + persistableMode = source.readInt(); } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 6b44a11..be4e864 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -23,8 +23,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; +import com.android.internal.util.ArrayUtils; + import java.text.Collator; +import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; /** * Information you can retrieve about a particular application. This @@ -321,6 +325,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_IS_GAME = 1<<25; /** + * Value for {@link #flags}: {@code true} if the application asks that only + * full-data streaming backups of its data be performed even though it defines + * a {@link android.app.backup.BackupAgent BackupAgent}, which normally + * indicates that the app will manage its backed-up data via incremental + * key/value updates. + */ + public static final int FLAG_FULL_BACKUP_ONLY = 1<<26; + + /** * Value for {@link #flags}: set to {@code true} if the application * is permitted to hold privileged permissions. * @@ -398,17 +411,30 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int largestWidthLimitDp = 0; /** - * Full path to the location of this package. + * Full path to the base APK for this application. */ public String sourceDir; /** - * Full path to the location of the publicly available parts of this - * package (i.e. the primary resource package and manifest). For - * non-forward-locked apps this will be the same as {@link #sourceDir). + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. */ public String publicSourceDir; - + + /** + * Full paths to zero or more split APKs that, when combined with the base + * APK defined in {@link #sourceDir}, form a complete application. + */ + public String[] splitSourceDirs; + + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + */ + public String[] splitPublicSourceDirs; + /** * Full paths to the locations of extra resource packages this application * uses. This field is only used if there are extra resource packages, @@ -512,13 +538,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { + " compatibleWidthLimitDp=" + compatibleWidthLimitDp + " largestWidthLimitDp=" + largestWidthLimitDp); pw.println(prefix + "sourceDir=" + sourceDir); - if (sourceDir == null) { - if (publicSourceDir != null) { - pw.println(prefix + "publicSourceDir=" + publicSourceDir); - } - } else if (!sourceDir.equals(publicSourceDir)) { + if (!Objects.equals(sourceDir, publicSourceDir)) { pw.println(prefix + "publicSourceDir=" + publicSourceDir); } + if (!ArrayUtils.isEmpty(splitSourceDirs)) { + pw.println(prefix + "splitSourceDirs=" + Arrays.toString(splitSourceDirs)); + } + if (!ArrayUtils.isEmpty(splitPublicSourceDirs) + && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) { + pw.println(prefix + "splitPublicSourceDirs=" + Arrays.toString(splitPublicSourceDirs)); + } if (resourceDirs != null) { pw.println(prefix + "resourceDirs=" + resourceDirs); } @@ -591,6 +620,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { largestWidthLimitDp = orig.largestWidthLimitDp; sourceDir = orig.sourceDir; publicSourceDir = orig.publicSourceDir; + splitSourceDirs = orig.splitSourceDirs; + splitPublicSourceDirs = orig.splitPublicSourceDirs; nativeLibraryDir = orig.nativeLibraryDir; cpuAbi = orig.cpuAbi; resourceDirs = orig.resourceDirs; @@ -633,6 +664,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(largestWidthLimitDp); dest.writeString(sourceDir); dest.writeString(publicSourceDir); + dest.writeStringArray(splitSourceDirs); + dest.writeStringArray(splitPublicSourceDirs); dest.writeString(nativeLibraryDir); dest.writeString(cpuAbi); dest.writeStringArray(resourceDirs); @@ -674,6 +707,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { largestWidthLimitDp = source.readInt(); sourceDir = source.readString(); publicSourceDir = source.readString(); + splitSourceDirs = source.readStringArray(); + splitPublicSourceDirs = source.readStringArray(); nativeLibraryDir = source.readString(); cpuAbi = source.readString(); resourceDirs = source.readStringArray(); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 6cb781f..6111b01 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -112,7 +112,7 @@ interface IPackageManager { ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); - boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest); + boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags, int userId); @@ -248,10 +248,13 @@ interface IPackageManager { void clearPackagePersistentPreferredActivities(String packageName, int userId); - void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig, - int userIdDest); + void addCrossProfileIntentFilter(in IntentFilter intentFilter, int sourceUserId, int targetUserId, + int flags); - void clearForwardingIntentFilters(int userIdOrig); + void addCrossProfileIntentsForPackage(in String packageName, int sourceUserId, + int targetUserId); + + void clearCrossProfileIntentFilters(int sourceUserId); /** * Report the set of 'Home' activity candidates, plus (if any) which of them @@ -433,6 +436,13 @@ interface IPackageManager { in VerificationParams verificationParams, in ContainerEncryptionParams encryptionParams); + void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI, + in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2, + int flags, in String installerPackageName, + in VerificationParams verificationParams, + in ContainerEncryptionParams encryptionParams, + in String packageAbiOverride); + int installExistingPackageAsUser(String packageName, int userId); void verifyPendingInstall(int id, int verificationCode); diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java index a977e41..dab0caf 100644 --- a/core/java/android/content/pm/InstrumentationInfo.java +++ b/core/java/android/content/pm/InstrumentationInfo.java @@ -30,17 +30,32 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { * "package" attribute. */ public String targetPackage; - + /** - * Full path to the location of this package. + * Full path to the base APK for this application. */ public String sourceDir; - + /** - * Full path to the location of the publicly available parts of this package (i.e. the resources - * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir). + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. */ public String publicSourceDir; + + /** + * Full paths to zero or more split APKs that, when combined with the base + * APK defined in {@link #sourceDir}, form a complete application. + */ + public String[] splitSourceDirs; + + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + */ + public String[] splitPublicSourceDirs; + /** * Full path to a directory assigned to the package for its persistent * data. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 69fa408..6c10bb8 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -57,6 +57,65 @@ public class LauncherApps { private List<OnAppsChangedListener> mListeners = new ArrayList<OnAppsChangedListener>(); + private List<OnAppsChangedCallback> mCallbacks + = new ArrayList<OnAppsChangedCallback>(); + + /** + * Callbacks for package changes to this and related managed profiles. + */ + public static abstract class OnAppsChangedCallback { + /** + * Indicates that a package was removed from the specified profile. + * + * @param packageName The name of the package that was removed. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageRemoved(String packageName, UserHandle user); + + /** + * Indicates that a package was added to the specified profile. + * + * @param packageName The name of the package that was added. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageAdded(String packageName, UserHandle user); + + /** + * Indicates that a package was modified in the specified profile. + * + * @param packageName The name of the package that has changed. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageChanged(String packageName, UserHandle user); + + /** + * Indicates that one or more packages have become available. For + * example, this can happen when a removable storage card has + * reappeared. + * + * @param packageNames The names of the packages that have become + * available. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether these packages are replacing + * existing ones. + */ + abstract public void onPackagesAvailable(String[] packageNames, UserHandle user, + boolean replacing); + + /** + * Indicates that one or more packages have become unavailable. For + * example, this can happen when a removable storage card has been + * removed. + * + * @param packageNames The names of the packages that have become + * unavailable. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether the packages are about to be + * replaced with new versions. + */ + abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user, + boolean replacing); + } /** * Callbacks for package changes to this and related managed profiles. @@ -270,7 +329,7 @@ public class LauncherApps { synchronized (this) { if (listener != null && !mListeners.contains(listener)) { mListeners.add(listener); - if (mListeners.size() == 1) { + if (mListeners.size() == 1 && mCallbacks.size() == 0) { try { mService.addOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -289,7 +348,44 @@ public class LauncherApps { public void removeOnAppsChangedListener(OnAppsChangedListener listener) { synchronized (this) { mListeners.remove(listener); - if (mListeners.size() == 0) { + if (mListeners.size() == 0 && mCallbacks.size() == 0) { + try { + mService.removeOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } + } + } + } + + /** + * Adds a callback for changes to packages in current and managed profiles. + * + * @param callback The callback to add. + */ + public void addOnAppsChangedCallback(OnAppsChangedCallback callback) { + synchronized (this) { + if (callback != null && !mCallbacks.contains(callback)) { + mCallbacks.add(callback); + if (mCallbacks.size() == 1 && mListeners.size() == 0) { + try { + mService.addOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } + } + } + } + } + + /** + * Removes a callback that was previously added. + * + * @param callback The callback to remove. + * @see #addOnAppsChangedListener(OnAppsChangedCallback) + */ + public void removeOnAppsChangedCallback(OnAppsChangedCallback callback) { + synchronized (this) { + mListeners.remove(callback); + if (mListeners.size() == 0 && mCallbacks.size() == 0) { try { mService.removeOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -309,6 +405,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageRemoved(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageRemoved(packageName, user); + } } } @@ -321,6 +420,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageChanged(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageChanged(packageName, user); + } } } @@ -333,6 +435,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageAdded(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageAdded(packageName, user); + } } } @@ -346,6 +451,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackagesAvailable(user, packageNames, replacing); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackagesAvailable(packageNames, user, replacing); + } } } @@ -359,7 +467,10 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackagesUnavailable(user, packageNames, replacing); } - } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackagesUnavailable(packageNames, user, replacing); + } + } } }; } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d7bd473..4672015 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -19,9 +19,12 @@ package android.content.pm; import android.app.PackageInstallObserver; import android.app.PackageUninstallObserver; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.FileBridge; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import java.io.OutputStream; + /** {@hide} */ public class PackageInstaller { private final PackageManager mPm; @@ -127,10 +130,17 @@ public class PackageInstaller { } } - public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes, - long lengthBytes) { + /** + * Open an APK file for writing, starting at the given offset. You can + * then stream data into the file, periodically calling + * {@link OutputStream#flush()} to ensure bytes have been written to + * disk. + */ + public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) { try { - return mSession.openWrite(overlayName, offsetBytes, lengthBytes); + final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName, + offsetBytes, lengthBytes); + return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0ba7180..d11698c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -26,8 +26,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.PackageParser.PackageParserException; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Environment; @@ -195,6 +197,22 @@ public abstract class PackageManager { */ public static final int MATCH_DEFAULT_ONLY = 0x00010000; + /** + * Flag for {@link addCrossProfileIntentFilter}: if the cross-profile intent has been set by the + * profile owner. + * @hide + */ + public static final int SET_BY_PROFILE_OWNER= 0x00000001; + + /** + * Flag for {@link addCrossProfileIntentFilter}: if this flag is set: + * when resolving an intent that matches the {@link CrossProfileIntentFilter}, the current + * profile will be skipped. + * Only activities in the target user can respond to the intent. + * @hide + */ + public static final int SKIP_CURRENT_PROFILE = 0x00000002; + /** @hide */ @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED}) @Retention(RetentionPolicy.SOURCE) @@ -520,7 +538,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the package being installed contains native code, but none that is - * compatible with the the device's CPU_ABI. + * compatible with the device's CPU_ABI. * @hide */ @SystemApi @@ -1601,7 +1619,7 @@ public abstract class PackageManager { * <p> * Throws {@link NameNotFoundException} if a package with the given name * cannot be found on the system. - * + * * @param packageName The name of the package to inspect. * @return Returns either a fully-qualified Intent that can be used to launch * the main Leanback activity in the package, or null if the package @@ -1615,7 +1633,7 @@ public abstract class PackageManager { * <p> * Throws {@link NameNotFoundException} if a package with the given name * cannot be found on the system. - * + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @return Returns an int array of the assigned gids, or null if there are @@ -2869,20 +2887,19 @@ public abstract class PackageManager { * */ public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) { - PackageParser packageParser = new PackageParser(archiveFilePath); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - final File sourceFile = new File(archiveFilePath); - PackageParser.Package pkg = packageParser.parsePackage( - sourceFile, archiveFilePath, metrics, 0); - if (pkg == null) { + final PackageParser parser = new PackageParser(); + final File apkFile = new File(archiveFilePath); + try { + PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); + if ((flags & GET_SIGNATURES) != 0) { + parser.collectCertificates(pkg, 0); + parser.collectManifestDigest(pkg); + } + PackageUserState state = new PackageUserState(); + return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); + } catch (PackageParserException e) { return null; } - if ((flags & GET_SIGNATURES) != 0) { - packageParser.collectCertificates(pkg, 0); - } - PackageUserState state = new PackageUserState(); - return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); } /** @@ -3454,7 +3471,7 @@ public abstract class PackageManager { /** - * Return the the enabled setting for a package component (activity, + * Return the enabled setting for a package component (activity, * receiver, service, provider). This returns the last value set by * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since @@ -3492,14 +3509,14 @@ public abstract class PackageManager { int newState, int flags); /** - * Return the the enabled setting for an application. This returns + * Return the enabled setting for an application. This returns * the last value set by * {@link #setApplicationEnabledSetting(String, int, int)}; in most * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since * the value originally specified in the manifest has not been modified. * - * @param packageName The component to retrieve. - * @return Returns the current enabled state for the component. May + * @param packageName The package name of the application to retrieve. + * @return Returns the current enabled state for the application. May * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED}, * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or * {@link #COMPONENT_ENABLED_STATE_DEFAULT}. The last one means the @@ -3576,24 +3593,34 @@ public abstract class PackageManager { } /** - * Adds a forwarding intent filter. After calling this method all intents sent from the user - * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if - * they match the specified intent filter. - * @param filter the {@link IntentFilter} the intent has to match to be forwarded - * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent - * filter - * @param userIdOrig user from which the intent can be forwarded - * @param userIdDest user to which the intent can be forwarded + * Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the + * user with id sourceUserId can also be be resolved by activities in the user with id + * targetUserId if they match the specified intent filter. + * @param filter the {@link IntentFilter} the intent has to match + * @param removable if set to false, {@link clearCrossProfileIntentFilters} will not remove this + * {@link CrossProfileIntentFilter} + * @hide + */ + public abstract void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, + int targetUserId, int flags); + + /** + * Clearing {@link CrossProfileIntentFilter}s which have the specified user as their + * source, and have been set by the profile owner + * @param sourceUserId * @hide */ - public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable, - int userIdOrig, int userIdDest); + public abstract void clearCrossProfileIntentFilters(int sourceUserId); /** - * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as - * the origin. - * @param userIdOrig user from which the intent can be forwarded + * Forwards all intents for {@link packageName} for user {@link sourceUserId} to user + * {@link targetUserId}. + * @hide + */ + public abstract void addCrossProfileIntentsForPackage(String packageName, + int sourceUserId, int targetUserId); + /** * @hide */ - public abstract void clearForwardingIntentFilters(int userIdOrig); + public abstract Bitmap getUserIcon(int userId); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 8965faa..b40a441 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -16,8 +16,14 @@ package android.content.pm; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import android.content.ComponentName; import android.content.Intent; @@ -31,6 +37,9 @@ import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; @@ -39,12 +48,18 @@ import android.util.Pair; import android.util.Slog; import android.util.TypedValue; -import java.io.BufferedInputStream; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.XmlUtils; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -56,21 +71,19 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.jar.StrictJarFile; import java.util.zip.ZipEntry; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - /** * Package archive parsing * @@ -81,6 +94,8 @@ public class PackageParser { private static final boolean DEBUG_PARSER = false; private static final boolean DEBUG_BACKUP = false; + // TODO: switch outError users to PackageParserException + /** File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; @@ -149,17 +164,21 @@ public class PackageParser { android.os.Build.VERSION_CODES.JELLY_BEAN) }; + /** + * @deprecated callers should move to explicitly passing around source path. + */ + @Deprecated private String mArchiveSourcePath; + private String[] mSeparateProcesses; private boolean mOnlyCoreApps; + private DisplayMetrics mMetrics; + private static final int SDK_VERSION = Build.VERSION.SDK_INT; private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; private int mParseError = PackageManager.INSTALL_SUCCEEDED; - private static final Object mSync = new Object(); - private static WeakReference<byte[]> mReadBuffer; - private static boolean sCompatibilityModeEnabled = true; private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; @@ -207,10 +226,10 @@ public class PackageParser { } } - /* Light weight package info. - * @hide + /** + * Lightweight parsed details about a single APK file. */ - public static class PackageLite { + public static class ApkLite { public final String packageName; public final String splitName; public final int versionCode; @@ -218,7 +237,7 @@ public class PackageParser { public final VerifierInfo[] verifiers; public final Signature[] signatures; - public PackageLite(String packageName, String splitName, int versionCode, + public ApkLite(String packageName, String splitName, int versionCode, int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) { this.packageName = packageName; this.splitName = splitName; @@ -243,8 +262,9 @@ public class PackageParser { private static final String TAG = "PackageParser"; - public PackageParser(String archiveSourcePath) { - mArchiveSourcePath = archiveSourcePath; + public PackageParser() { + mMetrics = new DisplayMetrics(); + mMetrics.setToDefaults(); } public void setSeparateProcesses(String[] procs) { @@ -255,6 +275,14 @@ public class PackageParser { mOnlyCoreApps = onlyCoreApps; } + public void setDisplayMetrics(DisplayMetrics metrics) { + mMetrics = metrics; + } + + private static final boolean isPackageFilename(File file) { + return isPackageFilename(file.getName()); + } + private static final boolean isPackageFilename(String name) { return name.endsWith(".apk"); } @@ -468,23 +496,21 @@ public class PackageParser { return pi; } - private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je, - byte[] readBuffer) { + private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) + throws PackageParserException { + InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. - InputStream is = new BufferedInputStream(jarFile.getInputStream(je)); - while (is.read(readBuffer, 0, readBuffer.length) != -1) { - // not using - } - is.close(); - return je != null ? jarFile.getCertificateChains(je) : null; - } catch (IOException e) { - Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e); - } catch (RuntimeException e) { - Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e); + is = jarFile.getInputStream(entry); + readFullyIgnoringContents(is); + return jarFile.getCertificateChains(entry); + } catch (IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed reading " + entry.getName() + " in " + jarFile, e); + } finally { + IoUtils.closeQuietly(is); } - return null; } public final static int PARSE_IS_SYSTEM = 1<<0; @@ -496,39 +522,148 @@ public class PackageParser { public final static int PARSE_IS_SYSTEM_DIR = 1<<6; public final static int PARSE_IS_PRIVILEGED = 1<<7; public final static int PARSE_GET_SIGNATURES = 1<<8; + public final static int PARSE_TRUSTED_OVERLAY = 1<<9; + + private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); - public int getParseError() { - return mParseError; + /** + * Used to sort a set of APKs based on their split names, always placing the + * base APK (with {@code null} split name) first. + */ + private static class SplitNameComparator implements Comparator<String> { + @Override + public int compare(String lhs, String rhs) { + if (lhs == null) { + return -1; + } else if (rhs == null) { + return 1; + } else { + return lhs.compareTo(rhs); + } + } } - public Package parsePackage(File sourceFile, String destCodePath, - DisplayMetrics metrics, int flags) { - return parsePackage(sourceFile, destCodePath, metrics, flags, false); + /** + * Parse all APKs contained in the given directory, treating them as a + * single package. This also performs sanity checking, such as requiring + * identical package name and version codes, a single base APK, and unique + * split names. + * <p> + * Note that this <em>does not</em> perform signature verification; that + * must be done separately in {@link #collectCertificates(Package, int)}. + */ + public Package parseSplitPackage(File apkDir, int flags) throws PackageParserException { + final File[] files = apkDir.listFiles(); + if (ArrayUtils.isEmpty(files)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "No packages found in split"); + } + + String packageName = null; + int versionCode = 0; + + final ArrayMap<String, File> apks = new ArrayMap<>(); + for (File file : files) { + if (file.isFile() && isPackageFilename(file)) { + final ApkLite lite = parseApkLite(file, 0); + + // Assert that all package names and version codes are + // consistent with the first one we encounter. + if (packageName == null) { + packageName = lite.packageName; + versionCode = lite.versionCode; + } else { + if (!packageName.equals(lite.packageName)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent package " + lite.packageName + " in " + file + + "; expected " + packageName); + } + if (versionCode != lite.versionCode) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent version " + lite.versionCode + " in " + file + + "; expected " + versionCode); + } + } + + // Assert that each split is defined only once + if (apks.put(lite.splitName, file) != null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Split name " + lite.splitName + + " defined more than once; most recent was " + file); + } + } + } + + final File baseFile = apks.remove(null); + if (baseFile == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Missing base APK in " + apkDir); + } + + // Always apply deterministic ordering based on splitName + final int size = apks.size(); + + final String[] splitNames = apks.keySet().toArray(new String[size]); + Arrays.sort(splitNames, sSplitNameComparator); + + final File[] splitFiles = new File[size]; + for (int i = 0; i < size; i++) { + splitFiles[i] = apks.get(splitNames[i]); + } + + final Package pkg = parseBaseApk(baseFile, flags); + if (pkg == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Failed to parse base APK: " + baseFile); + } + + for (File splitFile : splitFiles) { + parseSplitApk(pkg, splitFile, flags); + } + + return pkg; + } + + /** + * Parse the given APK file, treating it as as a single monolithic package. + * <p> + * Note that this <em>does not</em> perform signature verification; that + * must be done separately in {@link #collectCertificates(Package, int)}. + */ + public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { + final Package pkg = parseBaseApk(apkFile, flags); + if (pkg != null) { + return pkg; + } else { + throw new PackageParserException(mParseError, "Failed to parse " + apkFile); + } } - public Package parsePackage(File sourceFile, String destCodePath, - DisplayMetrics metrics, int flags, boolean trustedOverlay) { + private Package parseBaseApk(File apkFile, int flags) { + final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0; + mParseError = PackageManager.INSTALL_SUCCEEDED; - mArchiveSourcePath = sourceFile.getPath(); - if (!sourceFile.isFile()) { - Slog.w(TAG, "Skipping dir: " + mArchiveSourcePath); + final String apkPath = apkFile.getAbsolutePath(); + mArchiveSourcePath = apkFile.getAbsolutePath(); + if (!apkFile.isFile()) { + Slog.w(TAG, "Skipping dir: " + apkPath); mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK; return null; } - if (!isPackageFilename(sourceFile.getName()) + if (!isPackageFilename(apkFile.getName()) && (flags&PARSE_MUST_BE_APK) != 0) { if ((flags&PARSE_IS_SYSTEM) == 0) { // We expect to have non-.apk files in the system dir, // so don't warn about them. - Slog.w(TAG, "Skipping non-package file: " + mArchiveSourcePath); + Slog.w(TAG, "Skipping non-package file: " + apkPath); } mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK; return null; } if (DEBUG_JAR) - Slog.d(TAG, "Scanning package: " + mArchiveSourcePath); + Slog.d(TAG, "Scanning package: " + apkPath); XmlResourceParser parser = null; AssetManager assmgr = null; @@ -536,19 +671,18 @@ public class PackageParser { boolean assetError = true; try { assmgr = new AssetManager(); - int cookie = assmgr.addAssetPath(mArchiveSourcePath); + int cookie = assmgr.addAssetPath(apkPath); if (cookie != 0) { - res = new Resources(assmgr, metrics, null); + res = new Resources(assmgr, mMetrics, null); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); assetError = false; } else { - Slog.w(TAG, "Failed adding asset path:"+mArchiveSourcePath); + Slog.w(TAG, "Failed adding asset path:" + apkPath); } } catch (Exception e) { - Slog.w(TAG, "Unable to read AndroidManifest.xml of " - + mArchiveSourcePath, e); + Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e); } if (assetError) { if (assmgr != null) assmgr.close(); @@ -560,21 +694,20 @@ public class PackageParser { Exception errorException = null; try { // XXXX todo: need to figure out correct configuration. - pkg = parsePackage(res, parser, flags, trustedOverlay, errorText); + pkg = parseBaseApk(res, parser, flags, trustedOverlay, errorText); } catch (Exception e) { errorException = e; mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; } - if (pkg == null) { // If we are only parsing core apps, then a null with INSTALL_SUCCEEDED // just means to skip this app so don't make a fuss about it. if (!mOnlyCoreApps || mParseError != PackageManager.INSTALL_SUCCEEDED) { if (errorException != null) { - Slog.w(TAG, mArchiveSourcePath, errorException); + Slog.w(TAG, apkPath, errorException); } else { - Slog.w(TAG, mArchiveSourcePath + " (at " + Slog.w(TAG, apkPath + " (at " + parser.getPositionDescription() + "): " + errorText[0]); } @@ -590,24 +723,32 @@ public class PackageParser { parser.close(); assmgr.close(); - // Set code and resource paths - pkg.mPath = destCodePath; - pkg.mScanPath = mArchiveSourcePath; - //pkg.applicationInfo.sourceDir = destCodePath; - //pkg.applicationInfo.publicSourceDir = destRes; + pkg.codePath = apkPath; pkg.mSignatures = null; return pkg; } + private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + mArchiveSourcePath = apkFile.getAbsolutePath(); + + // TODO: expand split APK parsing + pkg.splitCodePaths = ArrayUtils.appendElement(String.class, pkg.splitCodePaths, + apkFile.getAbsolutePath()); + } + /** * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the * APK. If it successfully scanned the package and found the * {@code AndroidManifest.xml}, {@code true} is returned. */ - public boolean collectManifestDigest(Package pkg) { + public void collectManifestDigest(Package pkg) throws PackageParserException { + pkg.manifestDigest = null; + + // TODO: extend to gather digest for split APKs try { - final StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath); + final StrictJarFile jarFile = new StrictJarFile(pkg.codePath); try { final ZipEntry je = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); if (je != null) { @@ -616,180 +757,127 @@ public class PackageParser { } finally { jarFile.close(); } - return true; - } catch (IOException e) { - return false; + } catch (IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Failed to collect manifest digest"); } } - public boolean collectCertificates(Package pkg, int flags) { + /** + * Collect certificates from all the APKs described in the given package, + * populating {@link Package#mSignatures}. This also asserts that all APK + * contents are signed correctly and consistently. + */ + public void collectCertificates(Package pkg, int flags) throws PackageParserException { + pkg.mCertificates = null; pkg.mSignatures = null; + pkg.mSigningKeys = null; - WeakReference<byte[]> readBufferRef; - byte[] readBuffer = null; - synchronized (mSync) { - readBufferRef = mReadBuffer; - if (readBufferRef != null) { - mReadBuffer = null; - readBuffer = readBufferRef.get(); - } - if (readBuffer == null) { - readBuffer = new byte[8192]; - readBufferRef = new WeakReference<byte[]>(readBuffer); + collectCertificates(pkg, new File(pkg.codePath), flags); + + if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { + for (String splitCodePath : pkg.splitCodePaths) { + collectCertificates(pkg, new File(splitCodePath), flags); } } + } + + private static void collectCertificates(Package pkg, File apkFile, int flags) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + StrictJarFile jarFile = null; try { - StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath); - - Certificate[][] certs = null; - - if ((flags&PARSE_IS_SYSTEM) != 0) { - // If this package comes from the system image, then we - // can trust it... we'll just use the AndroidManifest.xml - // to retrieve its signatures, not validating all of the - // files. - ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); - certs = loadCertificates(jarFile, jarEntry, readBuffer); - if (certs == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates at entry " - + jarEntry.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } - if (DEBUG_JAR) { - Slog.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry - + " certs=" + (certs != null ? certs.length : 0)); - if (certs != null) { - final int N = certs.length; - for (int i=0; i<N; i++) { - Slog.i(TAG, " Public key: " - + certs[i][0].getPublicKey().getEncoded() - + " " + certs[i][0].getPublicKey()); - } - } - } - } else { - Iterator<ZipEntry> entries = jarFile.iterator(); - while (entries.hasNext()) { - final ZipEntry je = entries.next(); - if (je.isDirectory()) continue; + jarFile = new StrictJarFile(apkPath); - final String name = je.getName(); + // Always verify manifest, regardless of source + final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); + if (manifestEntry == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Package " + apkPath + " has no manifest"); + } - if (name.startsWith("META-INF/")) - continue; + final List<ZipEntry> toVerify = new ArrayList<>(); + toVerify.add(manifestEntry); - if (ANDROID_MANIFEST_FILENAME.equals(name)) { - pkg.manifestDigest = - ManifestDigest.fromInputStream(jarFile.getInputStream(je)); - } + // If we're parsing an untrusted package, verify all contents + if ((flags & PARSE_IS_SYSTEM) == 0) { + final Iterator<ZipEntry> i = jarFile.iterator(); + while (i.hasNext()) { + final ZipEntry entry = i.next(); - final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer); - if (DEBUG_JAR) { - Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName() - + ": certs=" + certs + " (" - + (certs != null ? certs.length : 0) + ")"); - } + if (entry.isDirectory()) continue; + if (entry.getName().startsWith("META-INF/")) continue; + if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue; - if (localCerts == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates at entry " - + je.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } else if (certs == null) { - certs = localCerts; - } else { - // Ensure all certificates match. - for (int i=0; i<certs.length; i++) { - boolean found = false; - for (int j=0; j<localCerts.length; j++) { - if (certs[i] != null && - certs[i].equals(localCerts[j])) { - found = true; - break; - } - } - if (!found || certs.length != localCerts.length) { - Slog.e(TAG, "Package " + pkg.packageName - + " has mismatched certificates at entry " - + je.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; - return false; - } - } - } + toVerify.add(entry); } } - jarFile.close(); - - synchronized (mSync) { - mReadBuffer = readBufferRef; - } - if (!ArrayUtils.isEmpty(certs)) { - pkg.mSignatures = convertToSignatures(certs); - } else { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates; ignoring!"); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } + // Verify that entries are signed consistently with the first entry + // we encountered. Note that for splits, certificates may have + // already been populated during an earlier parse of a base APK. + for (ZipEntry entry : toVerify) { + final Certificate[][] entryCerts = loadCertificates(jarFile, entry); + if (ArrayUtils.isEmpty(entryCerts)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Package " + apkPath + " has no certificates at entry " + + entry.getName()); + } - // Add the signing KeySet to the system - pkg.mSigningKeys = new HashSet<PublicKey>(); - for (int i=0; i < certs.length; i++) { - pkg.mSigningKeys.add(certs[i][0].getPublicKey()); + if (pkg.mCertificates == null) { + pkg.mCertificates = entryCerts; + pkg.mSignatures = convertToSignatures(entryCerts); + pkg.mSigningKeys = new ArraySet<>(); + for (int i = 0; i < entryCerts.length; i++) { + pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); + } + } else { + final boolean certsMatch = (pkg.mCertificates.length == entryCerts.length) + && ArrayUtils.containsAll(pkg.mCertificates, entryCerts) + && ArrayUtils.containsAll(entryCerts, pkg.mCertificates); + if (!certsMatch) { + throw new PackageParserException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + + " has mismatched certificates at entry " + + entry.getName()); + } + } } - - } catch (CertificateEncodingException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (IOException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (SecurityException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (RuntimeException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; - return false; + } catch (GeneralSecurityException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, + "Failed to collect certificates from " + apkPath, e); + } finally { + closeQuietly(jarFile); } - - return true; } /** * Only collect certificates on the manifest; does not validate signatures * across remainder of package. */ - private static Signature[] collectCertificates(String packageFilePath) { + private static Signature[] collectManifestCertificates(File apkFile) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); try { - final StrictJarFile jarFile = new StrictJarFile(packageFilePath); + final StrictJarFile jarFile = new StrictJarFile(apkPath); try { final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); - if (jarEntry != null) { - final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null); - return convertToSignatures(certs); + if (jarEntry == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Package " + apkPath + " has no manifest"); } + + final Certificate[][] certs = loadCertificates(jarFile, jarEntry); + return convertToSignatures(certs); + } finally { jarFile.close(); } - } catch (GeneralSecurityException e) { - Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e); - } catch (IOException e) { - Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e); + } catch (GeneralSecurityException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, + "Failed to collect certificates from " + apkPath, e); } - return null; } private static Signature[] convertToSignatures(Certificate[][] certs) @@ -801,67 +889,55 @@ public class PackageParser { return res; } - /* - * Utility method that retrieves just the package name and install - * location from the apk location at the given file path. - * @param packageFilePath file location of the apk - * @param flags Special parse flags - * @return PackageLite object with package information or null on failure. + /** + * Utility method that retrieves lightweight details about a single APK + * file, including package name, split name, and install location. + * + * @param apkFile path to a single APK + * @param flags optional parse flags, such as {@link #PARSE_GET_SIGNATURES} */ - public static PackageLite parsePackageLite(String packageFilePath, int flags) { + public static ApkLite parseApkLite(File apkFile, int flags) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + AssetManager assmgr = null; - final XmlResourceParser parser; - final Resources res; + XmlResourceParser parser = null; try { assmgr = new AssetManager(); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); - int cookie = assmgr.addAssetPath(packageFilePath); + int cookie = assmgr.addAssetPath(apkPath); if (cookie == 0) { - return null; + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Failed to parse " + apkPath); } final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - res = new Resources(assmgr, metrics, null); + + final Resources res = new Resources(assmgr, metrics, null); parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); - } catch (Exception e) { - if (assmgr != null) assmgr.close(); - Slog.w(TAG, "Unable to read AndroidManifest.xml of " - + packageFilePath, e); - return null; - } - // Only collect certificates on the manifest; does not validate - // signatures across remainder of package. - final Signature[] signatures; - if ((flags & PARSE_GET_SIGNATURES) != 0) { - signatures = collectCertificates(packageFilePath); - } else { - signatures = null; - } + // Only collect certificates on the manifest; does not validate + // signatures across remainder of package. + final Signature[] signatures; + if ((flags & PARSE_GET_SIGNATURES) != 0) { + signatures = collectManifestCertificates(apkFile); + } else { + signatures = null; + } - final AttributeSet attrs = parser; - final String errors[] = new String[1]; - PackageLite packageLite = null; - try { - packageLite = parsePackageLite(res, parser, attrs, flags, signatures, errors); - } catch (PackageParserException e) { - Slog.w(TAG, packageFilePath, e); - } catch (IOException e) { - Slog.w(TAG, packageFilePath, e); - } catch (XmlPullParserException e) { - Slog.w(TAG, packageFilePath, e); + final AttributeSet attrs = parser; + return parseApkLite(res, parser, attrs, flags, signatures); + + } catch (XmlPullParserException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to parse " + apkPath, e); } finally { if (parser != null) parser.close(); if (assmgr != null) assmgr.close(); } - if (packageLite == null) { - Slog.e(TAG, "parsePackageLite error: " + errors[0]); - return null; - } - return packageLite; } private static String validateName(String name, boolean requiresSeparator) { @@ -917,12 +993,16 @@ public class PackageParser { } } - final String splitName = attrs.getAttributeValue(null, "split"); + String splitName = attrs.getAttributeValue(null, "split"); if (splitName != null) { - final String error = validateName(splitName, true); - if (error != null) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, - "Invalid manifest split: " + error); + if (splitName.length() == 0) { + splitName = null; + } else { + final String error = validateName(splitName, true); + if (error != null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, + "Invalid manifest split: " + error); + } } } @@ -930,9 +1010,9 @@ public class PackageParser { (splitName != null) ? splitName.intern() : splitName); } - private static PackageLite parsePackageLite(Resources res, XmlPullParser parser, - AttributeSet attrs, int flags, Signature[] signatures, String[] outError) - throws IOException, XmlPullParserException, PackageParserException { + private static ApkLite parseApkLite(Resources res, XmlPullParser parser, + AttributeSet attrs, int flags, Signature[] signatures) throws IOException, + XmlPullParserException, PackageParserException { final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags); int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; @@ -965,14 +1045,14 @@ public class PackageParser { } if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) { - final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError); + final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags); if (verifier != null) { verifiers.add(verifier); } } } - return new PackageLite(packageSplit.first, packageSplit.second, versionCode, + return new ApkLite(packageSplit.first, packageSplit.second, versionCode, installLocation, verifiers, signatures); } @@ -988,9 +1068,8 @@ public class PackageParser { return new Signature(sig); } - private Package parsePackage( - Resources res, XmlResourceParser parser, int flags, boolean trustedOverlay, - String[] outError) throws XmlPullParserException, IOException { + private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, + boolean trustedOverlay, String[] outError) throws XmlPullParserException, IOException { AttributeSet attrs = parser; mParseInstrumentationArgs = null; @@ -1019,7 +1098,13 @@ public class PackageParser { } } - final Package pkg = new Package(pkgName, splitName); + if (!TextUtils.isEmpty(splitName)) { + outError[0] = "Expected base APK, but found split " + splitName; + mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; + return null; + } + + final Package pkg = new Package(pkgName); boolean foundApp = false; TypedArray sa = res.obtainAttributes(attrs, @@ -1710,7 +1795,7 @@ public class PackageParser { } } - owner.mKeySetMapping = new HashMap<String, Set<PublicKey>>(); + owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>(); for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) { PublicKey key = e.getKey(); Set<String> keySetNames = e.getValue(); @@ -1718,7 +1803,7 @@ public class PackageParser { if (owner.mKeySetMapping.containsKey(alias)) { owner.mKeySetMapping.get(alias).add(key); } else { - Set<PublicKey> keys = new HashSet<PublicKey>(); + ArraySet<PublicKey> keys = new ArraySet<PublicKey>(); keys.add(key); owner.mKeySetMapping.put(alias, keys); } @@ -2005,6 +2090,11 @@ public class PackageParser { false)) { ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_fullBackupOnly, + false)) { + ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY; + } } } @@ -2174,7 +2264,6 @@ public class PackageParser { } final int innerDepth = parser.getDepth(); - int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { @@ -2525,10 +2614,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) { - a.info.flags |= ActivityInfo.FLAG_PERSISTABLE; - } + a.info.persistableMode = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestActivity_persistableMode, + ActivityInfo.PERSIST_ROOT_ONLY); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, @@ -2541,6 +2629,12 @@ public class PackageParser { false)) { a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; } + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity, + false)) { + a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; + } } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; @@ -2551,13 +2645,13 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_singleUser, false)) { a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; - if (a.info.exported) { + if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Activity exported request ignored due to singleUser: " + a.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); a.info.exported = false; + setExported = true; } - setExported = true; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly, @@ -2910,7 +3004,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_singleUser, false)) { p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; - if (p.info.exported) { + if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Provider exported request ignored due to singleUser: " + p.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); @@ -3184,13 +3278,13 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_singleUser, false)) { s.info.flags |= ServiceInfo.FLAG_SINGLE_USER; - if (s.info.exported) { + if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Service exported request ignored due to singleUser: " + s.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); s.info.exported = false; + setExported = true; } - setExported = true; } sa.recycle(); @@ -3346,8 +3440,7 @@ public class PackageParser { } private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser, - AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException, - IOException { + AttributeSet attrs, int flags) { final TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestPackageVerifier); @@ -3581,10 +3674,20 @@ public class PackageParser { return true; } + /** + * Representation of a full package parsed from APK files on disk. A package + * consists of a single base APK, and zero or more split APKs. + */ public final static class Package { public String packageName; - public String splitName; + + // TODO: work towards making these paths invariant + + /** Base APK */ + public String codePath; + /** Split APKs, ordered by parsed splitName */ + public String[] splitCodePaths; // For now we only support one application per package. public final ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -3616,9 +3719,6 @@ public class PackageParser { // We store the application meta-data independently to avoid multiple unwanted references public Bundle mAppMetaData = null; - // If this is a 3rd party app, this is the path of the zip file. - public String mPath; - // The version code declared for this package. public int mVersionCode; @@ -3632,16 +3732,13 @@ public class PackageParser { public int mSharedUserLabel; // Signatures that were read from the package. - public Signature mSignatures[]; + public Signature[] mSignatures; + public Certificate[][] mCertificates; // For use by package manager service for quick lookup of // preferred up order. public int mPreferredOrder = 0; - // For use by the package manager to keep track of the path to the - // file an app came from. - public String mScanPath; - // For use by package manager to keep track of where it needs to do dexopt. public boolean mDexOptNeeded = true; @@ -3698,16 +3795,24 @@ public class PackageParser { /** * Data used to feed the KeySetManager */ - public Set<PublicKey> mSigningKeys; - public Map<String, Set<PublicKey>> mKeySetMapping; + public ArraySet<PublicKey> mSigningKeys; + public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping; - public Package(String packageName, String splitName) { + public Package(String packageName) { this.packageName = packageName; - this.splitName = splitName; applicationInfo.packageName = packageName; applicationInfo.uid = -1; } + public Collection<String> getAllCodePaths() { + ArrayList<String> paths = new ArrayList<>(); + paths.add(codePath); + if (!ArrayUtils.isEmpty(splitCodePaths)) { + Collections.addAll(paths, splitCodePaths); + } + return paths; + } + public void setPackageName(String newName) { packageName = newName; applicationInfo.packageName = newName; @@ -4310,6 +4415,33 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } + private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>(); + + public static long readFullyIgnoringContents(InputStream in) throws IOException { + byte[] buffer = sBuffer.getAndSet(null); + if (buffer == null) { + buffer = new byte[4096]; + } + + int n = 0; + int count = 0; + while ((n = in.read(buffer, 0, buffer.length)) != -1) { + count += n; + } + + sBuffer.set(buffer); + return count; + } + + public static void closeQuietly(StrictJarFile jarFile) { + if (jarFile != null) { + try { + jarFile.close(); + } catch (Exception ignored) { + } + } + } + public static class PackageParserException extends Exception { public final int error; @@ -4317,5 +4449,10 @@ public class PackageParser { super(detailMessage); this.error = error; } + + public PackageParserException(int error, String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + this.error = error; + } } } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index 1ff41c0..1f9d60c 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -19,8 +19,11 @@ package android.content.pm; import android.content.ComponentName; import android.content.IntentFilter; import android.graphics.drawable.Drawable; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Printer; import android.util.Slog; @@ -127,6 +130,18 @@ public class ResolveInfo implements Parcelable { public String resolvePackageName; /** + * If not equal to UserHandle.USER_CURRENT, then the intent will be forwarded to this user. + * @hide + */ + public int targetUserId; + + /** + * If true, then loadIcon will return the icon of the target user. + * @hide + */ + public boolean showTargetUserIcon; + + /** * @hide Target comes from system process? */ public boolean system; @@ -202,6 +217,10 @@ public class ResolveInfo implements Parcelable { return dr; } } + if (showTargetUserIcon) { + Bitmap bm = pm.getUserIcon(targetUserId); + return new BitmapDrawable(bm); + } return ci.loadIcon(pm); } @@ -215,7 +234,9 @@ public class ResolveInfo implements Parcelable { public final int getIconResource() { if (icon != 0) return icon; final ComponentInfo ci = getComponentInfo(); - if (ci != null) return ci.getIconResource(); + if (ci != null && !showTargetUserIcon) { + return ci.getIconResource(); + } return 0; } @@ -250,6 +271,7 @@ public class ResolveInfo implements Parcelable { } public ResolveInfo() { + targetUserId = UserHandle.USER_CURRENT; } public ResolveInfo(ResolveInfo orig) { @@ -266,6 +288,7 @@ public class ResolveInfo implements Parcelable { icon = orig.icon; resolvePackageName = orig.resolvePackageName; system = orig.system; + targetUserId = orig.targetUserId; } public String toString() { @@ -285,6 +308,13 @@ public class ResolveInfo implements Parcelable { } sb.append(" m=0x"); sb.append(Integer.toHexString(match)); + if (targetUserId != UserHandle.USER_CURRENT) { + sb.append(" targetUserId="); + sb.append(targetUserId); + } + if (showTargetUserIcon) { + sb.append(" [showTargetUserIcon]"); + } sb.append('}'); return sb.toString(); } @@ -320,6 +350,8 @@ public class ResolveInfo implements Parcelable { TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags); dest.writeInt(icon); dest.writeString(resolvePackageName); + dest.writeInt(targetUserId); + dest.writeInt(showTargetUserIcon ? 1 : 0); dest.writeInt(system ? 1 : 0); } @@ -363,6 +395,8 @@ public class ResolveInfo implements Parcelable { = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); icon = source.readInt(); resolvePackageName = source.readString(); + targetUserId = source.readInt(); + showTargetUserIcon = source.readInt() != 0; system = source.readInt() != 0; } @@ -374,6 +408,13 @@ public class ResolveInfo implements Parcelable { } public final int compare(ResolveInfo a, ResolveInfo b) { + // We want to put the one targeted to another user at the end of the dialog. + if (a.targetUserId != UserHandle.USER_CURRENT) { + return 1; + } + if (b.targetUserId != UserHandle.USER_CURRENT) { + return -1; + } CharSequence sa = a.loadLabel(mPM); if (sa == null) sa = a.activityInfo.name; CharSequence sb = b.loadLabel(mPM); diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index a07fc97..a83bd4a 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -17,11 +17,14 @@ package android.content.res; import android.content.pm.ActivityInfo; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; +import java.text.Format; +import java.util.ArrayList; import java.util.Locale; /** @@ -1306,4 +1309,240 @@ public final class Configuration implements Parcelable, Comparable<Configuration private static int getScreenLayoutNoDirection(int screenLayout) { return screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK; } + + /** + * + * @hide + */ + public static String localeToResourceQualifier(Locale locale) { + StringBuilder sb = new StringBuilder(); + boolean l = (locale.getLanguage().length() != 0); + boolean c = (locale.getCountry().length() != 0); + boolean s = (locale.getScript().length() != 0); + boolean v = (locale.getVariant().length() != 0); + + if (l) { + sb.append(locale.getLanguage()); + if (c) { + sb.append("-r").append(locale.getCountry()); + if (s) { + sb.append("-s").append(locale.getScript()); + if (v) { + sb.append("-v").append(locale.getVariant()); + } + } + } + } + return sb.toString(); + } + + + /** + * Returns a string representation of the configuration that can be parsed + * by build tools (like AAPT). + * + * + * + * @hide + */ + public static String resourceQualifierString(Configuration config) { + ArrayList<String> parts = new ArrayList<String>(); + + if (config.mcc != 0) { + parts.add(config.mcc + "mcc"); + if (config.mnc != 0) { + parts.add(config.mnc + "mnc"); + } + } + + if (!config.locale.getLanguage().isEmpty()) { + parts.add(localeToResourceQualifier(config.locale)); + } + + switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) { + case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR: + parts.add("ldltr"); + break; + case Configuration.SCREENLAYOUT_LAYOUTDIR_RTL: + parts.add("ldrtl"); + break; + default: + break; + } + + if (config.smallestScreenWidthDp != 0) { + parts.add("sw" + config.smallestScreenWidthDp + "dp"); + } + + if (config.screenWidthDp != 0) { + parts.add("w" + config.screenWidthDp + "dp"); + } + + if (config.screenHeightDp != 0) { + parts.add("h" + config.screenHeightDp + "dp"); + } + + switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) { + case Configuration.SCREENLAYOUT_SIZE_SMALL: + parts.add("small"); + break; + case Configuration.SCREENLAYOUT_SIZE_NORMAL: + parts.add("normal"); + break; + case Configuration.SCREENLAYOUT_SIZE_LARGE: + parts.add("large"); + break; + case Configuration.SCREENLAYOUT_SIZE_XLARGE: + parts.add("xlarge"); + break; + default: + break; + } + + switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) { + case Configuration.SCREENLAYOUT_LONG_YES: + parts.add("long"); + break; + case Configuration.SCREENLAYOUT_LONG_NO: + parts.add("notlong"); + break; + default: + break; + } + + switch (config.orientation) { + case Configuration.ORIENTATION_LANDSCAPE: + parts.add("land"); + break; + case Configuration.ORIENTATION_PORTRAIT: + parts.add("port"); + break; + default: + break; + } + + switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) { + case Configuration.UI_MODE_TYPE_APPLIANCE: + parts.add("appliance"); + break; + case Configuration.UI_MODE_TYPE_DESK: + parts.add("desk"); + break; + case Configuration.UI_MODE_TYPE_TELEVISION: + parts.add("television"); + break; + case Configuration.UI_MODE_TYPE_CAR: + parts.add("car"); + break; + case Configuration.UI_MODE_TYPE_WATCH: + parts.add("watch"); + break; + default: + break; + } + + switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) { + case Configuration.UI_MODE_NIGHT_YES: + parts.add("night"); + break; + case Configuration.UI_MODE_NIGHT_NO: + parts.add("notnight"); + break; + default: + break; + } + + switch (config.densityDpi) { + case 0: + break; + case 120: + parts.add("ldpi"); + break; + case 160: + parts.add("mdpi"); + break; + case 213: + parts.add("tvdpi"); + break; + case 240: + parts.add("hdpi"); + break; + case 320: + parts.add("xhdpi"); + break; + default: + parts.add(config.densityDpi + "dpi"); + break; + } + + switch (config.touchscreen) { + case Configuration.TOUCHSCREEN_NOTOUCH: + parts.add("notouch"); + break; + case Configuration.TOUCHSCREEN_FINGER: + parts.add("finger"); + break; + default: + break; + } + + switch (config.keyboardHidden) { + case Configuration.KEYBOARDHIDDEN_NO: + parts.add("keysexposed"); + break; + case Configuration.KEYBOARDHIDDEN_YES: + parts.add("keyshidden"); + break; + case Configuration.KEYBOARDHIDDEN_SOFT: + parts.add("keyssoft"); + break; + default: + break; + } + + switch (config.keyboard) { + case Configuration.KEYBOARD_NOKEYS: + parts.add("nokeys"); + break; + case Configuration.KEYBOARD_QWERTY: + parts.add("qwerty"); + break; + case Configuration.KEYBOARD_12KEY: + parts.add("12key"); + break; + default: + break; + } + + switch (config.navigationHidden) { + case Configuration.NAVIGATIONHIDDEN_NO: + parts.add("navexposed"); + break; + case Configuration.NAVIGATIONHIDDEN_YES: + parts.add("navhidden"); + break; + default: + break; + } + + switch (config.navigation) { + case Configuration.NAVIGATION_NONAV: + parts.add("nonav"); + break; + case Configuration.NAVIGATION_DPAD: + parts.add("dpad"); + break; + case Configuration.NAVIGATION_TRACKBALL: + parts.add("trackball"); + break; + case Configuration.NAVIGATION_WHEEL: + parts.add("wheel"); + break; + default: + break; + } + + parts.add("v" + Build.VERSION.SDK_INT); + return TextUtils.join("-", parts); + } } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 20dcf83..d2146ac 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -885,9 +885,9 @@ public class TypedArray { /** * Extracts theme attributes from a typed array for later resolution using - * {@link Theme#resolveAttributes(int[], int[])}. Removes the entries from - * the typed array so that subsequent calls to typed getters will return the - * default value without crashing. + * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}. + * Removes the entries from the typed array so that subsequent calls to typed + * getters will return the default value without crashing. * * @return an array of length {@link #getIndexCount()} populated with theme * attributes, or null if there are no theme attributes in the typed diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 0705e0c..cc8503b 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -167,11 +167,16 @@ public class Camera { private boolean mOneShot; private boolean mWithBuffer; private boolean mFaceDetectionRunning = false; - private Object mAutoFocusCallbackLock = new Object(); + private final Object mAutoFocusCallbackLock = new Object(); private static final int NO_ERROR = 0; private static final int EACCESS = -13; private static final int ENODEV = -19; + private static final int EBUSY = -16; + private static final int EINVAL = -22; + private static final int ENOSYS = -38; + private static final int EUSERS = -87; + private static final int EOPNOTSUPP = -95; /** * Broadcast Action: A new picture is taken by the camera, and the entry of @@ -190,6 +195,22 @@ public class Camera { public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; /** + * Camera HAL device API version 1.0 + * @hide + */ + public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100; + + /** + * A constant meaning the normal camera connect/open will be used. + */ + private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2; + + /** + * Used to indicate HAL version un-specified. + */ + private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1; + + /** * Hardware face detection. It does not use much CPU. */ private static final int CAMERA_FACE_DETECTION_HW = 0; @@ -331,14 +352,82 @@ public class Camera { return null; } - Camera(int cameraId) { - int err = cameraInit(cameraId); + /** + * Creates a new Camera object to access a particular hardware camera with + * given hal API version. If the same camera is opened by other applications + * or the hal API version is not supported by this device, this will throw a + * RuntimeException. + * <p> + * You must call {@link #release()} when you are done using the camera, + * otherwise it will remain locked and be unavailable to other applications. + * <p> + * Your application should only have one Camera object active at a time for + * a particular hardware camera. + * <p> + * Callbacks from other methods are delivered to the event loop of the + * thread which called open(). If this thread has no event loop, then + * callbacks are delivered to the main application event loop. If there is + * no main application event loop, callbacks are not delivered. + * <p class="caution"> + * <b>Caution:</b> On some devices, this method may take a long time to + * complete. It is best to call this method from a worker thread (possibly + * using {@link android.os.AsyncTask}) to avoid blocking the main + * application UI thread. + * + * @param cameraId The hardware camera to access, between 0 and + * {@link #getNumberOfCameras()}-1. + * @param halVersion The HAL API version this camera device to be opened as. + * @return a new Camera object, connected, locked and ready for use. + * + * @throws IllegalArgumentException if the {@code halVersion} is invalid + * + * @throws RuntimeException if opening the camera fails (for example, if the + * camera is in use by another process or device policy manager has disabled + * the camera). + * + * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName) + * @see #CAMERA_HAL_API_VERSION_1_0 + * + * @hide + */ + public static Camera openLegacy(int cameraId, int halVersion) { + if (halVersion < CAMERA_HAL_API_VERSION_1_0) { + throw new IllegalArgumentException("Invalid HAL version " + halVersion); + } + + return new Camera(cameraId, halVersion); + } + + /** + * Create a legacy camera object. + * + * @param cameraId The hardware camera to access, between 0 and + * {@link #getNumberOfCameras()}-1. + * @param halVersion The HAL API version this camera device to be opened as. + */ + private Camera(int cameraId, int halVersion) { + int err = cameraInitVersion(cameraId, halVersion); if (checkInitErrors(err)) { switch(err) { case EACCESS: throw new RuntimeException("Fail to connect to camera service"); case ENODEV: throw new RuntimeException("Camera initialization failed"); + case ENOSYS: + throw new RuntimeException("Camera initialization failed because some methods" + + " are not implemented"); + case EOPNOTSUPP: + throw new RuntimeException("Camera initialization failed because the hal" + + " version is not supported by this device"); + case EINVAL: + throw new RuntimeException("Camera initialization failed because the input" + + " arugments are invalid"); + case EBUSY: + throw new RuntimeException("Camera initialization failed because the camera" + + " device was already opened"); + case EUSERS: + throw new RuntimeException("Camera initialization failed because the max" + + " number of camera devices were already opened"); default: // Should never hit this. throw new RuntimeException("Unknown camera error"); @@ -346,10 +435,7 @@ public class Camera { } } - /** - * @hide - */ - public int cameraInit(int cameraId) { + private int cameraInitVersion(int cameraId, int halVersion) { mShutterCallback = null; mRawImageCallback = null; mJpegCallback = null; @@ -369,9 +455,48 @@ public class Camera { String packageName = ActivityThread.currentPackageName(); - return native_setup(new WeakReference<Camera>(this), cameraId, packageName); + return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName); + } + + private int cameraInitNormal(int cameraId) { + return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT); + } + + /** + * Connect to the camera service using #connectLegacy + * + * <p> + * This acts the same as normal except that it will return + * the detailed error code if open fails instead of + * converting everything into {@code NO_INIT}.</p> + * + * <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p> + * + * @return a detailed errno error code, or {@code NO_ERROR} on success + * + * @hide + */ + public int cameraInitUnspecified(int cameraId) { + return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED); + } + + /** used by Camera#open, Camera#open(int) */ + Camera(int cameraId) { + int err = cameraInitNormal(cameraId); + if (checkInitErrors(err)) { + switch(err) { + case EACCESS: + throw new RuntimeException("Fail to connect to camera service"); + case ENODEV: + throw new RuntimeException("Camera initialization failed"); + default: + // Should never hit this. + throw new RuntimeException("Unknown camera error"); + } + } } + /** * @hide */ @@ -392,11 +517,12 @@ public class Camera { Camera() { } + @Override protected void finalize() { release(); } - private native final int native_setup(Object camera_this, int cameraId, + private native final int native_setup(Object camera_this, int cameraId, int halVersion, String packageName); private native final void native_release(); @@ -929,7 +1055,7 @@ public class Camera { private class EventHandler extends Handler { - private Camera mCamera; + private final Camera mCamera; public EventHandler(Camera c, Looper looper) { super(looper); @@ -2210,6 +2336,7 @@ public class Camera { * @hide * @deprecated */ + @Deprecated public void dump() { Log.e(TAG, "dump: size=" + mMap.size()); for (String k : mMap.keySet()) { diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index 4c50dda..2bc3dd4 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -69,4 +69,16 @@ interface ICameraService * well-formatted in the generated java method. */ int getCameraVendorTagDescriptor(out BinderHolder desc); + + // Writes the camera1 parameters into a single-element array. + int getLegacyParameters(int cameraId, out String[] parameters); + // Determines if a particular API version is supported; see ICameraService.h for version defines + int supportsCameraApi(int cameraId, int apiVersion); + + int connectLegacy(ICameraClient client, int cameraId, + int halVersion, + String clientPackageName, + int clientUid, + // Container for an ICamera object + out BinderHolder device); } diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index c593e9e..de2cc67 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -142,9 +142,10 @@ public final class Sensor { public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature"; /** - * A constant describing a proximity sensor type. + * A constant describing a proximity sensor type. This is a wake up sensor. * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values} * for more details. + * @see #isWakeUpSensor() */ public static final int TYPE_PROXIMITY = 8; @@ -307,8 +308,10 @@ public final class Sensor { * itself. The sensor continues to operate while the device is asleep * and will automatically wake the device to notify when significant * motion is detected. The application does not need to hold any wake - * locks for this sensor to trigger. + * locks for this sensor to trigger. This is a wake up sensor. * <p>See {@link TriggerEvent} for more details. + * + * @see #isWakeUpSensor() */ public static final int TYPE_SIGNIFICANT_MOTION = 17; @@ -381,11 +384,17 @@ public final class Sensor { /** * A constant describing a heart rate monitor. * <p> - * A sensor that measures the heart rate in beats per minute. + * The reported value is the heart rate in beats per minute. + * <p> + * The reported accuracy represents the status of the monitor during the reading. See the + * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager} + * for more details on accuracy/status values. In particular, when the accuracy is + * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate + * value should be discarded. * <p> - * value[0] represents the beats per minute when the measurement was taken. - * value[0] is 0 if the heart rate monitor could not measure the rate or the - * rate is 0 beat per minute. + * This sensor requires permission {@code android.permission.BODY_SENSORS}. + * It will not be returned by {@code SensorManager.getSensorsList} nor + * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission. */ public static final int TYPE_HEART_RATE = 21; @@ -397,6 +406,323 @@ public final class Sensor { public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate"; /** + * A non-wake up variant of proximity sensor. + * + * @see #TYPE_PROXIMITY + */ + public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22; + + /** + * A constant string describing a non-wake up proximity sensor type. + * + * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR + */ + public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = + "android.sensor.non_wake_up_proximity_sensor"; + + /** + * A constant describing a wake up variant of accelerometer sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ACCELEROMETER + */ + public static final int TYPE_WAKE_UP_ACCELEROMETER = 23; + + /** + * A constant string describing a wake up accelerometer. + * + * @see #TYPE_WAKE_UP_ACCELEROMETER + */ + public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER = + "android.sensor.wake_up_accelerometer"; + + /** + * A constant describing a wake up variant of a magnetic field sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_MAGNETIC_FIELD + */ + public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24; + + /** + * A constant string describing a wake up magnetic field sensor. + * + * @see #TYPE_WAKE_UP_MAGNETIC_FIELD + */ + public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD = + "android.sensor.wake_up_magnetic_field"; + + /** + * A constant describing a wake up variant of an orientation sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ORIENTATION + */ + public static final int TYPE_WAKE_UP_ORIENTATION = 25; + + /** + * A constant string describing a wake up orientation sensor. + * + * @see #TYPE_WAKE_UP_ORIENTATION + */ + public static final String STRING_TYPE_WAKE_UP_ORIENTATION = + "android.sensor.wake_up_orientation"; + + /** + * A constant describing a wake up variant of a gyroscope sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GYROSCOPE + */ + public static final int TYPE_WAKE_UP_GYROSCOPE = 26; + + /** + * A constant string describing a wake up gyroscope sensor type. + * + * @see #TYPE_WAKE_UP_GYROSCOPE + */ + public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope"; + + /** + * A constant describing a wake up variant of a light sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_LIGHT + */ + public static final int TYPE_WAKE_UP_LIGHT = 27; + + /** + * A constant string describing a wake up light sensor type. + * + * @see #TYPE_WAKE_UP_LIGHT + */ + public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light"; + + /** + * A constant describing a wake up variant of a pressure sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_PRESSURE + */ + public static final int TYPE_WAKE_UP_PRESSURE = 28; + + /** + * A constant string describing a wake up pressure sensor type. + * + * @see #TYPE_WAKE_UP_PRESSURE + */ + public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure"; + + /** + * A constant describing a wake up variant of a gravity sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GRAVITY + */ + public static final int TYPE_WAKE_UP_GRAVITY = 29; + + /** + * A constant string describing a wake up gravity sensor type. + * + * @see #TYPE_WAKE_UP_GRAVITY + */ + public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity"; + + /** + * A constant describing a wake up variant of a linear acceleration sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_LINEAR_ACCELERATION + */ + public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30; + + /** + * A constant string describing a wake up linear acceleration sensor type. + * + * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION + */ + public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION = + "android.sensor.wake_up_linear_acceleration"; + + /** + * A constant describing a wake up variant of a rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31; + + /** + * A constant string describing a wake up rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR = + "android.sensor.wake_up_rotation_vector"; + + /** + * A constant describing a wake up variant of a relative humidity sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_RELATIVE_HUMIDITY + */ + public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32; + + /** + * A constant string describing a wake up relative humidity sensor type. + * + * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY + */ + public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY = + "android.sensor.wake_up_relative_humidity"; + + /** + * A constant describing a wake up variant of an ambient temperature sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_AMBIENT_TEMPERATURE + */ + public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33; + + /** + * A constant string describing a wake up ambient temperature sensor type. + * + * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE + */ + public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE = + "android.sensor.wake_up_ambient_temperature"; + + /** + * A constant describing a wake up variant of an uncalibrated magnetic field sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED + */ + public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34; + + /** + * A constant string describing a wake up uncalibrated magnetic field sensor type. + * + * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED + */ + public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = + "android.sensor.wake_up_magnetic_field_uncalibrated"; + + /** + * A constant describing a wake up variant of a game rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GAME_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35; + + /** + * A constant string describing a wake up game rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR = + "android.sensor.wake_up_game_rotation_vector"; + + /** + * A constant describing a wake up variant of an uncalibrated gyroscope sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GYROSCOPE_UNCALIBRATED + */ + public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36; + + /** + * A constant string describing a wake up uncalibrated gyroscope sensor type. + * + * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED + */ + public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = + "android.sensor.wake_up_gyroscope_uncalibrated"; + + /** + * A constant describing a wake up variant of a step detector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_STEP_DETECTOR + */ + public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37; + + /** + * A constant string describing a wake up step detector sensor type. + * + * @see #TYPE_WAKE_UP_STEP_DETECTOR + */ + public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR = + "android.sensor.wake_up_step_detector"; + + /** + * A constant describing a wake up variant of a step counter sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_STEP_COUNTER + */ + public static final int TYPE_WAKE_UP_STEP_COUNTER = 38; + + /** + * A constant string describing a wake up step counter sensor type. + * + * @see #TYPE_WAKE_UP_STEP_COUNTER + */ + public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER = + "android.sensor.wake_up_step_counter"; + + /** + * A constant describing a wake up variant of a geomagnetic rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39; + + /** + * A constant string describing a wake up geomagnetic rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = + "android.sensor.wake_up_geomagnetic_rotation_vector"; + + /** + * A constant describing a wake up variant of a heart rate sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_HEART_RATE + */ + public static final int TYPE_WAKE_UP_HEART_RATE = 40; + + /** + * A constant string describing a wake up heart rate sensor type. + * + * @see #TYPE_WAKE_UP_HEART_RATE + */ + public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate"; + + /** + * A sensor of this type generates an event each time a tilt event is detected. A tilt event + * is generated if the direction of the 2-seconds window average gravity changed by at + * least 35 degrees since the activation of the sensor. It is a wake up sensor. + * + * @hide + * @see #isWakeUpSensor() + */ + public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41; + + /** + * A constant string describing a wake up tilt detector sensor type. + * + * @hide + * @see #TYPE_WAKE_UP_TILT_DETECTOR + */ + public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR = + "android.sensor.wake_up_tilt_detector"; + + /** * A constant describing a wake gesture sensor. * <p> * Wake gesture sensors enable waking up the device based on a device specific motion. @@ -410,6 +736,7 @@ public final class Sensor { * the device. This sensor must be low power, as it is likely to be activated 24/7. * Values of events created by this sensors should not be used. * + * @see #isWakeUpSensor() * @hide This sensor is expected to only be used by the power manager */ public static final int TYPE_WAKE_GESTURE = 42; @@ -427,16 +754,47 @@ public final class Sensor { */ public static final int TYPE_ALL = -1; - /* Reporting mode constants for sensors. Each sensor will have exactly one - reporting mode associated with it. */ - // Events are reported at a constant rate. - static int REPORTING_MODE_CONTINUOUS = 1; + // If this flag is set, the sensor defined as a wake up sensor. This field and REPORTING_MODE_* + // constants are defined as flags in sensors.h. Modify at both places if needed. + private static final int SENSOR_FLAG_WAKE_UP_SENSOR = 1; + + /** + * Events are reported at a constant rate which is set by the rate parameter of + * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}. Note: If other + * applications are requesting a higher rate, the sensor data might be delivered at faster rates + * than requested. + */ + public static final int REPORTING_MODE_CONTINUOUS = 0; + + /** + * Events are reported only when the value changes. Event delivery rate can be limited by + * setting appropriate value for rate parameter of + * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} Note: If other + * applications are requesting a higher rate, the sensor data might be delivered at faster rates + * than requested. + */ + public static final int REPORTING_MODE_ON_CHANGE = 1; + + /** + * Events are reported in one-shot mode. Upon detection of an event, the sensor deactivates + * itself and then sends a single event. Sensors of this reporting mode must be registered to + * using {@link SensorManager#requestTriggerSensor(TriggerEventListener, Sensor)}. + */ + public static final int REPORTING_MODE_ONE_SHOT = 2; - // Events are reported only when the value changes. - static int REPORTING_MODE_ON_CHANGE = 2; + /** + * Events are reported as described in the description of the sensor. The rate passed to + * registerListener might not have an impact on the rate of event delivery. See the sensor + * definition for more information on when and how frequently the events are reported. For + * example, step detectors report events when a step is detected. + * + * @see SensorManager#registerListener(SensorEventListener, Sensor, int, int) + */ + public static final int REPORTING_MODE_SPECIAL_TRIGGER = 3; - // Upon detection of an event, the sensor deactivates itself and then sends a single event. - static int REPORTING_MODE_ONE_SHOT = 3; + // Mask for the LSB 2nd, 3rd and fourth bits. + private static final int REPORTING_MODE_MASK = 0xE; + private static final int REPORTING_MODE_SHIFT = 1; // TODO(): The following arrays are fragile and error-prone. This needs to be refactored. @@ -445,58 +803,74 @@ public final class Sensor { // associated with // {@link SensorEvent} or {@link TriggerEvent} for the Sensor private static final int[] sSensorReportingModes = { - 0, 0, // padding because sensor types start at 1 - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ACCELEROMETER - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GEOMAGNETIC_FIELD - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ORIENTATION - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GYROSCOPE - REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_LIGHT - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_PRESSURE - REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_TEMPERATURE - REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_PROXIMITY - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GRAVITY - REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_LINEAR_ACCELERATION - REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_ROTATION_VECTOR - REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_RELATIVE_HUMIDITY - REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_AMBIENT_TEMPERATURE - REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED - REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_GAME_ROTATION_VECTOR - REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED - REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_SIGNIFICANT_MOTION - // added post 4.3 - REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR - REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER - REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR - REPORTING_MODE_ON_CHANGE, 1 // SENSOR_TYPE_HEART_RATE_MONITOR + 0, // padding because sensor types start at 1 + 3, // SENSOR_TYPE_ACCELEROMETER + 3, // SENSOR_TYPE_GEOMAGNETIC_FIELD + 3, // SENSOR_TYPE_ORIENTATION + 3, // SENSOR_TYPE_GYROSCOPE + 3, // SENSOR_TYPE_LIGHT + 3, // SENSOR_TYPE_PRESSURE + 3, // SENSOR_TYPE_TEMPERATURE + 3, // SENSOR_TYPE_PROXIMITY + 3, // SENSOR_TYPE_GRAVITY + 3, // SENSOR_TYPE_LINEAR_ACCELERATION + 5, // SENSOR_TYPE_ROTATION_VECTOR + 3, // SENSOR_TYPE_RELATIVE_HUMIDITY + 3, // SENSOR_TYPE_AMBIENT_TEMPERATURE + 6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED + 4, // SENSOR_TYPE_GAME_ROTATION_VECTOR + 6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED + 1, // SENSOR_TYPE_SIGNIFICANT_MOTION + 1, // SENSOR_TYPE_STEP_DETECTOR + 1, // SENSOR_TYPE_STEP_COUNTER + 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR + 1, // SENSOR_TYPE_HEART_RATE_MONITOR + 3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR + // wake up variants of base sensors + 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER + 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD + 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION + 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE + 3, // SENSOR_TYPE_WAKE_UP_LIGHT + 3, // SENSOR_TYPE_WAKE_UP_PRESSURE + 3, // SENSOR_TYPE_WAKE_UP_GRAVITY + 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION + 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR + 3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY + 3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE + 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED + 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR + 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED + 1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR + 1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER + 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR + 1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR + 1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR + 1, // SENSOR_TYPE_WAKE_GESTURE }; - static int getReportingMode(Sensor sensor) { - int offset = sensor.mType * 2; - if (offset >= sSensorReportingModes.length) { - // we don't know about this sensor, so this is probably a - // vendor-defined sensor, in that case, we figure out the reporting - // mode from the sensor meta-data. - int minDelay = sensor.mMinDelay; - if (minDelay == 0) { - return REPORTING_MODE_ON_CHANGE; - } else if (minDelay < 0) { - return REPORTING_MODE_ONE_SHOT; - } else { - return REPORTING_MODE_CONTINUOUS; - } - } - return sSensorReportingModes[offset]; + /** + * Each sensor has exactly one reporting mode associated with it. This method returns the + * reporting mode constant for this sensor type. + * + * @return Reporting mode for the input sensor, one of REPORTING_MODE_* constants. + * @see #REPORTING_MODE_CONTINUOUS + * @see #REPORTING_MODE_ON_CHANGE + * @see #REPORTING_MODE_ONE_SHOT + * @see #REPORTING_MODE_SPECIAL_TRIGGER + */ + public int getReportingMode() { + return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT); } static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) { - int type = sensor.mType; // RotationVector length has changed to 3 to 5 for API level 18 // Set it to 3 for backward compatibility. - if (type == Sensor.TYPE_ROTATION_VECTOR && + if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) { return 3; } - int offset = type * 2 + 1; + int offset = sensor.mType; if (offset >= sSensorReportingModes.length) { // we don't know about this sensor, so this is probably a // vendor-defined sensor, in that case, we don't know how many value @@ -525,6 +899,8 @@ public final class Sensor { private int mFifoMaxEventCount; private String mStringType; private String mRequiredPermission; + private int mMaxDelay; + private int mFlags; Sensor() { } @@ -625,6 +1001,51 @@ public final class Sensor { return mHandle; } + /** + * This value is defined only for continuous mode sensors. It is the delay between two + * sensor events corresponding to the lowest frequency that this sensor supports. When + * lower frequencies are requested through registerListener() the events will be generated + * at this frequency instead. It can be used to estimate when the batch FIFO may be full. + * Older devices may set this value to zero. Ignore this value in case it is negative + * or zero. + * + * @return The max delay for this sensor in microseconds. + */ + public int getMaxDelay() { + return mMaxDelay; + } + + /** + * Returns whether this sensor is a wake-up sensor. + * <p> + * Wake up sensors wake the application processor up when they have events to deliver. When a + * wake up sensor is registered to without batching enabled, each event will wake the + * application processor up. + * <p> + * When a wake up sensor is registered to with batching enabled, it + * wakes the application processor up when maxReportingLatency has elapsed or when the hardware + * FIFO storing the events from wake up sensors is getting full. + * <p> + * Non-wake up sensors never wake the application processor up. Their events are only reported + * when the application processor is awake, for example because the application holds a wake + * lock, or another source woke the application processor up. + * <p> + * When a non-wake up sensor is registered to without batching enabled, the measurements made + * while the application processor is asleep might be lost and never returned. + * <p> + * When a non-wake up sensor is registered to with batching enabled, the measurements made while + * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors. + * When this FIFO gets full, new events start overwriting older events. When the application + * then wakes up, the latest events are returned, and some old events might be lost. The number + * of events actually returned depends on the hardware FIFO size, as well as on what other + * sensors are activated. If losing sensor events is not acceptable during batching, you must + * use the wake-up version of the sensor. + * @return true if this is a wake up sensor, false otherwise. + */ + public boolean isWakeUpSensor() { + return (mFlags & SENSOR_FLAG_WAKE_UP_SENSOR) != 0; + } + void setRange(float max, float res) { mMaxRange = max; mResolution = res; diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java index 677d244..0d859fb 100644 --- a/core/java/android/hardware/SensorEventListener.java +++ b/core/java/android/hardware/SensorEventListener.java @@ -39,11 +39,13 @@ public interface SensorEventListener { public void onSensorChanged(SensorEvent event); /** - * Called when the accuracy of a sensor has changed. - * <p>See {@link android.hardware.SensorManager SensorManager} - * for details. + * Called when the accuracy of the registered sensor has changed. + * + * <p>See the SENSOR_STATUS_* constants in + * {@link android.hardware.SensorManager SensorManager} for details. * - * @param accuracy The new accuracy of this sensor + * @param accuracy The new accuracy of this sensor, one of + * {@code SensorManager.SENSOR_STATUS_*} */ - public void onAccuracyChanged(Sensor sensor, int accuracy); + public void onAccuracyChanged(Sensor sensor, int accuracy); } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 5f2b5f0..25c7630 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -321,6 +321,13 @@ public abstract class SensorManager { /** + * The values returned by this sensor cannot be trusted because the sensor + * had no contact with what it was measuring (for example, the heart rate + * monitor is not in contact with the user). + */ + public static final int SENSOR_STATUS_NO_CONTACT = -1; + + /** * The values returned by this sensor cannot be trusted, calibration is * needed or the environment doesn't allow readings */ @@ -421,9 +428,10 @@ public abstract class SensorManager { * {@link SensorManager#getSensorList(int) getSensorList}. * * @param type - * of sensors requested + * of sensors requested * - * @return the default sensors matching the asked type. + * @return the default sensor matching the requested type if one exists and the application + * has the necessary permissions, or null otherwise. * * @see #getSensorList(int) * @see Sensor diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 8684a04..a6c3ea4 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -99,7 +99,7 @@ public class SystemSensorManager extends SensorManager { return false; } // Trigger Sensors should use the requestTriggerSensor call. - if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) { + if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) { Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor."); return false; } @@ -133,7 +133,7 @@ public class SystemSensorManager extends SensorManager { @Override protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) { // Trigger Sensors should use the cancelTriggerSensor call. - if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) { + if (sensor != null && sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) { return; } @@ -159,7 +159,7 @@ public class SystemSensorManager extends SensorManager { protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) { if (sensor == null) throw new IllegalArgumentException("sensor cannot be null"); - if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false; + if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false; synchronized (mTriggerListeners) { TriggerEventQueue queue = mTriggerListeners.get(listener); @@ -181,7 +181,7 @@ public class SystemSensorManager extends SensorManager { @Override protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor, boolean disable) { - if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) { + if (sensor != null && sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) { return false; } synchronized (mTriggerListeners) { @@ -395,25 +395,12 @@ public class SystemSensorManager extends SensorManager { t.timestamp = timestamp; t.accuracy = inAccuracy; t.sensor = sensor; - switch (t.sensor.getType()) { - // Only report accuracy for sensors that support it. - case Sensor.TYPE_MAGNETIC_FIELD: - case Sensor.TYPE_ORIENTATION: - // call onAccuracyChanged() only if the value changes - final int accuracy = mSensorAccuracies.get(handle); - if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { - mSensorAccuracies.put(handle, t.accuracy); - mListener.onAccuracyChanged(t.sensor, t.accuracy); - } - break; - default: - // For other sensors, just report the accuracy once - if (mFirstEvent.get(handle) == false) { - mFirstEvent.put(handle, true); - mListener.onAccuracyChanged( - t.sensor, SENSOR_STATUS_ACCURACY_HIGH); - } - break; + + // call onAccuracyChanged() only if the value changes + final int accuracy = mSensorAccuracies.get(handle); + if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { + mSensorAccuracies.put(handle, t.accuracy); + mListener.onAccuracyChanged(t.sensor, t.accuracy); } mListener.onSensorChanged(t); } diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java index 1af575f..ca71e81 100644 --- a/core/java/android/hardware/camera2/CameraAccessException.java +++ b/core/java/android/hardware/camera2/CameraAccessException.java @@ -114,7 +114,10 @@ public class CameraAccessException extends AndroidException { mReason = problem; } - private static String getDefaultMessage(int problem) { + /** + * @hide + */ + public static String getDefaultMessage(int problem) { switch (problem) { case CAMERA_IN_USE: return "The camera device is in use already"; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 9eea545..e2f88eb 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -579,6 +579,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * of the lens that can be focused correctly.</p> * <p>If the lens is fixed-focus, this should be * 0.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> */ public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE = new Key<Float>("android.lens.info.minimumFocusDistance", float.class); @@ -1423,6 +1424,18 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.sensor.info.whiteLevel", int.class); /** + * <p>The sensor timestamp calibration quality.</p> + * <p>The sensor timestamp calibration quality determines the reliability of + * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} provided by the camera device.</p> + * + * @see CaptureResult#SENSOR_TIMESTAMP + * @see #SENSOR_INFO_TIMESTAMP_CALIBRATION_UNCALIBRATED + * @see #SENSOR_INFO_TIMESTAMP_CALIBRATION_CALIBRATED + */ + public static final Key<Integer> SENSOR_INFO_TIMESTAMP_CALIBRATION = + new Key<Integer>("android.sensor.info.timestampCalibration", int.class); + + /** * <p>The standard reference illuminant used as the scene light source when * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}, * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index a69a813..9a3d806 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -19,8 +19,10 @@ package android.hardware.camera2; import android.content.Context; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; +import android.hardware.CameraInfo; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.legacy.CameraDeviceUserShim; +import android.hardware.camera2.legacy.LegacyMetadataMapper; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.BinderHolder; @@ -57,6 +59,10 @@ public final class CameraManager { private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; private static final int USE_CALLING_UID = -1; + @SuppressWarnings("unused") + private static final int API_VERSION_1 = 1; + private static final int API_VERSION_2 = 2; + private final ICameraService mCameraService; private ArrayList<String> mDeviceIdList; @@ -84,9 +90,8 @@ public final class CameraManager { try { CameraBinderDecorator.throwOnError( CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); - } catch(CameraRuntimeException e) { - throw new IllegalStateException("Failed to setup camera vendor tags", - e.asChecked()); + } catch (CameraRuntimeException e) { + handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); } try { @@ -143,6 +148,9 @@ public final class CameraManager { synchronized (mLock) { mListenerMap.put(listener, handler); + + // TODO: fire the current oldest known state when adding a new listener + // (must be done while holding lock) } } @@ -186,16 +194,46 @@ public final class CameraManager { } } - CameraMetadataNative info = new CameraMetadataNative(); - try { - mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info); - } catch(CameraRuntimeException e) { - throw e.asChecked(); - } catch(RemoteException e) { - // impossible - return null; + int id = Integer.valueOf(cameraId); + + /* + * Get the camera characteristics from the camera service directly if it supports it, + * otherwise get them from the legacy shim instead. + */ + + if (!supportsCamera2Api(cameraId)) { + // Legacy backwards compatibility path; build static info from the camera parameters + String[] outParameters = new String[1]; + try { + mCameraService.getLegacyParameters(id, /*out*/outParameters); + String parameters = outParameters[0]; + + CameraInfo info = new CameraInfo(); + mCameraService.getCameraInfo(id, /*out*/info); + + return LegacyMetadataMapper.createCharacteristics(parameters, info); + } catch (RemoteException e) { + // Impossible + return null; + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } + + } else { + // Normal path: Get the camera characteristics directly from the camera service + CameraMetadataNative info = new CameraMetadataNative(); + + try { + mCameraService.getCameraCharacteristics(id, info); + } catch(CameraRuntimeException e) { + throw e.asChecked(); + } catch(RemoteException e) { + // impossible + return null; + } + + return new CameraCharacteristics(info); } - return new CameraCharacteristics(info); } /** @@ -240,11 +278,20 @@ public final class CameraManager { ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); int id = Integer.parseInt(cameraId); try { - mCameraService.connectDevice(callbacks, id, mContext.getPackageName(), - USE_CALLING_UID, holder); - cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + if (supportsCamera2Api(cameraId)) { + // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices + mCameraService.connectDevice(callbacks, id, mContext.getPackageName(), + USE_CALLING_UID, holder); + cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + } else { + // Use legacy camera implementation for HAL1 devices + Log.i(TAG, "Using legacy camera HAL."); + cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); + } } catch (CameraRuntimeException e) { - if (e.getReason() == CameraAccessException.CAMERA_IN_USE || + if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { + throw new AssertionError("Should've gone down the shim path"); + } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE || e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE || e.getReason() == CameraAccessException.CAMERA_DISABLED || e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || @@ -441,6 +488,65 @@ public final class CameraManager { return mDeviceIdList; } + private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { + int problem = e.getReason(); + switch (problem) { + case CameraAccessException.CAMERA_DISCONNECTED: + String errorMsg = CameraAccessException.getDefaultMessage(problem); + Log.w(TAG, msg + ": " + errorMsg); + break; + default: + throw new IllegalStateException(msg, e.asChecked()); + } + } + + /** + * Queries the camera service if it supports the camera2 api directly, or needs a shim. + * + * @param cameraId a non-{@code null} camera identifier + * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise. + */ + private boolean supportsCamera2Api(String cameraId) { + return supportsCameraApi(cameraId, API_VERSION_2); + } + + /** + * Queries the camera service if it supports a camera api directly, or needs a shim. + * + * @param cameraId a non-{@code null} camera identifier + * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2} + * @return {@code true} if connecting will work for that device version. + */ + private boolean supportsCameraApi(String cameraId, int apiVersion) { + int id = Integer.parseInt(cameraId); + + /* + * Possible return values: + * - NO_ERROR => Camera2 API is supported + * - CAMERA_DEPRECATED_HAL => Camera2 API is *not* supported (thrown as an exception) + * + * Anything else is an unexpected error we don't want to recover from. + */ + + try { + int res = mCameraService.supportsCameraApi(id, apiVersion); + + if (res != CameraBinderDecorator.NO_ERROR) { + throw new AssertionError("Unexpected value " + res); + } + + return true; + } catch (CameraRuntimeException e) { + if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { + return false; + } else { + throw e; + } + } catch (RemoteException e) { + throw new AssertionError("Camera service unreachable", e); + } + } + // TODO: this class needs unit tests // TODO: extract class into top level private class CameraServiceListener extends ICameraServiceListener.Stub { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 33e1915..dad1854 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -248,7 +248,6 @@ public abstract class CameraMetadata<TKey> { * <li>Manual frame duration control<ul> * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li> * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li> - * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li> * </ul> * </li> * <li>Manual exposure control<ul> @@ -279,6 +278,9 @@ public abstract class CameraMetadata<TKey> { * result.</p> * <p>A given camera device may also support additional manual sensor controls, * but this capability only covers the above list of controls.</p> + * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will + * additionally return a min frame duration that is greater than + * zero for each supported size-format combination.</p> * * @see CaptureRequest#BLACK_LEVEL_LOCK * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP @@ -419,6 +421,33 @@ public abstract class CameraMetadata<TKey> { public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4; // + // Enumeration values for CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION + // + + /** + * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, + * but can not be compared to timestamps from other subsystems + * (e.g. accelerometer, gyro etc.), or other instances of the same or different + * camera devices in the same system. Timestamps between streams and results for + * a single camera instance are comparable, and the timestamps for all buffers + * and the result metadata generated by a single capture are identical.</p> + * + * @see CaptureResult#SENSOR_TIMESTAMP + * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION + */ + public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_UNCALIBRATED = 0; + + /** + * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as + * android.os.SystemClock#elapsedRealtimeNanos(), + * and they can be compared to other timestamps using that base.</p> + * + * @see CaptureResult#SENSOR_TIMESTAMP + * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION + */ + public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_CALIBRATED = 1; + + // // Enumeration values for CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index bf7bd37..5bc59dc 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -18,6 +18,7 @@ package android.hardware.camera2; import android.hardware.camera2.CameraCharacteristics.Key; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.TypeReference; import android.os.Parcel; import android.os.Parcelable; @@ -280,7 +281,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> @Override public int hashCode() { - return mSettings.hashCode(); + return HashCodeHelpers.hashCode(mSettings, mSurfaceSet, mUserTag); } public static final Parcelable.Creator<CaptureRequest> CREATOR = @@ -1020,9 +1021,17 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Video stabilization automatically translates and scales images from the camera * in order to stabilize motion between consecutive frames.</p> * <p>If enabled, video stabilization can modify the - * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream - * stabilized</p> + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p> + * <p>Switching between different video stabilization modes may take several frames + * to initialize, the camera device will report the current mode in capture result + * metadata. For example, When "ON" mode is requested, the video stabilization modes + * in the first several capture results may still be "OFF", and it will become "ON" + * when the initialization is done.</p> + * <p>If a camera device supports both this mode and OIS ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), + * turning both modes on may produce undesirable interaction, so it is recommended not to + * enable both at the same time.</p> * + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON @@ -1258,6 +1267,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * makes use of mechanical elements to stabilize the camera * sensor, and thus allows for longer exposure times before * camera shake becomes apparent.</p> + * <p>Switching between different optical stabilization modes may take several + * frames to initialize, the camera device will report the current mode in + * capture result metadata. For example, When "ON" mode is requested, the + * optical stabilization modes in the first several capture results may still + * be "OFF", and it will become "ON" when the initialization is done.</p> + * <p>If a camera device supports both OIS and EIS ({@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode}), + * turning both modes on may produce undesirable interaction, so it is recommended not + * to enable both at the same time.</p> * <p>Not all devices will support OIS; see * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION android.lens.info.availableOpticalStabilization} for * available controls.</p> diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 3d17ed3..9097220 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -1591,9 +1591,17 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Video stabilization automatically translates and scales images from the camera * in order to stabilize motion between consecutive frames.</p> * <p>If enabled, video stabilization can modify the - * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream - * stabilized</p> - * + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p> + * <p>Switching between different video stabilization modes may take several frames + * to initialize, the camera device will report the current mode in capture result + * metadata. For example, When "ON" mode is requested, the video stabilization modes + * in the first several capture results may still be "OFF", and it will become "ON" + * when the initialization is done.</p> + * <p>If a camera device supports both this mode and OIS ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), + * turning both modes on may produce undesirable interaction, so it is recommended not to + * enable both at the same time.</p> + * + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON @@ -1846,6 +1854,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * makes use of mechanical elements to stabilize the camera * sensor, and thus allows for longer exposure times before * camera shake becomes apparent.</p> + * <p>Switching between different optical stabilization modes may take several + * frames to initialize, the camera device will report the current mode in + * capture result metadata. For example, When "ON" mode is requested, the + * optical stabilization modes in the first several capture results may still + * be "OFF", and it will become "ON" when the initialization is done.</p> + * <p>If a camera device supports both OIS and EIS ({@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode}), + * turning both modes on may produce undesirable interaction, so it is recommended not + * to enable both at the same time.</p> * <p>Not all devices will support OIS; see * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION android.lens.info.availableOpticalStabilization} for * available controls.</p> @@ -2114,15 +2130,22 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Time at start of exposure of first - * row of the image sensor, in nanoseconds.</p> + * row of the image sensor active array, in nanoseconds.</p> * <p>The timestamps are also included in all image * buffers produced for the same capture, and will be identical - * on all the outputs. The timestamps measure time since an - * unspecified starting point, and are monotonically - * increasing.</p> - * <p>They can be compared with the timestamps for other captures - * from the same camera device, but are not guaranteed to be - * comparable to any other time source.</p> + * on all the outputs.</p> + * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION android.sensor.info.timestampCalibration} <code>==</code> UNCALIBRATED, + * the timestamps measure time since an unspecified starting point, + * and are monotonically increasing. They can be compared with the + * timestamps for other captures from the same camera device, but are + * not guaranteed to be comparable to any other time source.</p> + * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION android.sensor.info.timestampCalibration} <code>==</code> CALIBRATED, + * the timestamps measure time in the same timebase as + * android.os.SystemClock#elapsedRealtimeNanos(), and they can be + * compared to other timestamps from other subsystems that are using + * that base.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION */ public static final Key<Long> SENSOR_TIMESTAMP = new Key<Long>("android.sensor.timestamp", long.class); @@ -2215,6 +2238,20 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.sensor.testPatternMode", int.class); /** + * <p>Duration between the start of first row exposure + * and the start of last row exposure.</p> + * <p>This is the exposure time skew (in the unit of nanosecond) between the first and + * last row exposure start times. The first row and the last row are the first + * and last rows inside of the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p> + * <p>For typical camera sensors that use rolling shutters, this is also equivalent + * to the frame readout time.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + */ + public static final Key<Long> SENSOR_ROLLING_SHUTTER_SKEW = + new Key<Long>("android.sensor.rollingShutterSkew", long.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 diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index d9f3af4..2e59eee 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -28,6 +28,8 @@ import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.utils.CloseableLock; +import android.hardware.camera2.utils.CloseableLock.ScopedLock; import android.hardware.camera2.utils.LongParcelable; import android.os.Handler; import android.os.IBinder; @@ -57,13 +59,14 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) private ICameraDeviceUser mRemoteDevice; - private final Object mLock = new Object(); + private final CloseableLock mCloseLock; private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); private final StateListener mDeviceListener; private volatile StateListener mSessionStateListener; private final Handler mDeviceHandler; + private volatile boolean mClosing = false; private boolean mInError = false; private boolean mIdle = true; @@ -100,7 +103,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final Runnable mCallOnOpened = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onOpened(CameraDeviceImpl.this); @@ -113,7 +118,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final Runnable mCallOnUnconfigured = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onUnconfigured(CameraDeviceImpl.this); @@ -126,7 +133,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final Runnable mCallOnActive = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onActive(CameraDeviceImpl.this); @@ -139,7 +148,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final Runnable mCallOnBusy = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onBusy(CameraDeviceImpl.this); @@ -150,20 +161,29 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { }; private final Runnable mCallOnClosed = new Runnable() { + private boolean mClosedOnce = false; + @Override public void run() { + if (mClosedOnce) { + throw new AssertionError("Don't post #onClosed more than once"); + } + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onClosed(CameraDeviceImpl.this); } mDeviceListener.onClosed(CameraDeviceImpl.this); + mClosedOnce = true; } }; private final Runnable mCallOnIdle = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onIdle(CameraDeviceImpl.this); @@ -176,7 +196,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final Runnable mCallOnDisconnected = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { sessionListener.onDisconnected(CameraDeviceImpl.this); @@ -195,6 +217,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { mDeviceListener = listener; mDeviceHandler = handler; mCharacteristics = characteristics; + mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId); final int MAX_TAG_LEN = 23; String tag = String.format("CameraDevice-JV-%s", mCameraId); @@ -210,8 +233,8 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } public void setRemoteDevice(ICameraDeviceUser remoteDevice) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { // TODO: Move from decorator to direct binder-mediated exceptions - synchronized(mLock) { // If setRemoteFailure already called, do nothing if (mInError) return; @@ -254,7 +277,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } final int code = failureCode; final boolean isError = failureIsError; - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed, can't go to error state + mInError = true; mDeviceHandler.post(new Runnable() { @Override @@ -280,7 +305,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { if (outputs == null) { outputs = new ArrayList<Surface>(); } - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create @@ -344,7 +369,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { public void createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateListener listener, Handler handler) throws CameraAccessException { - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { if (DEBUG) { Log.d(TAG, "createCaptureSession"); } @@ -389,7 +414,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); CameraMetadataNative templatedRequest = new CameraMetadataNative(); @@ -435,7 +460,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { * starting and stopping repeating request and flushing. * * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never - * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered. + * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair * is added to the list mFrameNumberRequestPairs.</p> * @@ -446,7 +471,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private void checkEarlyTriggerSequenceComplete( final int requestId, final long lastFrameNumber) { // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request - // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately. + // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately. if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) { final CaptureListenerHolder holder; int index = mCaptureListenerMap.indexOfKey(requestId); @@ -463,7 +488,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { if (holder != null) { if (DEBUG) { - Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because" + Log.v(TAG, "immediately trigger onCaptureSequenceAborted because" + " request did not reach HAL"); } @@ -480,10 +505,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { || lastFrameNumber > Integer.MAX_VALUE) { throw new AssertionError(lastFrameNumber + " cannot be cast to int"); } - holder.getListener().onCaptureSequenceCompleted( + holder.getListener().onCaptureSequenceAborted( CameraDeviceImpl.this, - requestId, - lastFrameNumber); + requestId); } } }; @@ -509,7 +533,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { handler = checkHandler(handler); } - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); int requestId; @@ -581,7 +605,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public void stopRepeating() throws CameraAccessException { - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { @@ -612,7 +636,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private void waitUntilIdle() throws CameraAccessException { - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { throw new IllegalStateException("Active repeating request ongoing"); @@ -633,7 +657,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public void flush() throws CameraAccessException { - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) { checkIfCameraClosedOrInError(); mDeviceHandler.post(mCallOnBusy); @@ -656,8 +680,15 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public void close() { - synchronized (mLock) { + mClosing = true; + // Acquire exclusive lock, close, release (idempotent) + mCloseLock.close(); + /* + * The rest of this is safe, since no other methods will be able to execute + * (they will throw ISE instead; the callbacks will get dropped) + */ + { try { if (mRemoteDevice != null) { mRemoteDevice.disconnect(); @@ -805,7 +836,12 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { // remove request from mCaptureListenerMap final int requestId = frameNumberRequestPair.getValue(); final CaptureListenerHolder holder; - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; + } + int index = mCaptureListenerMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null; @@ -890,9 +926,12 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) { Runnable r = null; - if (isClosed()) return; - synchronized(mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) { + return; // Camera already closed + } + mInError = true; switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: @@ -914,25 +953,24 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { break; } CameraDeviceImpl.this.mDeviceHandler.post(r); - } - // Fire onCaptureSequenceCompleted - if (DEBUG) { - Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber())); + // Fire onCaptureSequenceCompleted + if (DEBUG) { + Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber())); + } + mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true); + checkAndFireSequenceComplete(); } - mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true); - checkAndFireSequenceComplete(); - } @Override public void onCameraIdle() { - if (isClosed()) return; - if (DEBUG) { Log.d(TAG, "Camera now idle"); } - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + if (!CameraDeviceImpl.this.mIdle) { CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle); } @@ -948,30 +986,33 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } final CaptureListenerHolder holder; - // Get the listener for this frame ID, if there is one - synchronized (mLock) { + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed + + // Get the listener for this frame ID, if there is one holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); - } - if (holder == null) { - return; - } + if (holder == null) { + return; + } - if (isClosed()) return; + if (isClosed()) return; - // Dispatch capture start notice - holder.getHandler().post( - new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()) { - holder.getListener().onCaptureStarted( - CameraDeviceImpl.this, - holder.getRequest(resultExtras.getSubsequenceId()), - timestamp); + // Dispatch capture start notice + holder.getHandler().post( + new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()) { + holder.getListener().onCaptureStarted( + CameraDeviceImpl.this, + holder.getRequest(resultExtras.getSubsequenceId()), + timestamp); + } } - } - }); + }); + + } } @Override @@ -984,88 +1025,91 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { + requestId); } + try (ScopedLock scopedLock = mCloseLock.acquireLock()) { + if (scopedLock == null) return; // Camera already closed - // TODO: Handle CameraCharacteristics access from CaptureResult correctly. - result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, - getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); + // TODO: Handle CameraCharacteristics access from CaptureResult correctly. + result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, + getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); - final CaptureListenerHolder holder; - synchronized (mLock) { - holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); - } + final CaptureListenerHolder holder = + CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); - Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); - boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial); + Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); + boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial); - // Update tracker (increment counter) when it's not a partial result. - if (!quirkIsPartialResult) { - mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false); - } + // Update tracker (increment counter) when it's not a partial result. + if (!quirkIsPartialResult) { + mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), + /*error*/false); + } - // Check if we have a listener for this - if (holder == null) { - if (DEBUG) { - Log.d(TAG, - "holder is null, early return at frame " - + resultExtras.getFrameNumber()); + // Check if we have a listener for this + if (holder == null) { + if (DEBUG) { + Log.d(TAG, + "holder is null, early return at frame " + + resultExtras.getFrameNumber()); + } + return; } - return; - } - if (isClosed()) { - if (DEBUG) { - Log.d(TAG, - "camera is closed, early return at frame " - + resultExtras.getFrameNumber()); + if (isClosed()) { + if (DEBUG) { + Log.d(TAG, + "camera is closed, early return at frame " + + resultExtras.getFrameNumber()); + } + return; } - return; - } - final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); + final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); - Runnable resultDispatch = null; + Runnable resultDispatch = null; - // Either send a partial result or the final capture completed result - if (quirkIsPartialResult) { - final CaptureResult resultAsCapture = - new CaptureResult(result, request, requestId); + // Either send a partial result or the final capture completed result + if (quirkIsPartialResult) { + final CaptureResult resultAsCapture = + new CaptureResult(result, request, requestId); - // Partial result - resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - holder.getListener().onCapturePartial( - CameraDeviceImpl.this, - request, - resultAsCapture); + // Partial result + resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + holder.getListener().onCapturePartial( + CameraDeviceImpl.this, + request, + resultAsCapture); + } } - } - }; - } else { - final TotalCaptureResult resultAsCapture = - new TotalCaptureResult(result, request, requestId); + }; + } else { + final TotalCaptureResult resultAsCapture = + new TotalCaptureResult(result, request, requestId); - // Final capture result - resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - holder.getListener().onCaptureCompleted( - CameraDeviceImpl.this, - request, - resultAsCapture); + // Final capture result + resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + holder.getListener().onCaptureCompleted( + CameraDeviceImpl.this, + request, + resultAsCapture); + } } - } - }; - } + }; + } - holder.getHandler().post(resultDispatch); + holder.getHandler().post(resultDispatch); + + // Fire onCaptureSequenceCompleted + if (!quirkIsPartialResult) { + checkAndFireSequenceComplete(); + } - // Fire onCaptureSequenceCompleted - if (!quirkIsPartialResult) { - checkAndFireSequenceComplete(); } } @@ -1101,10 +1145,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } } + /** Whether the camera device has started to close (may not yet have finished) */ private boolean isClosed() { - synchronized(mLock) { - return (mRemoteDevice == null); - } + return mClosing; } private CameraCharacteristics getCharacteristics() { diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java index 22ff9c6..ab7e844 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java @@ -34,6 +34,7 @@ import android.util.Log; * <li>{@code CONFIGURING -> IDLE}</li> * <li>{@code IDLE -> CONFIGURING}</li> * <li>{@code IDLE -> CAPTURING}</li> + * <li>{@code IDLE -> IDLE}</li> * <li>{@code CAPTURING -> IDLE}</li> * <li>{@code ANY -> ERROR}</li> * </ul> @@ -216,12 +217,17 @@ public class CameraDeviceState { mCurrentState = STATE_CONFIGURING; break; case STATE_IDLE: + if (mCurrentState == STATE_IDLE) { + break; + } + if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) { Log.e(TAG, "Cannot call idle while in state: " + mCurrentState); mCurrentError = CameraBinderDecorator.INVALID_OPERATION; doStateTransition(STATE_ERROR); break; } + if (mCurrentState != STATE_IDLE && mCurrentHandler != null && mCurrentListener != null) { mCurrentHandler.post(new Runnable() { diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 54d9c3c..03dd354 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -17,7 +17,9 @@ package android.hardware.camera2.legacy; import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; @@ -25,7 +27,9 @@ import android.hardware.camera2.utils.LongParcelable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; +import android.os.ConditionVariable; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; @@ -33,7 +37,6 @@ import android.view.Surface; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; /** * Compatibility implementation of the Camera2 API binder interface. @@ -53,6 +56,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { private static final String TAG = "CameraDeviceUserShim"; private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG); + private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout) private final LegacyCameraDevice mLegacyDevice; @@ -60,31 +64,143 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { private int mSurfaceIdCounter; private boolean mConfiguring; private final SparseArray<Surface> mSurfaces; + private final CameraCharacteristics mCameraCharacteristics; + private final CameraLooper mCameraInit; - protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) { + protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera, + CameraCharacteristics characteristics, CameraLooper cameraInit) { mLegacyDevice = legacyCamera; mConfiguring = false; mSurfaces = new SparseArray<Surface>(); + mCameraCharacteristics = characteristics; + mCameraInit = cameraInit; mSurfaceIdCounter = 0; } + /** + * Create a separate looper/thread for the camera to run on; open the camera. + * + * <p>Since the camera automatically latches on to the current thread's looper, + * it's important that we have our own thread with our own looper to guarantee + * that the camera callbacks get correctly posted to our own thread.</p> + */ + private static class CameraLooper implements Runnable, AutoCloseable { + private final int mCameraId; + private Looper mLooper; + private volatile int mInitErrors; + private final Camera mCamera = Camera.openUninitialized(); + private final ConditionVariable mStartDone = new ConditionVariable(); + private final Thread mThread; + + /** + * Spin up a new thread, immediately open the camera in the background. + * + * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p> + * + * @param cameraId numeric camera Id + * + * @see #waitForOpen + */ + public CameraLooper(int cameraId) { + mCameraId = cameraId; + + mThread = new Thread(this); + mThread.start(); + } + + public Camera getCamera() { + return mCamera; + } + + @Override + public void run() { + // Set up a looper to be used by camera. + Looper.prepare(); + + // Save the looper so that we can terminate this thread + // after we are done with it. + mLooper = Looper.myLooper(); + mInitErrors = mCamera.cameraInitUnspecified(mCameraId); + + mStartDone.open(); + Looper.loop(); // Blocks forever until #close is called. + } + + /** + * Quit the looper safely; then join until the thread shuts down. + */ + @Override + public void close() { + if (mLooper == null) { + return; + } + + mLooper.quitSafely(); + try { + mThread.join(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + + mLooper = null; + } + + /** + * Block until the camera opens; then return its initialization error code (if any). + * + * @param timeoutMs timeout in milliseconds + * + * @return int error code + * + * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR}) + */ + public int waitForOpen(int timeoutMs) { + // Block until the camera is open asynchronously + if (!mStartDone.block(timeoutMs)) { + Log.e(TAG, "waitForOpen - Camera failed to open after timeout of " + + OPEN_CAMERA_TIMEOUT_MS + " ms"); + try { + mCamera.release(); + } catch (RuntimeException e) { + Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e); + } + + throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR); + } + + return mInitErrors; + } + } + public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks, int cameraId) { if (DEBUG) { Log.d(TAG, "Opening shim Camera device"); } - // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async. - Camera legacyCamera = Camera.openUninitialized(); - int initErrors = legacyCamera.cameraInit(cameraId); + + /* + * Put the camera open on a separate thread with its own looper; otherwise + * if the main thread is used then the callbacks might never get delivered + * (e.g. in CTS which run its own default looper only after tests) + */ + + CameraLooper init = new CameraLooper(cameraId); + + // TODO: Make this async instead of blocking + int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS); + Camera legacyCamera = init.getCamera(); + // Check errors old HAL initialization - if (Camera.checkInitErrors(initErrors)) { - // TODO: Map over old camera error codes. This likely involves improving the error - // reporting in the HAL1 connect path. - throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED); - } + CameraBinderDecorator.throwOnError(initErrors); + + CameraInfo info = new CameraInfo(); + Camera.getCameraInfo(cameraId, info); + + CameraCharacteristics characteristics = + LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info); LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks); - return new CameraDeviceUserShim(cameraId, device); + return new CameraDeviceUserShim(cameraId, device, characteristics, init); } @Override @@ -92,7 +208,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { if (DEBUG) { Log.d(TAG, "disconnect called."); } - mLegacyDevice.close(); + + try { + mLegacyDevice.close(); + } finally { + mCameraInit.close(); + } } @Override @@ -169,7 +290,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } int numSurfaces = mSurfaces.size(); if (numSurfaces > 0) { - surfaces = new ArrayList<Surface>(); + surfaces = new ArrayList<>(); for (int i = 0; i < numSurfaces; ++i) { surfaces.add(mSurfaces.valueAt(i)); } @@ -220,8 +341,17 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { if (DEBUG) { Log.d(TAG, "createDefaultRequest called."); } - // TODO: implement createDefaultRequest. - Log.e(TAG, "createDefaultRequest unimplemented."); + + CameraMetadataNative template; + try { + template = + LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId); + } catch (IllegalArgumentException e) { + Log.e(TAG, "createDefaultRequest - invalid templateId specified"); + return CameraBinderDecorator.BAD_VALUE; + } + + request.swap(template); return CameraBinderDecorator.NO_ERROR; } diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java index 3fd2309..5d44fd2 100644 --- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java @@ -100,6 +100,7 @@ public class GLThreadManager { break; case MSG_ALLOW_FRAMES: mDroppingFrames = false; + break; default: Log.e(TAG, "Unhandled message " + msg.what + " on GLThread."); break; @@ -148,6 +149,12 @@ public class GLThreadManager { Handler handler = mGLHandlerThread.getHandler(); handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP)); mGLHandlerThread.quitSafely(); + try { + mGLHandlerThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.", + mGLHandlerThread.getName(), mGLHandlerThread.getId())); + } } /** diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index f9cf905..b6264dc 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -23,18 +23,20 @@ import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.utils.LongParcelable; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.view.Surface; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; + +import static android.hardware.camera2.utils.CameraBinderDecorator.*; +import static com.android.internal.util.Preconditions.*; /** * This class emulates the functionality of a Camera2 device using a the old Camera class. @@ -48,18 +50,20 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class LegacyCameraDevice implements AutoCloseable { public static final String DEBUG_PROP = "HAL1ShimLogging"; - private final String TAG; + private static final boolean DEBUG = false; private final int mCameraId; private final ICameraDeviceCallbacks mDeviceCallbacks; private final CameraDeviceState mDeviceState = new CameraDeviceState(); + private List<Surface> mConfiguredSurfaces; private final ConditionVariable mIdle = new ConditionVariable(/*open*/true); - private final AtomicInteger mRequestIdCounter = new AtomicInteger(0); - private final HandlerThread mCallbackHandlerThread = new HandlerThread("ResultThread"); + private final HandlerThread mResultThread = new HandlerThread("ResultThread"); + private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread"); private final Handler mCallbackHandler; + private final Handler mResultHandler; private static final int ILLEGAL_VALUE = -1; private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) { @@ -81,66 +85,108 @@ public class LegacyCameraDevice implements AutoCloseable { public void onError(final int errorCode, RequestHolder holder) { mIdle.open(); final CaptureResultExtras extras = getExtrasFromRequest(holder); - try { - mDeviceCallbacks.onCameraError(errorCode, extras); - } catch (RemoteException e) { - Log.e(TAG, "Received remote exception during onCameraError callback: ", e); - } - + mResultHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "doing onError callback."); + } + try { + mDeviceCallbacks.onCameraError(errorCode, extras); + } catch (RemoteException e) { + throw new IllegalStateException( + "Received remote exception during onCameraError callback: ", e); + } + } + }); } @Override public void onConfiguring() { // Do nothing + if (DEBUG) { + Log.d(TAG, "doing onConfiguring callback."); + } } @Override public void onIdle() { mIdle.open(); - try { - mDeviceCallbacks.onCameraIdle(); - } catch (RemoteException e) { - Log.e(TAG, "Received remote exception during onCameraIdle callback: ", e); - } + mResultHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "doing onIdle callback."); + } + try { + mDeviceCallbacks.onCameraIdle(); + } catch (RemoteException e) { + throw new IllegalStateException( + "Received remote exception during onCameraIdle callback: ", e); + } + } + }); } @Override public void onCaptureStarted(RequestHolder holder) { final CaptureResultExtras extras = getExtrasFromRequest(holder); - try { - // TODO: Don't fake timestamp - mDeviceCallbacks.onCaptureStarted(extras, System.nanoTime()); - } catch (RemoteException e) { - Log.e(TAG, "Received remote exception during onCameraError callback: ", e); - } - + final long timestamp = System.nanoTime(); + mResultHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "doing onCaptureStarted callback."); + } + try { + // TODO: Don't fake timestamp + mDeviceCallbacks.onCaptureStarted(extras, timestamp); + } catch (RemoteException e) { + throw new IllegalStateException( + "Received remote exception during onCameraError callback: ", e); + } + } + }); } @Override - public void onCaptureResult(CameraMetadataNative result, RequestHolder holder) { + public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) { final CaptureResultExtras extras = getExtrasFromRequest(holder); - try { - // TODO: Don't fake metadata - mDeviceCallbacks.onResultReceived(result, extras); - } catch (RemoteException e) { - Log.e(TAG, "Received remote exception during onCameraError callback: ", e); - } + mResultHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "doing onCaptureResult callback."); + } + try { + // TODO: Don't fake metadata + mDeviceCallbacks.onResultReceived(result, extras); + } catch (RemoteException e) { + throw new IllegalStateException( + "Received remote exception during onCameraError callback: ", e); + } + } + }); } }; private final RequestThreadManager mRequestThreadManager; /** - * Check if a given surface uses {@link ImageFormat#YUV_420_888} format. + * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily + * converted to this; YV12 and NV21 are the two currently supported formats. * * @param s the surface to check. - * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}. + * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible + * format. */ static boolean needsConversion(Surface s) { - return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888; + int nativeType = LegacyCameraDevice.nativeDetectSurfaceType(s); + return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 || + nativeType == ImageFormat.NV21; } /** @@ -161,6 +207,8 @@ public class LegacyCameraDevice implements AutoCloseable { mDeviceCallbacks = callbacks; TAG = String.format("CameraDevice-%d-LE", mCameraId); + mResultThread.start(); + mResultHandler = new Handler(mResultThread.getLooper()); mCallbackHandlerThread.start(); mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper()); mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener); @@ -172,16 +220,35 @@ public class LegacyCameraDevice implements AutoCloseable { /** * Configure the device with a set of output surfaces. * + * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p> + * + * <p>Every surface in {@code outputs} must be non-{@code null}.</p> + * * @param outputs a list of surfaces to set. - * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR} + * @return an error code for this binder operation, or {@link NO_ERROR} * on success. */ public int configureOutputs(List<Surface> outputs) { + if (outputs != null) { + for (Surface output : outputs) { + if (output == null) { + Log.e(TAG, "configureOutputs - null outputs are not allowed"); + return BAD_VALUE; + } + } + } + int error = mDeviceState.setConfiguring(); - if (error == CameraBinderDecorator.NO_ERROR) { + if (error == NO_ERROR) { mRequestThreadManager.configure(outputs); error = mDeviceState.setIdle(); } + + // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..) + if (error == NO_ERROR) { + mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null; + } + return error; } @@ -198,7 +265,35 @@ public class LegacyCameraDevice implements AutoCloseable { */ public int submitRequestList(List<CaptureRequest> requestList, boolean repeating, /*out*/LongParcelable frameNumber) { - // TODO: validate request here + if (requestList == null || requestList.isEmpty()) { + Log.e(TAG, "submitRequestList - Empty/null requests are not allowed"); + return BAD_VALUE; + } + + // Make sure that there all requests have at least 1 surface; all surfaces are non-null + for (CaptureRequest request : requestList) { + if (request.getTargets().isEmpty()) { + Log.e(TAG, "submitRequestList - " + + "Each request must have at least one Surface target"); + return BAD_VALUE; + } + + for (Surface surface : request.getTargets()) { + if (surface == null) { + Log.e(TAG, "submitRequestList - Null Surface targets are not allowed"); + return BAD_VALUE; + } else if (mConfiguredSurfaces == null) { + Log.e(TAG, "submitRequestList - must configure " + + " device with valid surfaces before submitting requests"); + return INVALID_OPERATION; + } else if (!mConfiguredSurfaces.contains(surface)) { + Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured"); + return BAD_VALUE; + } + } + } + + // TODO: further validation of request here mIdle.close(); return mRequestThreadManager.submitCaptureRequests(requestList, repeating, frameNumber); @@ -244,6 +339,22 @@ public class LegacyCameraDevice implements AutoCloseable { public void close() { mRequestThreadManager.quit(); mCallbackHandlerThread.quitSafely(); + mResultThread.quitSafely(); + + try { + mCallbackHandlerThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.", + mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId())); + } + + try { + mResultThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.", + mResultThread.getName(), mResultThread.getId())); + } + // TODO: throw IllegalStateException in every method after close has been called } @@ -258,9 +369,27 @@ public class LegacyCameraDevice implements AutoCloseable { } } + /** + * Query the surface for its currently configured default buffer size. + * @param surface a non-{@code null} {@code Surface} + * @return the width and height of the surface + * + * @throws NullPointerException if the {@code surface} was {@code null} + * @throws IllegalStateException if the {@code surface} was invalid + */ + static Size getSurfaceSize(Surface surface) { + checkNotNull(surface); + + int[] dimens = new int[2]; + nativeDetectSurfaceDimens(surface, /*out*/dimens); + + return new Size(dimens[0], dimens[1]); + } + protected static native int nativeDetectSurfaceType(Surface surface); - protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens); + protected static native void nativeDetectSurfaceDimens(Surface surface, + /*out*/int[/*2*/] dimens); protected static native void nativeConfigureSurface(Surface surface, int width, int height, int pixelFormat); diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java new file mode 100644 index 0000000..f702556 --- /dev/null +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -0,0 +1,623 @@ +/* + * 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.camera2.legacy; + +import android.graphics.ImageFormat; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Size; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.params.StreamConfiguration; +import android.hardware.camera2.params.StreamConfigurationDuration; +import android.util.Log; +import android.util.Range; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static com.android.internal.util.Preconditions.*; +import static android.hardware.camera2.CameraCharacteristics.*; + +/** + * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the + * camera characteristics. + */ +public class LegacyMetadataMapper { + private static final String TAG = "LegacyMetadataMapper"; + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + + // from graphics.h + private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22; + private static final int HAL_PIXEL_FORMAT_BLOB = 0x21; + + // for metadata + private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f; + + private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // ms + private static final long APPROXIMATE_SENSOR_AREA = (1 << 23); // 8mp + private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms + private static final long NS_PER_MS = 1000000; + + /* + * Development hijinks: Lie about not supporting certain capabilities + * + * - Unblock some CTS tests from running whose main intent is not the metadata itself + * + * TODO: Remove these constants and strip out any code that previously relied on them + * being set to true. + */ + private static final boolean LIE_ABOUT_FLASH = true; + private static final boolean LIE_ABOUT_AE = true; + private static final boolean LIE_ABOUT_AF = true; + private static final boolean LIE_ABOUT_AWB = true; + + /** + * Create characteristics for a legacy device by mapping the {@code parameters} + * and {@code info} + * + * @param parameters A non-{@code null} parameters set + * @param info Camera info with camera facing direction and angle of orientation + * + * @return static camera characteristics for a camera device + * + * @throws NullPointerException if any of the args were {@code null} + */ + public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters, + CameraInfo info) { + checkNotNull(parameters, "parameters must not be null"); + checkNotNull(info, "info must not be null"); + + String paramStr = parameters.flatten(); + android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo(); + outerInfo.info = info; + + return createCharacteristics(paramStr, outerInfo); + } + + /** + * Create characteristics for a legacy device by mapping the {@code parameters} + * and {@code info} + * + * @param parameters A string parseable by {@link Camera.Parameters#unflatten} + * @param info Camera info with camera facing direction and angle of orientation + * @return static camera characteristics for a camera device + * + * @throws NullPointerException if any of the args were {@code null} + */ + public static CameraCharacteristics createCharacteristics(String parameters, + android.hardware.CameraInfo info) { + checkNotNull(parameters, "parameters must not be null"); + checkNotNull(info, "info must not be null"); + checkNotNull(info.info, "info.info must not be null"); + + CameraMetadataNative m = new CameraMetadataNative(); + + mapCameraInfo(m, info.info); + + Camera.Parameters params = Camera.getEmptyParameters(); + params.unflatten(parameters); + mapCameraParameters(m, params); + + if (VERBOSE) { + Log.v(TAG, "createCharacteristics metadata:"); + Log.v(TAG, "--------------------------------------------------- (start)"); + m.dumpToLog(); + Log.v(TAG, "--------------------------------------------------- (end)"); + } + + return new CameraCharacteristics(m); + } + + private static void mapCameraInfo(CameraMetadataNative m, CameraInfo i) { + m.set(LENS_FACING, i.facing == CameraInfo.CAMERA_FACING_BACK ? + LENS_FACING_BACK : LENS_FACING_FRONT); + m.set(SENSOR_ORIENTATION, i.orientation); + } + + private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) { + m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED); + mapStreamConfigs(m, p); + mapControlAe(m, p); + mapControlAwb(m, p); + mapCapabilities(m, p); + mapLens(m, p); + mapFlash(m, p); + // TODO: map other fields + } + + private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) { + + ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>(); + /* + * Implementation-defined (preview, recording, etc) -> use camera1 preview sizes + * YUV_420_888 cpu callbacks -> use camera1 preview sizes + * Other preview callbacks (CPU) -> use camera1 preview sizes + * JPEG still capture -> use camera1 still capture sizes + * + * Use platform-internal format constants here, since StreamConfigurationMap does the + * remapping to public format constants. + */ + List<Size> previewSizes = p.getSupportedPreviewSizes(); + appendStreamConfig(availableStreamConfigs, + HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, previewSizes); + appendStreamConfig(availableStreamConfigs, + ImageFormat.YUV_420_888, previewSizes); + for (int format : p.getSupportedPreviewFormats()) { + if (ImageFormat.isPublicFormat(format)) { + appendStreamConfig(availableStreamConfigs, format, previewSizes); + } else { + /* + * Do not add any formats unknown to us + * (since it would fail runtime checks in StreamConfigurationMap) + */ + Log.w(TAG, + String.format("mapStreamConfigs - Skipping non-public format %x", format)); + } + } + + List<Camera.Size> jpegSizes = p.getSupportedPictureSizes(); + appendStreamConfig(availableStreamConfigs, + HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes()); + m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + availableStreamConfigs.toArray(new StreamConfiguration[0])); + + // No frame durations available + m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]); + + StreamConfigurationDuration[] jpegStalls = + new StreamConfigurationDuration[jpegSizes.size()]; + int i = 0; + long longestStallDuration = -1; + for (Camera.Size s : jpegSizes) { + long stallDuration = calculateJpegStallDuration(s); + jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width, + s.height, stallDuration); + if (longestStallDuration < stallDuration) { + longestStallDuration = stallDuration; + } + } + // Set stall durations for jpeg, other formats use default stall duration + m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls); + + m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration); + } + + @SuppressWarnings({"unchecked"}) + private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) { + /* + * control.aeAvailableTargetFpsRanges + */ + List<int[]> fpsRanges = p.getSupportedPreviewFpsRange(); + if (fpsRanges == null) { + throw new AssertionError("Supported FPS ranges cannot be null."); + } + int rangesSize = fpsRanges.size(); + if (rangesSize <= 0) { + throw new AssertionError("At least one FPS range must be supported."); + } + Range<Integer>[] ranges = new Range[rangesSize]; + int i = 0; + for (int[] r : fpsRanges) { + ranges[i++] = Range.create(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + } + m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges); + + /* + * control.aeAvailableAntiBandingModes + */ + + List<String> antiBandingModes = p.getSupportedAntibanding(); + int antiBandingModesSize = antiBandingModes.size(); + if (antiBandingModesSize > 0) { + int[] modes = new int[antiBandingModesSize]; + int j = 0; + for (String mode : antiBandingModes) { + int convertedMode = convertAntiBandingMode(mode); + if (convertedMode == -1) { + Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) + + " not supported, skipping..."); + } else { + modes[j++] = convertedMode; + } + } + m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j)); + } + + /* + * control.aeAvailableModes + */ + List<String> flashModes = p.getSupportedFlashModes(); + + String[] flashModeStrings = new String[] { + Camera.Parameters.FLASH_MODE_AUTO, + Camera.Parameters.FLASH_MODE_ON, + Camera.Parameters.FLASH_MODE_RED_EYE, + // Map these manually + Camera.Parameters.FLASH_MODE_TORCH, + Camera.Parameters.FLASH_MODE_OFF, + }; + int[] flashModeInts = new int[] { + CONTROL_AE_MODE_ON, + CONTROL_AE_MODE_ON_AUTO_FLASH, + CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE + }; + int[] aeAvail = convertStringListToIntArray(flashModes, flashModeStrings, flashModeInts); + + // No flash control -> AE is always on + if (aeAvail == null || aeAvail.length == 0) { + aeAvail = new int[] { + CONTROL_AE_MODE_ON + }; + } + + if (LIE_ABOUT_FLASH) { + // TODO: Remove this branch + Log.w(TAG, "mapControlAe - lying; saying we only support CONTROL_AE_MODE_ON"); + aeAvail = new int[] { + CONTROL_AE_MODE_ON + }; + } + + m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail); + } + + private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) { + if (!LIE_ABOUT_AWB) { + throw new AssertionError("Not implemented yet"); + } + } + + private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) { + int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE }; + m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities); + } + + private static void mapLens(CameraMetadataNative m, Camera.Parameters p) { + /* + * We can tell if the lens is fixed focus; + * but if it's not, we can't tell the minimum focus distance, so leave it null then. + */ + if (p.getFocusMode() == Camera.Parameters.FOCUS_MODE_FIXED) { + m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS); + } + } + + private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) { + boolean flashAvailable = false; + List<String> supportedFlashModes = p.getSupportedFlashModes(); + if (supportedFlashModes != null) { + // If only 'OFF' is available, we don't really have flash support + if (!(supportedFlashModes.contains(Camera.Parameters.FLASH_MODE_OFF) && + supportedFlashModes.size() == 1)) { + flashAvailable = true; + } + } + + if (LIE_ABOUT_FLASH && flashAvailable) { + // TODO: remove this branch + Log.w(TAG, "mapFlash - lying; saying we never support flash"); + flashAvailable = false; + } + + m.set(FLASH_INFO_AVAILABLE, flashAvailable); + } + + private static void appendStreamConfig( + ArrayList<StreamConfiguration> configs, int format, List<Camera.Size> sizes) { + for (Camera.Size size : sizes) { + StreamConfiguration config = + new StreamConfiguration(format, size.width, size.height, /*input*/false); + configs.add(config); + } + } + + /** + * Returns -1 if the anti-banding mode string is null, or not supported. + */ + private static int convertAntiBandingMode(final String mode) { + if (mode == null) { + return -1; + } + switch(mode) { + case Camera.Parameters.ANTIBANDING_OFF: { + return CONTROL_AE_ANTIBANDING_MODE_OFF; + } + case Camera.Parameters.ANTIBANDING_50HZ: { + return CONTROL_AE_ANTIBANDING_MODE_50HZ; + } + case Camera.Parameters.ANTIBANDING_60HZ: { + return CONTROL_AE_ANTIBANDING_MODE_60HZ; + } + case Camera.Parameters.ANTIBANDING_AUTO: { + return CONTROL_AE_ANTIBANDING_MODE_AUTO; + } + default: { + return -1; + } + } + } + + /** + * Returns null if the anti-banding mode enum is not supported. + */ + private static String convertAntiBandingModeToLegacy(int mode) { + switch(mode) { + case CONTROL_AE_ANTIBANDING_MODE_OFF: { + return Camera.Parameters.ANTIBANDING_OFF; + } + case CONTROL_AE_ANTIBANDING_MODE_50HZ: { + return Camera.Parameters.ANTIBANDING_50HZ; + } + case CONTROL_AE_ANTIBANDING_MODE_60HZ: { + return Camera.Parameters.ANTIBANDING_60HZ; + } + case CONTROL_AE_ANTIBANDING_MODE_AUTO: { + return Camera.Parameters.ANTIBANDING_AUTO; + } + default: { + return null; + } + } + } + + + private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) { + int[] legacyFps = new int[2]; + legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower(); + legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper(); + return legacyFps; + } + + /** + * Return the stall duration for a given output jpeg size in nanoseconds. + * + * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p> + */ + private static long calculateJpegStallDuration(Camera.Size size) { + long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture + long area = size.width * (long) size.height; + long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME * NS_PER_MS / + APPROXIMATE_SENSOR_AREA; // 600ms stall for 8mp + return baseDuration + area * stallPerArea; + } + + /** + * Generate capture result metadata from legacy camera parameters. + * + * @param params a {@link Camera.Parameters} object to generate metadata from. + * @param request the {@link CaptureRequest} used for this result. + * @param timestamp the timestamp to use for this result in nanoseconds. + * @return a {@link CameraMetadataNative} object containing result metadata. + */ + public static CameraMetadataNative convertResultMetadata(Camera.Parameters params, + CaptureRequest request, + long timestamp) { + CameraMetadataNative result = new CameraMetadataNative(); + + /* + * control + */ + // control.afState + if (LIE_ABOUT_AF) { + // TODO: Implement autofocus state machine + result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE)); + } + + // control.aeState + if (LIE_ABOUT_AE) { + // Lie to pass CTS temporarily. + // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves + result.set(CaptureResult.CONTROL_AE_STATE, + CONTROL_AE_STATE_CONVERGED); + + result.set(CaptureResult.CONTROL_AE_MODE, + request.get(CaptureRequest.CONTROL_AE_MODE)); + } + + // control.awbLock + result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock()); + + // control.awbState + if (LIE_ABOUT_AWB) { + // Lie to pass CTS temporarily. + // TODO: CTS needs to be updated not to query this value + // for LIMITED devices unless its guaranteed to be available. + result.set(CaptureResult.CONTROL_AWB_STATE, + CameraMetadata.CONTROL_AWB_STATE_CONVERGED); + // TODO: Read the awb mode from parameters instead + result.set(CaptureResult.CONTROL_AWB_MODE, + request.get(CaptureRequest.CONTROL_AWB_MODE)); + } + + /* + * lens + */ + // lens.focalLength + result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength()); + + /* + * sensor + */ + // sensor.timestamp + result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp); + + // TODO: Remaining result metadata tags conversions. + return result; + } + + /** + * Set the legacy parameters using the request metadata. + * + * @param request a {@link CaptureRequest} object to generate parameters from. + * @param params the a {@link Camera.Parameters} to set parameters in. + */ + public static void convertRequestMetadata(CaptureRequest request, + /*out*/Camera.Parameters params) { + + /* + * control.ae* + */ + // control.aeAntibandingMode + Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE); + if (antiBandingMode != null) { + String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode); + if (legacyMode != null) params.setAntibanding(legacyMode); + } + + // control.aeTargetFpsRange + Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); + if (aeFpsRange != null) { + int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange); + params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + } + + /* + * control + */ + // control.awbLock + Boolean awbLock = request.get(CaptureRequest.CONTROL_AWB_LOCK); + params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock); + } + + /** + * Create an int[] from the List<> by using {@code convertFrom} and {@code convertTo} + * as a one-to-one map (via the index). + * + * <p>Strings not appearing in {@code convertFrom} are ignored (with a warning); + * strings appearing in {@code convertFrom} but not {@code convertTo} are silently + * dropped.</p> + * + * @param list Source list of strings + * @param convertFrom Conversion list of strings + * @param convertTo Conversion list of ints + * @return An array of ints where the values correspond to the ones in {@code convertTo} + * or {@code null} if {@code list} was {@code null} + */ + private static int[] convertStringListToIntArray( + List<String> list, String[] convertFrom, int[] convertTo) { + if (list == null) { + return null; + } + + List<Integer> convertedList = new ArrayList<>(list.size()); + + for (String str : list) { + int strIndex = getArrayIndex(convertFrom, str); + + // Guard against bad API1 values + if (strIndex < 0) { + Log.w(TAG, "Ignoring invalid parameter " + str); + continue; + } + + // Ignore values we can't map into (intentional) + if (strIndex < convertTo.length) { + convertedList.add(convertTo[strIndex]); + } + } + + int[] returnArray = new int[convertedList.size()]; + for (int i = 0; i < returnArray.length; ++i) { + returnArray[i] = convertedList.get(i); + } + + return returnArray; + } + + /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */ + private static <T> int getArrayIndex(T[] array, T needle) { + if (needle == null) { + return -1; + } + + int index = 0; + for (T elem : array) { + if (Objects.equals(elem, needle)) { + return index; + } + index++; + } + + return -1; + } + + /** + * Create a request template + * + * @param c a non-{@code null} camera characteristics for this camera + * @param templateId a non-negative template ID + * + * @return a non-{@code null} request template + * + * @throws IllegalArgumentException if {@code templateId} was invalid + * + * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL + */ + public static CameraMetadataNative createRequestTemplate( + CameraCharacteristics c, int templateId) { + if (templateId < 0 || templateId > CameraDevice.TEMPLATE_MANUAL) { + throw new IllegalArgumentException("templateId out of range"); + } + + CameraMetadataNative m = new CameraMetadataNative(); + + /* + * NOTE: If adding new code here and it needs to query the static info, + * query the camera characteristics, so we can reuse this for api2 code later + * to create our own templates in the framework + */ + + if (LIE_ABOUT_AWB) { + m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO); + } else { + throw new AssertionError("Valid control.awbMode not implemented yet"); + } + + // control.aeMode + m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON); + // AE is always unconditionally available in API1 devices + + // control.afMode + { + Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE); + + int afMode; + if (minimumFocusDistance != null && + minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) { + // Cannot control auto-focus with fixed-focus cameras + afMode = CameraMetadata.CONTROL_AF_MODE_OFF; + } else { + // If a minimum focus distance is reported; the camera must have AF + afMode = CameraMetadata.CONTROL_AF_MODE_AUTO; + } + + m.set(CaptureRequest.CONTROL_AF_MODE, afMode); + } + + // TODO: map other request template values + return m; + } +} diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java index 5c68303..7820648 100644 --- a/core/java/android/hardware/camera2/legacy/RequestQueue.java +++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java @@ -77,7 +77,8 @@ public class RequestQueue { long ret = INVALID_FRAME; if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) { mRepeatingRequest = null; - ret = mCurrentRepeatingFrameNumber; + ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME : + mCurrentRepeatingFrameNumber - 1; mCurrentRepeatingFrameNumber = INVALID_FRAME; } else { Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId); @@ -105,7 +106,8 @@ public class RequestQueue { long ret = INVALID_FRAME; if (burst.isRepeating()) { if (mRepeatingRequest != null) { - ret = mCurrentRepeatingFrameNumber; + ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME : + mCurrentRepeatingFrameNumber - 1; } mCurrentRepeatingFrameNumber = INVALID_FRAME; mRepeatingRequest = burst; @@ -122,7 +124,7 @@ public class RequestQueue { for (BurstHolder b : mRequestQueue) { total += b.getNumberOfRequests(); if (b.getRequestId() == requestId) { - return total; + return total - 1; } } throw new IllegalStateException( diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index c4669f5..efc2b0e 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -16,7 +16,6 @@ package android.hardware.camera2.legacy; -import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.camera2.CaptureRequest; @@ -28,12 +27,15 @@ import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.util.Pair; +import android.util.Size; import android.view.Surface; import java.io.IOError; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.List; /** @@ -62,23 +64,31 @@ public class RequestThreadManager { private static final int MSG_CLEANUP = 3; private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms - private static final int JPEG_FRAME_TIMEOUT = 1000; // ms + private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2) + private static final float ASPECT_RATIO_TOLERANCE = 0.01f; private boolean mPreviewRunning = false; + private volatile long mLastJpegTimestamp; + private volatile long mLastPreviewTimestamp; private volatile RequestHolder mInFlightPreview; private volatile RequestHolder mInFlightJpeg; - private List<Surface> mPreviewOutputs = new ArrayList<Surface>(); - private List<Surface> mCallbackOutputs = new ArrayList<Surface>(); + private final List<Surface> mPreviewOutputs = new ArrayList<Surface>(); + private final List<Surface> mCallbackOutputs = new ArrayList<Surface>(); private GLThreadManager mGLThreadManager; private SurfaceTexture mPreviewTexture; + private Camera.Parameters mParams; + + private Size mIntermediateBufferSize; private final RequestQueue mRequestQueue = new RequestQueue(); + private CaptureRequest mLastRequest = null; private SurfaceTexture mDummyTexture; private Surface mDummySurface; private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview"); + private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests"); /** * Container object for Configure messages. @@ -93,6 +103,31 @@ public class RequestThreadManager { } } + + /** + * Comparator for {@link Size} objects by the area. + * + * <p>This comparator totally orders by rectangle area. Tiebreaks on width.</p> + */ + private static class SizeAreaComparator implements Comparator<Size> { + @Override + public int compare(Size size, Size size2) { + if (size == null || size2 == null) { + throw new NullPointerException("Null argument passed to compare"); + } + if (size.equals(size2)) return 0; + long width = size.getWidth(); + long width2 = size2.getWidth(); + long area = width * size.getHeight(); + long area2 = width2 * size2.getHeight(); + if (area == area2) { + return (width > width2) ? 1 : -1; + } + return (area > area2) ? 1 : -1; + + } + } + /** * Counter class used to calculate and log the current FPS of frame production. */ @@ -177,23 +212,37 @@ public class RequestThreadManager { } }; + private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() { + @Override + public void onShutter() { + mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos(); + } + }; + private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback = new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { - if (DEBUG) { - mPrevCounter.countAndLog(); - } RequestHolder holder = mInFlightPreview; if (holder == null) { + mGLThreadManager.queueNewFrame(null); Log.w(TAG, "Dropping preview frame."); - mInFlightPreview = null; return; } + + if (DEBUG) { + mPrevCounter.countAndLog(); + } + mInFlightPreview = null; + if (holder.hasPreviewTargets()) { mGLThreadManager.queueNewFrame(holder.getHolderTargets()); } + /** + * TODO: Get timestamp from GL thread after buffer update. + */ + mLastPreviewTimestamp = surfaceTexture.getTimestamp(); mReceivedPreview.open(); } }; @@ -213,14 +262,18 @@ public class RequestThreadManager { } private void doJpegCapture(RequestHolder request) throws IOException { + if (DEBUG) Log.d(TAG, "doJpegCapture"); + if (!mPreviewRunning) { + if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface"); + createDummySurface(); mCamera.setPreviewTexture(mDummyTexture); startPreview(); } mInFlightJpeg = request; // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted - mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback); + mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback); mPreviewRunning = false; } @@ -230,7 +283,13 @@ public class RequestThreadManager { return; // Already running } - mPreviewTexture.setDefaultBufferSize(640, 480); // TODO: size selection based on request + if (mPreviewTexture == null) { + throw new IllegalStateException( + "Preview capture called with no preview surfaces configured."); + } + + mPreviewTexture.setDefaultBufferSize(mIntermediateBufferSize.getWidth(), + mIntermediateBufferSize.getHeight()); mCamera.setPreviewTexture(mPreviewTexture); Camera.Parameters params = mCamera.getParameters(); List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange(); @@ -248,6 +307,7 @@ public class RequestThreadManager { startPreview(); } + private void configureOutputs(Collection<Surface> outputs) throws IOException { stopPreview(); if (mGLThreadManager != null) { @@ -261,18 +321,74 @@ public class RequestThreadManager { mInFlightPreview = null; mInFlightJpeg = null; - for (Surface s : outputs) { - int format = LegacyCameraDevice.nativeDetectSurfaceType(s); - switch (format) { - case CameraMetadataNative.NATIVE_JPEG_FORMAT: - mCallbackOutputs.add(s); - break; - default: - mPreviewOutputs.add(s); - break; + if (outputs != null) { + for (Surface s : outputs) { + int format = LegacyCameraDevice.nativeDetectSurfaceType(s); + switch (format) { + case CameraMetadataNative.NATIVE_JPEG_FORMAT: + mCallbackOutputs.add(s); + break; + default: + mPreviewOutputs.add(s); + break; + } + } + } + mParams = mCamera.getParameters(); + if (mPreviewOutputs.size() > 0) { + List<Size> outputSizes = new ArrayList<>(outputs.size()); + for (Surface s : mPreviewOutputs) { + int[] dimens = {0, 0}; + LegacyCameraDevice.nativeDetectSurfaceDimens(s, dimens); + outputSizes.add(new Size(dimens[0], dimens[1])); + } + + Size largestOutput = findLargestByArea(outputSizes); + + // Find largest jpeg dimension - assume to have the same aspect ratio as sensor. + List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes()); + Size largestJpegDimen = findLargestByArea(supportedJpegSizes); + + List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes()); + + // Use smallest preview dimension with same aspect ratio as sensor that is >= than all + // of the configured output dimensions. If none exists, fall back to using the largest + // supported preview size. + long largestOutputArea = largestOutput.getHeight() * (long) largestOutput.getWidth(); + Size bestPreviewDimen = findLargestByArea(supportedPreviewSizes); + for (Size s : supportedPreviewSizes) { + long currArea = s.getWidth() * s.getHeight(); + long bestArea = bestPreviewDimen.getWidth() * bestPreviewDimen.getHeight(); + if (checkAspectRatiosMatch(largestJpegDimen, s) && (currArea < bestArea && + currArea >= largestOutputArea)) { + bestPreviewDimen = s; + } + } + + mIntermediateBufferSize = bestPreviewDimen; + if (DEBUG) { + Log.d(TAG, "Intermediate buffer selected with dimens: " + + bestPreviewDimen.toString()); + } + } else { + mIntermediateBufferSize = null; + if (DEBUG) { + Log.d(TAG, "No Intermediate buffer selected, no preview outputs were configured"); } } + Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs, mParams); + if (smallestSupportedJpegSize != null) { + /* + * Set takePicture size to the smallest supported JPEG size large enough + * to scale/crop out of for the bounding rectangle of the configured JPEG sizes. + */ + + Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize); + mParams.setPictureSize( + smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight()); + } + // TODO: Detect and optimize single-output paths here to skip stream teeing. if (mGLThreadManager == null) { mGLThreadManager = new GLThreadManager(mCameraId); @@ -282,7 +398,123 @@ public class RequestThreadManager { mGLThreadManager.setConfigurationAndWait(mPreviewOutputs); mGLThreadManager.allowNewFrames(); mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture(); - mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback); + if (mPreviewTexture != null) { + mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback); + } + + // TODO: configure the JPEG surface with some arbitrary size + // using LegacyCameraDevice.nativeConfigureSurface + } + + /** + * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger + * than all of the configured {@code JPEG} outputs (by both width and height). + * + * <p>If multiple supported JPEG sizes are larger, select the smallest of them which + * still satisfies the above constraint.</p> + * + * <p>As a result, the returned size is guaranteed to be usable without needing + * to upscale any of the outputs. If only one {@code JPEG} surface is used, + * then no scaling/cropping is necessary between the taken picture and + * the {@code JPEG} output surface.</p> + * + * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats + * @param params api1 parameters (used for reading only) + * + * @return a size large enough to fit all of the configured {@code JPEG} outputs, or + * {@code null} if the {@code callbackOutputs} did not have any {@code JPEG} + * surfaces. + */ + private Size calculatePictureSize( + Collection<Surface> callbackOutputs, Camera.Parameters params) { + /* + * Find the largest JPEG size (if any), from the configured outputs: + * - the api1 picture size should be set to the smallest legal size that's at least as large + * as the largest configured JPEG size + */ + List<Size> configuredJpegSizes = new ArrayList<Size>(); + for (Surface callbackSurface : callbackOutputs) { + int format = LegacyCameraDevice.nativeDetectSurfaceType(callbackSurface); + + if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) { + continue; // Ignore non-JPEG callback formats + } + + Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface); + configuredJpegSizes.add(jpegSize); + } + if (!configuredJpegSizes.isEmpty()) { + /* + * Find the largest configured JPEG width, and height, independently + * of the rest. + * + * The rest of the JPEG streams can be cropped out of this smallest bounding + * rectangle. + */ + int maxConfiguredJpegWidth = -1; + int maxConfiguredJpegHeight = -1; + for (Size jpegSize : configuredJpegSizes) { + maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ? + jpegSize.getWidth() : maxConfiguredJpegWidth; + maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ? + jpegSize.getHeight() : maxConfiguredJpegHeight; + } + Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight); + + List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes()); + + /* + * Find the smallest supported JPEG size that can fit the smallest bounding + * rectangle for the configured JPEG sizes. + */ + List<Size> candidateSupportedJpegSizes = new ArrayList<>(); + for (Size supportedJpegSize : supportedJpegSizes) { + if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth && + supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) { + candidateSupportedJpegSizes.add(supportedJpegSize); + } + } + + if (candidateSupportedJpegSizes.isEmpty()) { + throw new AssertionError( + "Could not find any supported JPEG sizes large enough to fit " + + smallestBoundJpegSize); + } + + Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes, + new SizeAreaComparator()); + + if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) { + Log.w(TAG, + String.format( + "configureOutputs - Will need to crop picture %s into " + + "smallest bound size %s", + smallestSupportedJpegSize, smallestBoundJpegSize)); + } + + return smallestSupportedJpegSize; + } + + return null; + } + + private static Size findLargestByArea(List<Size> sizes) { + return Collections.max(sizes, new SizeAreaComparator()); + } + + private static boolean checkAspectRatiosMatch(Size a, Size b) { + float aAspect = a.getWidth() / (float) a.getHeight(); + float bAspect = b.getWidth() / (float) b.getHeight(); + + return Math.abs(aAspect - bAspect) < ASPECT_RATIO_TOLERANCE; + } + + private static List<Size> convertSizeList(List<Camera.Size> sizeList) { + List<Size> sizes = new ArrayList<>(sizeList.size()); + for (Camera.Size s : sizeList) { + sizes.add(new Size(s.width, s.height)); + } + return sizes; } // Calculate the highest FPS range supported @@ -312,7 +544,6 @@ public class RequestThreadManager { private final Handler.Callback mRequestHandlerCb = new Handler.Callback() { private boolean mCleanup = false; - private List<RequestHolder> mRepeating = null; @SuppressWarnings("unchecked") @Override @@ -321,10 +552,14 @@ public class RequestThreadManager { return true; } + if (DEBUG) { + Log.d(TAG, "Request thread handling message:" + msg.what); + } switch (msg.what) { case MSG_CONFIGURE_OUTPUTS: ConfigureHolder config = (ConfigureHolder) msg.obj; - Log.i(TAG, "Configure outputs: " + config.surfaces.size() + + int sizes = config.surfaces != null ? config.surfaces.size() : 0; + Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured."); try { configureOutputs(config.surfaces); @@ -352,7 +587,15 @@ public class RequestThreadManager { List<RequestHolder> requests = nextBurst.first.produceRequestHolders(nextBurst.second); for (RequestHolder holder : requests) { + CaptureRequest request = holder.getRequest(); + if (mLastRequest == null || mLastRequest != request) { + mLastRequest = request; + LegacyMetadataMapper.convertRequestMetadata(mLastRequest, + /*out*/mParams); + mCamera.setParameters(mParams); + } mDeviceState.setCaptureStart(holder); + long timestamp = 0; try { if (holder.hasPreviewTargets()) { mReceivedPreview.close(); @@ -361,23 +604,31 @@ public class RequestThreadManager { // TODO: report error to CameraDevice Log.e(TAG, "Hit timeout for preview callback!"); } + timestamp = mLastPreviewTimestamp; } if (holder.hasJpegTargets()) { mReceivedJpeg.close(); doJpegCapture(holder); - mReceivedJpeg.block(); if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) { // TODO: report error to CameraDevice Log.e(TAG, "Hit timeout for jpeg callback!"); } mInFlightJpeg = null; + timestamp = mLastJpegTimestamp; } } catch (IOException e) { // TODO: err handling throw new IOError(e); } - // TODO: Set fields in result. - mDeviceState.setCaptureResult(holder, new CameraMetadataNative()); + if (timestamp == 0) { + timestamp = SystemClock.elapsedRealtimeNanos(); + } + CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams, + request, timestamp); + mDeviceState.setCaptureResult(holder, result); + } + if (DEBUG) { + mRequestCounter.countAndLog(); } break; case MSG_CLEANUP: @@ -437,6 +688,12 @@ public class RequestThreadManager { Handler handler = mRequestThread.waitAndGetHandler(); handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP)); mRequestThread.quitSafely(); + try { + mRequestThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.", + mRequestThread.getName(), mRequestThread.getId())); + } } /** @@ -473,12 +730,14 @@ public class RequestThreadManager { /** - * Configure with the current output Surfaces. + * Configure with the current list of output Surfaces. * * <p> * This operation blocks until the configuration is complete. * </p> * + * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p> + * * @param outputs a {@link java.util.Collection} of outputs to configure. */ public void configure(Collection<Surface> outputs) { diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java index 2f0f6bc..bbc7005 100644 --- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java +++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java @@ -431,10 +431,15 @@ public class SurfaceTextureRenderer { public void configureSurfaces(Collection<Surface> surfaces) { releaseEGLContext(); + if (surfaces == null || surfaces.size() == 0) { + Log.w(TAG, "No output surfaces configured for GL drawing."); + return; + } + for (Surface s : surfaces) { // If pixel conversions aren't handled by egl, use a pbuffer if (LegacyCameraDevice.needsConversion(s)) { - LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.NV21); + LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.YV12); EGLSurfaceHolder holder = new EGLSurfaceHolder(); holder.surface = s; mConversionSurfaces.add(holder); @@ -481,6 +486,7 @@ public class SurfaceTextureRenderer { } checkGlError("before updateTexImage"); mSurfaceTexture.updateTexImage(); + if (targetSurfaces == null) return; for (EGLSurfaceHolder holder : mSurfaces) { if (targetSurfaces.contains(holder.surface)) { makeCurrent(holder.eglSurface); diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 3036425..fff171b 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -63,6 +63,12 @@ import static com.android.internal.util.Preconditions.*; public final class StreamConfigurationMap { private static final String TAG = "StreamConfigurationMap"; + + /** + * Indicates that a minimum frame duration is not available for a particular configuration. + */ + public static final long NO_MIN_FRAME_DURATION = 0; + /** * Create a new {@link StreamConfigurationMap}. * @@ -359,7 +365,9 @@ public final class StreamConfigurationMap { * * @param format an image format from {@link ImageFormat} or {@link PixelFormat} * @param size an output-compatible size - * @return a minimum frame duration {@code >=} 0 in nanoseconds + * @return a minimum frame duration {@code >} 0 in nanoseconds, or + * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this + * can only occur on limited mode devices). * * @throws IllegalArgumentException if {@code format} or {@code size} was not supported * @throws NullPointerException if {@code size} was {@code null} @@ -406,7 +414,9 @@ public final class StreamConfigurationMap { * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a * non-empty array returned by {@link #getOutputSizes(Class)} * @param size an output-compatible size - * @return a minimum frame duration {@code >=} 0 in nanoseconds + * @return a minimum frame duration {@code >} 0 in nanoseconds, or + * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this + * can only occur on limited mode devices). * * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported * @throws NullPointerException if {@code size} or {@code klass} was {@code null} @@ -892,7 +902,7 @@ public final class StreamConfigurationMap { private long getDurationDefault(int duration) { switch (duration) { case DURATION_MIN_FRAME: - throw new AssertionError("Minimum frame durations are required to be listed"); + return NO_MIN_FRAME_DURATION; case DURATION_STALL: return 0L; // OK. A lack of a stall duration implies a 0 stall duration default: diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java index 40cda08..898c746 100644 --- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java +++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java @@ -47,6 +47,8 @@ public class CameraBinderDecorator { * - POLICY_PROHIBITS * - RESOURCE_BUSY * - NO_SUCH_DEVICE + * - NOT_SUPPORTED + * - TOO_MANY_USERS */ public static final int EACCES = -13; public static final int EBUSY = -16; diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java new file mode 100644 index 0000000..af55055 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/CloseableLock.java @@ -0,0 +1,330 @@ +/* + * 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.camera2.utils; + +import android.util.Log; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implement a shared/exclusive lock that can be closed. + * + * <p>A shared lock can be acquired if any other shared locks are also acquired. An + * exclusive lock acquire will block until all shared locks have been released.</p> + * + * <p>Locks are re-entrant; trying to acquire another lock (of the same type) + * while a lock is already held will immediately succeed.</p> + * + * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not + * supported; attempting it will throw an {@link IllegalStateException}.</p> + * + * <p>If the lock is closed, all future and current acquires will immediately return {@code null}. + * </p> + */ +public class CloseableLock implements AutoCloseable { + + private static final boolean VERBOSE = false; + + private final String TAG = "CloseableLock"; + private final String mName; + + private volatile boolean mClosed = false; + + /** If an exclusive lock is acquired by some thread. */ + private boolean mExclusive = false; + /** + * How many shared locks are acquired by any thread: + * + * <p>Reentrant locking increments this. If an exclusive lock is held, + * this value will stay at 0.</p> + */ + private int mSharedLocks = 0; + + private final ReentrantLock mLock = new ReentrantLock(); + /** This condition automatically releases mLock when waiting; re-acquiring it after notify */ + private final Condition mCondition = mLock.newCondition(); + + /** How many times the current thread is holding the lock */ + private final ThreadLocal<Integer> mLockCount = + new ThreadLocal<Integer>() { + @Override protected Integer initialValue() { + return 0; + } + }; + + /** + * Helper class to release a lock at the end of a try-with-resources statement. + */ + public class ScopedLock implements AutoCloseable { + private ScopedLock() {} + + /** Release the lock with {@link CloseableLock#releaseLock}. */ + @Override + public void close() { + releaseLock(); + } + } + + /** + * Create a new instance; starts out with 0 locks acquired. + */ + public CloseableLock() { + mName = ""; + } + + /** + * Create a new instance; starts out with 0 locks acquired. + * + * @param name set an optional name for logging functionality + */ + public CloseableLock(String name) { + mName = name; + } + + /** + * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock. + * + * <p>Marking a lock as closed will fail all further acquisition attempts; + * it will also immediately unblock all other threads currently trying to acquire a lock.</p> + * + * <p>This operation is idempotent; calling it more than once has no effect.</p> + * + * @throws IllegalStateException + * if an attempt is made to {@code close} while this thread has a lock acquired + */ + @Override + public void close() { + if (mClosed) { + log("close - already closed; ignoring"); + return; + } + + ScopedLock scoper = acquireExclusiveLock(); + // Already closed by another thread? + if (scoper == null) { + return; + } else if (mLockCount.get() != 1) { + // Future: may want to add a #releaseAndClose to allow this. + throw new IllegalStateException( + "Cannot close while one or more acquired locks are being held by this " + + "thread; release all other locks first"); + } + + try { + mLock.lock(); + + mClosed = true; + mExclusive = false; + mSharedLocks = 0; + mLockCount.remove(); + + // Notify all threads that are waiting to unblock and return immediately + mCondition.signalAll(); + } finally { + mLock.unlock(); + } + + log("close - completed"); + } + + /** + * Try to acquire the lock non-exclusively, blocking until the operation completes. + * + * <p>If the lock has already been closed, or being closed before this operation returns, + * the call will immediately return {@code false}.</p> + * + * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed), + * this operation will return immediately. If another thread holds an exclusive lock, + * this thread will block until the exclusive lock has been released.</p> + * + * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is + * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p> + * + * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock + * was already closed. + * + * @throws IllegalStateException if this thread is already holding an exclusive lock + */ + public ScopedLock acquireLock() { + + int ownedLocks; + + try { + mLock.lock(); + + // Lock is already closed, all further acquisitions will fail + if (mClosed) { + log("acquire lock early aborted (already closed)"); + return null; + } + + ownedLocks = mLockCount.get(); + + // This thread is already holding an exclusive lock + if (mExclusive && ownedLocks > 0) { + throw new IllegalStateException( + "Cannot acquire shared lock while holding exclusive lock"); + } + + // Is another thread holding the exclusive lock? Block until we can get in. + while (mExclusive) { + mCondition.awaitUninterruptibly(); + + // Did another thread #close while we were waiting? Unblock immediately. + if (mClosed) { + log("acquire lock unblocked aborted (already closed)"); + return null; + } + } + + mSharedLocks++; + + ownedLocks = mLockCount.get() + 1; + mLockCount.set(ownedLocks); + } finally { + mLock.unlock(); + } + + log("acquired lock (local own count = " + ownedLocks + ""); + return new ScopedLock(); + } + + /** + * Try to acquire the lock exclusively, blocking until all other threads release their locks. + * + * <p>If the lock has already been closed, or being closed before this operation returns, + * the call will immediately return {@code false}.</p> + * + * <p>If any other threads are holding a lock, this thread will block until all + * other locks are released.</p> + * + * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported, + * and must be matched by an equal number of {@link #releaseLock} calls.</p> + * + * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock + * was already closed. + * + * @throws IllegalStateException + * if an attempt is made to acquire an exclusive lock while already holding a lock + */ + public ScopedLock acquireExclusiveLock() { + + int ownedLocks; + + try { + mLock.lock(); + + // Lock is already closed, all further acquisitions will fail + if (mClosed) { + log("acquire exclusive lock early aborted (already closed)"); + return null; + } + + ownedLocks = mLockCount.get(); + + // This thread is already holding a shared lock + if (!mExclusive && ownedLocks > 0) { + throw new IllegalStateException( + "Cannot acquire exclusive lock while holding shared lock"); + } + + /* + * Is another thread holding the lock? Block until we can get in. + * + * If we are already holding the lock, always let it through since + * we are just reentering the exclusive lock. + */ + while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) { + mCondition.awaitUninterruptibly(); + + // Did another thread #close while we were waiting? Unblock immediately. + if (mClosed) { + log("acquire exclusive lock unblocked aborted (already closed)"); + return null; + } + } + + mExclusive = true; + + ownedLocks = mLockCount.get() + 1; + mLockCount.set(ownedLocks); + } finally { + mLock.unlock(); + } + + log("acquired exclusive lock (local own count = " + ownedLocks + ""); + return new ScopedLock(); + } + + /** + * Release a single lock that was acquired. + * + * <p>Any other other that is blocked and trying to acquire a lock will get a chance + * to acquire the lock.</p> + * + * @throws IllegalStateException if no locks were acquired, or if the lock was already closed + */ + public void releaseLock() { + if (mLockCount.get() <= 0) { + throw new IllegalStateException( + "Cannot release lock that was not acquired by this thread"); + } + + int ownedLocks; + + try { + mLock.lock(); + + // Lock is already closed, it couldn't have been acquired in the first place + if (mClosed) { + throw new IllegalStateException("Do not release after the lock has been closed"); + } + + if (!mExclusive) { + mSharedLocks--; + } else { + if (mSharedLocks != 0) { + throw new AssertionError("Too many shared locks " + mSharedLocks); + } + } + + ownedLocks = mLockCount.get() - 1; + mLockCount.set(ownedLocks); + + if (ownedLocks == 0 && mExclusive) { + // Wake up any threads that might be waiting for the exclusive lock to be released + mExclusive = false; + mCondition.signalAll(); + } else if (ownedLocks == 0 && mSharedLocks == 0) { + // Wake up any threads that might be trying to get the exclusive lock + mCondition.signalAll(); + } + } finally { + mLock.unlock(); + } + + log("released lock (local lock count " + ownedLocks + ")"); + } + + private void log(String what) { + if (VERBOSE) { + Log.v(TAG + "[" + mName + "]", what); + } + } + +} diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index 8ad9463..d86dd5e 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -185,6 +185,7 @@ public final class HdmiCec { public static final int RESULT_TARGET_NOT_AVAILABLE = 3; public static final int RESULT_ALREADY_IN_PROGRESS = 4; public static final int RESULT_EXCEPTION = 5; + public static final int RESULT_INCORRECT_MODE = 6; private static final int[] ADDRESS_TO_TYPE = { DEVICE_TV, // ADDR_TV diff --git a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.aidl b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.aidl new file mode 100644 index 0000000..1615910 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.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.hdmi; + +parcelable HdmiCecDeviceInfo; diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.aidl b/core/java/android/hardware/hdmi/HdmiPortInfo.aidl new file mode 100644 index 0000000..157b5b3 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.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.hdmi; + +parcelable HdmiPortInfo; diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java new file mode 100644 index 0000000..7b25f8a --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java @@ -0,0 +1,164 @@ +/* + * 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.hdmi; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class to encapsulate HDMI port information. Contains the capability of the ports such as + * HDMI-CEC, MHL, ARC(Audio Return Channel), and physical address assigned to each port. + * + * @hide + */ +@SystemApi +public final class HdmiPortInfo implements Parcelable { + /** HDMI port type: Input */ + public static final int PORT_INPUT = 0; + + /** HDMI port type: Output */ + public static final int PORT_OUTPUT = 1; + + private final int mId; + private final int mType; + private final int mAddress; + private final boolean mCecSupported; + private final boolean mArcSupported; + private final boolean mMhlSupported; + + /** + * Constructor. + * + * @param id identifier assigned to each port. 1 for HDMI port 1 + * @param type HDMI port input/output type + * @param address physical address of the port + * @param cec {@code true} if HDMI-CEC is supported on the port + * @param mhl {@code true} if MHL is supported on the port + * @param arc {@code true} if audio return channel is supported on the port + */ + public HdmiPortInfo(int id, int type, int address, boolean cec, boolean mhl, boolean arc) { + mId = id; + mType = type; + mAddress = address; + mCecSupported = cec; + mArcSupported = arc; + mMhlSupported = mhl; + } + + /** + * Returns the port id. + * + * @return port id + */ + public int getId() { + return mId; + } + + /** + * Returns the port type. + * + * @return port type + */ + public int getType() { + return mType; + } + + /** + * Returns the port address. + * + * @return port address + */ + public int getAddress() { + return mAddress; + } + + /** + * Returns {@code true} if the port supports HDMI-CEC signaling. + * + * @return {@code true} if the port supports HDMI-CEC signaling. + */ + public boolean isCecSupported() { + return mCecSupported; + } + + /** + * Returns {@code true} if the port supports MHL signaling. + * + * @return {@code true} if the port supports MHL signaling. + */ + public boolean isMhlSupported() { + return mMhlSupported; + } + + /** + * Returns {@code true} if the port supports audio return channel. + * + * @return {@code true} if the port supports audio return channel + */ + public boolean isArcSupported() { + return mArcSupported; + } + + /** + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + */ + @Override + public int describeContents() { + return 0; + } + + + /** + * A helper class to deserialize {@link HdmiPortInfo} for a parcel. + */ + public static final Parcelable.Creator<HdmiPortInfo> CREATOR = + new Parcelable.Creator<HdmiPortInfo>() { + @Override + public HdmiPortInfo createFromParcel(Parcel source) { + int id = source.readInt(); + int type = source.readInt(); + int address = source.readInt(); + boolean cec = (source.readInt() == 1); + boolean arc = (source.readInt() == 1); + boolean mhl = (source.readInt() == 1); + return new HdmiPortInfo(id, type, address, cec, arc, mhl); + } + + @Override + public HdmiPortInfo[] newArray(int size) { + return new HdmiPortInfo[size]; + } + }; + + /** + * Serialize this object into a {@link Parcel}. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeInt(mType); + dest.writeInt(mAddress); + dest.writeInt(mCecSupported ? 1 : 0); + dest.writeInt(mArcSupported ? 1 : 0); + dest.writeInt(mMhlSupported ? 1 : 0); + } +} diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java index 6dc4a4f..85af3d1 100644 --- a/core/java/android/hardware/hdmi/HdmiTvClient.java +++ b/core/java/android/hardware/hdmi/HdmiTvClient.java @@ -16,6 +16,8 @@ package android.hardware.hdmi; import android.annotation.SystemApi; +import android.os.RemoteException; +import android.util.Log; /** * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system @@ -33,4 +35,46 @@ public final class HdmiTvClient { HdmiTvClient(IHdmiControlService service) { mService = service; } + + // Factory method for HdmiTvClient. + // Declared package-private. Accessed by HdmiControlManager only. + static HdmiTvClient create(IHdmiControlService service) { + return new HdmiTvClient(service); + } + + /** + * Callback interface used to get the result of {@link #deviceSelect}. + */ + public interface SelectCallback { + /** + * Called when the operation is finished. + * + * @param result the result value of {@link #deviceSelect} + */ + void onComplete(int result); + } + + /** + * Select a CEC logical device to be a new active source. + * + * @param logicalAddress + * @param callback + */ + public void deviceSelect(int logicalAddress, SelectCallback callback) { + // TODO: Replace SelectCallback with PartialResult. + try { + mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select device: ", e); + } + } + + private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { + return new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + callback.onComplete(result); + } + }; + } } diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 8da38e1..73726fa 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -16,9 +16,14 @@ package android.hardware.hdmi; -import android.hardware.hdmi.HdmiCecMessage; +import android.hardware.hdmi.HdmiCecDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.hdmi.IHdmiDeviceEventListener; import android.hardware.hdmi.IHdmiHotplugEventListener; +import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; + +import java.util.List; /** * Binder interface that clients running in the application process @@ -33,4 +38,14 @@ interface IHdmiControlService { void queryDisplayStatus(IHdmiControlCallback callback); void addHotplugEventListener(IHdmiHotplugEventListener listener); void removeHotplugEventListener(IHdmiHotplugEventListener listener); + void addDeviceEventListener(IHdmiDeviceEventListener listener); + void deviceSelect(int logicalAddress, IHdmiControlCallback callback); + void portSelect(int portId, IHdmiControlCallback callback); + void sendKeyEvent(int keyCode, boolean isPressed); + List<HdmiPortInfo> getPortInfo(); + boolean canChangeSystemAudioMode(); + boolean getSystemAudioMode(); + void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback); + void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener); + void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener); } diff --git a/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl new file mode 100644 index 0000000..c4e5989 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl @@ -0,0 +1,35 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiCecDeviceInfo; + +/** + * Callback interface definition for HDMI client to get informed of + * the CEC logical device status change event. + * + * @hide + */ +oneway interface IHdmiDeviceEventListener { + + /** + * @param deviceInfo {@link HdmiCecDeviceInfo} of the logical device whose + * status has changed + * @param activated true if the device gets activated + */ + void onStatusChanged(in HdmiCecDeviceInfo deviceInfo, in boolean activated); +} diff --git a/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl new file mode 100644 index 0000000..714bbe7 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl @@ -0,0 +1,31 @@ +/* + * 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.hdmi; + +/** + * Callback interface definition for HDMI client to get informed of + * "System Audio" mode change. + * + * @hide + */ +oneway interface IHdmiSystemAudioModeChangeListener { + + /** + * @param enabled true if the device gets activated + */ + void onStatusChanged(in boolean enabled); +} diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java new file mode 100644 index 0000000..2d7af85 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -0,0 +1,310 @@ +/* + * 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.soundtrigger; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; + +import java.util.ArrayList; +import java.util.UUID; + +/** + * The SoundTrigger class provides access via JNI to the native service managing + * the sound trigger HAL. + * + * @hide + */ +public class SoundTrigger { + + public static final int STATUS_OK = 0; + public static final int STATUS_ERROR = Integer.MIN_VALUE; + public static final int STATUS_PERMISSION_DENIED = -1; + public static final int STATUS_NO_INIT = -19; + public static final int STATUS_BAD_VALUE = -22; + public static final int STATUS_DEAD_OBJECT = -32; + public static final int STATUS_INVALID_OPERATION = -38; + + /***************************************************************************** + * A ModuleProperties describes a given sound trigger hardware module + * managed by the native sound trigger service. Each module has a unique + * ID used to target any API call to this paricular module. Module + * properties are returned by listModules() method. + ****************************************************************************/ + public static class ModuleProperties { + /** Unique module ID provided by the native service */ + public final int id; + + /** human readable voice detection engine implementor */ + public final String implementor; + + /** human readable voice detection engine description */ + public final String description; + + /** Unique voice engine Id (changes with each version) */ + public final UUID uuid; + + /** Voice detection engine version */ + public final int version; + + /** Maximum number of active sound models */ + public final int maxSoundModels; + + /** Maximum number of key phrases */ + public final int maxKeyPhrases; + + /** Maximum number of users per key phrase */ + public final int maxUsers; + + /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */ + public final int recognitionModes; + + /** Supports seamless transition to capture mode after recognition */ + public final boolean supportsCaptureTransition; + + /** Maximum buffering capacity in ms if supportsCaptureTransition() is true */ + public final int maxBufferMs; + + /** Supports capture by other use cases while detection is active */ + public final boolean supportsConcurrentCapture; + + /** Rated power consumption when detection is active with TDB silence/sound/speech ratio */ + public final int powerConsumptionMw; + + ModuleProperties(int id, String implementor, String description, + String uuid, int version, int maxSoundModels, int maxKeyPhrases, + int maxUsers, int recognitionModes, boolean supportsCaptureTransition, + int maxBufferMs, boolean supportsConcurrentCapture, + int powerConsumptionMw) { + this.id = id; + this.implementor = implementor; + this.description = description; + this.uuid = UUID.fromString(uuid); + this.version = version; + this.maxSoundModels = maxSoundModels; + this.maxKeyPhrases = maxKeyPhrases; + this.maxUsers = maxUsers; + this.recognitionModes = recognitionModes; + this.supportsCaptureTransition = supportsCaptureTransition; + this.maxBufferMs = maxBufferMs; + this.supportsConcurrentCapture = supportsConcurrentCapture; + this.powerConsumptionMw = powerConsumptionMw; + } + } + + /***************************************************************************** + * A SoundModel describes the attributes and contains the binary data used by the hardware + * implementation to detect a particular sound pattern. + * A specialized version {@link KeyPhraseSoundModel} is defined for key phrase + * sound models. + ****************************************************************************/ + public static class SoundModel { + /** Undefined sound model type */ + public static final int TYPE_UNKNOWN = -1; + + /** Keyphrase sound model */ + public static final int TYPE_KEYPHRASE = 0; + + /** Sound model type (e.g. TYPE_KEYPHRASE); */ + public final int type; + + /** Opaque data. For use by vendor implementation and enrollment application */ + public final byte[] data; + + public SoundModel(int type, byte[] data) { + this.type = type; + this.data = data; + } + } + + /***************************************************************************** + * A KeyPhrase describes a key phrase that can be detected by a + * {@link KeyPhraseSoundModel} + ****************************************************************************/ + public static class KeyPhrase { + /** Recognition modes supported for this key phrase in the model */ + public final int recognitionModes; + + /** Locale of the keyphrase. JAVA Locale string e.g en_US */ + public final String locale; + + /** Key phrase text */ + public final String text; + + /** Number of users this key phrase has been trained for */ + public final int numUsers; + + public KeyPhrase(int recognitionModes, String locale, String text, int numUsers) { + this.recognitionModes = recognitionModes; + this.locale = locale; + this.text = text; + this.numUsers = numUsers; + } + } + + /***************************************************************************** + * A KeyPhraseSoundModel is a specialized {@link SoundModel} for key phrases. + * It contains data needed by the hardware to detect a certain number of key phrases + * and the list of corresponding {@link KeyPhrase} descriptors. + ****************************************************************************/ + public static class KeyPhraseSoundModel extends SoundModel { + /** Key phrases in this sound model */ + public final KeyPhrase[] keyPhrases; // keyword phrases in model + + public KeyPhraseSoundModel(byte[] data, KeyPhrase[] keyPhrases) { + super(TYPE_KEYPHRASE, data); + this.keyPhrases = keyPhrases; + } + } + + /** + * Modes for key phrase recognition + */ + /** Simple recognition of the key phrase */ + public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1; + /** Trigger only if one user is identified */ + public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2; + /** Trigger only if one user is authenticated */ + public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4; + + /** + * Status codes for {@link RecognitionEvent} + */ + /** Recognition success */ + public static final int RECOGNITION_STATUS_SUCCESS = 0; + /** Recognition aborted (e.g. capture preempted by anotehr use case */ + public static final int RECOGNITION_STATUS_ABORT = 1; + /** Recognition failure */ + public static final int RECOGNITION_STATUS_FAILURE = 2; + + /** + * A RecognitionEvent is provided by the + * {@link StatusListener#onRecognition(RecognitionEvent)} + * callback upon recognition success or failure. + */ + public static class RecognitionEvent { + /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */ + public final int status; + /** Sound Model corresponding to this event callback */ + public final int soundModelHandle; + /** True if it is possible to capture audio from this utterance buffered by the hardware */ + public final boolean captureAvailable; + /** Audio session ID to be used when capturing the utterance with an AudioRecord + * if captureAvailable() is true. */ + public final int captureSession; + /** Delay in ms between end of model detection and start of audio available for capture. + * A negative value is possible (e.g. if keyphrase is also available for capture) */ + public final int captureDelayMs; + /** Opaque data for use by system applications who know about voice engine internals, + * typically during enrollment. */ + public final byte[] data; + + RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, + int captureSession, int captureDelayMs, byte[] data) { + this.status = status; + this.soundModelHandle = soundModelHandle; + this.captureAvailable = captureAvailable; + this.captureSession = captureSession; + this.captureDelayMs = captureDelayMs; + this.data = data; + } + } + + /** + * Additional data conveyed by a {@link KeyPhraseRecognitionEvent} + * for a key phrase detection. + */ + public static class KeyPhraseRecognitionExtra { + /** Confidence level for each user defined in the key phrase in the same order as + * users in the key phrase. The confidence level is expressed in percentage (0% -100%) */ + public final int[] confidenceLevels; + + /** Recognition modes matched for this event */ + public final int recognitionModes; + + KeyPhraseRecognitionExtra(int[] confidenceLevels, int recognitionModes) { + this.confidenceLevels = confidenceLevels; + this.recognitionModes = recognitionModes; + } + } + + /** + * Specialized {@link RecognitionEvent} for a key phrase detection. + */ + public static class KeyPhraseRecognitionEvent extends RecognitionEvent { + /** Indicates if the key phrase is present in the buffered audio available for capture */ + public final KeyPhraseRecognitionExtra[] keyPhraseExtras; + + /** Additional data available for each recognized key phrases in the model */ + public final boolean keyPhraseInCapture; + + KeyPhraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, + int captureSession, int captureDelayMs, byte[] data, + boolean keyPhraseInCapture, KeyPhraseRecognitionExtra[] keyPhraseExtras) { + super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, data); + this.keyPhraseInCapture = keyPhraseInCapture; + this.keyPhraseExtras = keyPhraseExtras; + } + } + + /** + * Returns a list of descriptors for all harware modules loaded. + * @param modules A ModuleProperties array where the list will be returned. + * @return - {@link #STATUS_OK} in case of success + * - {@link #STATUS_ERROR} in case of unspecified error + * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission + * - {@link #STATUS_NO_INIT} if the native service cannot be reached + * - {@link #STATUS_BAD_VALUE} if modules is null + * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + */ + public static native int listModules(ArrayList <ModuleProperties> modules); + + /** + * Get an interface on a hardware module to control sound models and recognition on + * this module. + * @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory. + * @param listener {@link StatusListener} interface. Mandatory. + * @param handler the Handler that will receive the callabcks. Can be null if default handler + * is OK. + * @return a valid sound module in case of success or null in case of error. + */ + public static SoundTriggerModule attachModule(int moduleId, + StatusListener listener, + Handler handler) { + if (listener == null) { + return null; + } + SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler); + return module; + } + + /** + * Interface provided by the client application when attaching to a {@link SoundTriggerModule} + * to received recognition and error notifications. + */ + public static interface StatusListener { + /** + * Called when recognition succeeds of fails + */ + public abstract void onRecognition(RecognitionEvent event); + + /** + * Called when the sound trigger native service dies + */ + public abstract void onServiceDied(); + } +} diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java new file mode 100644 index 0000000..776f85d --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -0,0 +1,192 @@ +/* + * 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.soundtrigger; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import java.lang.ref.WeakReference; +import java.util.UUID; + +/** + * The SoundTriggerModule provides APIs to control sound models and sound detection + * on a given sound trigger hardware module. + * + * @hide + */ +public class SoundTriggerModule { + private long mNativeContext; + + private int mId; + private NativeEventHandlerDelegate mEventHandlerDelegate; + + private static final int EVENT_RECOGNITION = 1; + private static final int EVENT_SERVICE_DIED = 2; + + SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) { + mId = moduleId; + mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler); + native_setup(new WeakReference<SoundTriggerModule>(this)); + } + private native void native_setup(Object module_this); + + @Override + protected void finalize() { + native_finalize(); + } + private native void native_finalize(); + + /** + * Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called + * anymore and associated resources will be released. + * */ + public native void detach(); + + /** + * Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in + * order to start listening to a key phrase in this model. + * @param model The sound model to load. + * @param soundModelHandle an array of int where the sound model handle will be returned. + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if parameters are invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle); + + /** + * Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition + * @param soundModelHandle The sound model handle + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + */ + public native int unloadSoundModel(int soundModelHandle); + + /** + * Start listening to all key phrases in a {@link SoundTrigger.SoundModel}. + * Recognition must be restarted after each callback (success or failure) received on + * the {@link SoundTrigger.StatusListener}. + * @param soundModelHandle The sound model handle to start listening to + * @param data Opaque data for use by the implementation for this recognition + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int startRecognition(int soundModelHandle, byte[] data); + + /** + * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel} + * @param soundModelHandle The sound model handle to stop listening to + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int stopRecognition(int soundModelHandle); + + private class NativeEventHandlerDelegate { + private final Handler mHandler; + + NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener, + Handler handler) { + // find the looper for our new event handler + Looper looper; + if (handler != null) { + looper = handler.getLooper(); + } else { + looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + } + + // construct the event handler with this looper + if (looper != null) { + // implement the event handler delegate + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case EVENT_RECOGNITION: + if (listener != null) { + listener.onRecognition( + (SoundTrigger.RecognitionEvent)msg.obj); + } + break; + case EVENT_SERVICE_DIED: + if (listener != null) { + listener.onServiceDied(); + } + break; + default: + break; + } + } + }; + } else { + mHandler = null; + } + } + + Handler handler() { + return mHandler; + } + } + + @SuppressWarnings("unused") + private static void postEventFromNative(Object module_ref, + int what, int arg1, int arg2, Object obj) { + SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get(); + if (module == null) { + return; + } + + NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate; + if (delegate != null) { + Handler handler = delegate.handler(); + if (handler != null) { + Message m = handler.obtainMessage(what, arg1, arg2, obj); + handler.sendMessage(m); + } + } + } +} + diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 7faf8ef..b68ce36 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -68,6 +68,7 @@ import java.util.HashMap; */ public class ConnectivityManager { private static final String TAG = "ConnectivityManager"; + private static final boolean LEGACY_DBG = true; // STOPSHIP /** * A change in network connectivity has occurred. A default connection has either @@ -823,6 +824,14 @@ public class ConnectivityManager { NetworkRequest request = null; synchronized (sLegacyRequests) { + if (LEGACY_DBG) { + Log.d(TAG, "Looking for legacyRequest for netCap with hash: " + netCap + " (" + + netCap.hashCode() + ")"); + Log.d(TAG, "sLegacyRequests has:"); + for (NetworkCapabilities nc : sLegacyRequests.keySet()) { + Log.d(TAG, " " + nc + " (" + nc.hashCode() + ")"); + } + } LegacyRequest l = sLegacyRequests.get(netCap); if (l != null) { Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); @@ -837,7 +846,7 @@ public class ConnectivityManager { request = requestNetworkForFeatureLocked(netCap); } if (request != null) { - Log.d(TAG, "starting startUsingNeworkFeature for request " + request); + Log.d(TAG, "starting startUsingNetworkFeature for request " + request); return PhoneConstants.APN_REQUEST_STARTED; } else { Log.d(TAG, " request Failed"); @@ -898,6 +907,7 @@ public class ConnectivityManager { case NetworkCapabilities.NET_CAPABILITY_IMS: case NetworkCapabilities.NET_CAPABILITY_RCS: case NetworkCapabilities.NET_CAPABILITY_XCAP: + case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED: //there by default continue; default: // At least one capability usually provided by unrestricted diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index dd9c39f..b7af374 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.LinkAddress; +import android.net.RouteInfo; /** * Callback class for receiving events from an INetworkManagementService @@ -98,4 +99,14 @@ interface INetworkManagementEventObserver { * @param servers The IP addresses of the DNS servers. */ void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers); + + /** + * A route has been added or updated. + */ + void routeUpdated(in RouteInfo route); + + /** + * A route has been removed. + */ + void routeRemoved(in RouteInfo route); } diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index a14d13f..f1fa3eb 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pair; import java.net.InetAddress; import java.net.UnknownHostException; @@ -46,9 +47,18 @@ public final class IpPrefix implements Parcelable { private final byte[] address; // network byte order private final int prefixLength; + private void checkAndMaskAddressAndPrefixLength() { + if (address.length != 4 && address.length != 16) { + throw new IllegalArgumentException( + "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); + } + NetworkUtils.maskRawAddress(address, prefixLength); + } + /** * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in - * network byte order and a prefix length. + * network byte order and a prefix length. Silently truncates the address to the prefix length, + * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}. * * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long. * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). @@ -56,24 +66,46 @@ public final class IpPrefix implements Parcelable { * @hide */ public IpPrefix(byte[] address, int prefixLength) { - if (address.length != 4 && address.length != 16) { - throw new IllegalArgumentException( - "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); - } - if (prefixLength < 0 || prefixLength > (address.length * 8)) { - throw new IllegalArgumentException("IpPrefix with " + address.length + - " bytes has invalid prefix length " + prefixLength); - } this.address = address.clone(); this.prefixLength = prefixLength; - // TODO: Validate that the non-prefix bits are zero + checkAndMaskAddressAndPrefixLength(); } /** + * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently + * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently + * converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ public IpPrefix(InetAddress address, int prefixLength) { - this(address.getAddress(), prefixLength); + // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, + // which is unnecessary because getAddress() already returns a clone. + this.address = address.getAddress(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64". + * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24} + * is silently converted to {@code 192.0.2.0/24}. + * + * @param prefix the prefix to parse + * + * @hide + */ + public IpPrefix(String prefix) { + // We don't reuse the (InetAddress, int) constructor because "error: call to this must be + // first statement in constructor". We could factor out setting the member variables to an + // init() method, but if we did, then we'd have to make the members non-final, or "error: + // cannot assign a value to final variable address". So we just duplicate the code here. + Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix); + this.address = ipAndMask.first.getAddress(); + this.prefixLength = ipAndMask.second; + checkAndMaskAddressAndPrefixLength(); } /** @@ -129,7 +161,7 @@ public final class IpPrefix implements Parcelable { } /** - * Returns the prefix length of this {@code IpAddress}. + * Returns the prefix length of this {@code IpPrefix}. * * @return the prefix length. */ @@ -138,6 +170,20 @@ public final class IpPrefix implements Parcelable { } /** + * Returns a string representation of this {@code IpPrefix}. + * + * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::"}. + */ + public String toString() { + try { + return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength; + } catch(UnknownHostException e) { + // Cosmic rays? + throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e); + } + } + + /** * Implement the Parcelable interface. */ public int describeContents() { diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index 5246078..f9a25f9 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pair; import java.net.Inet4Address; import java.net.InetAddress; @@ -166,23 +167,9 @@ public class LinkAddress implements Parcelable { * @hide */ public LinkAddress(String address, int flags, int scope) { - InetAddress inetAddress = null; - int prefixLength = -1; - try { - String [] pieces = address.split("/", 2); - prefixLength = Integer.parseInt(pieces[1]); - inetAddress = InetAddress.parseNumericAddress(pieces[0]); - } catch (NullPointerException e) { // Null string. - } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. - } catch (NumberFormatException e) { // Non-numeric prefix. - } catch (IllegalArgumentException e) { // Invalid IP address. - } - - if (inetAddress == null || prefixLength == -1) { - throw new IllegalArgumentException("Bad LinkAddress params " + address); - } - - init(inetAddress, prefixLength, flags, scope); + // This may throw an IllegalArgumentException; catching it is the caller's responsibility. + Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address); + init(ipAndMask.first, ipAndMask.second, flags, scope); } /** diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 8eefa0f..e7184ed 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.List; +import java.util.Objects; /** * Describes the properties of a network link. @@ -334,15 +335,17 @@ public final class LinkProperties implements Parcelable { } /** - * Adds a {@link RouteInfo} to this {@code LinkProperties}. If the {@link RouteInfo} - * had an interface name set and that differs from the interface set for this - * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The - * proper course is to add either un-named or properly named {@link RouteInfo}. + * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the + * {@link RouteInfo} had an interface name set and that differs from the interface set for this + * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The proper + * course is to add either un-named or properly named {@link RouteInfo}. * * @param route A {@link RouteInfo} to add to this object. + * @return {@code false} if the route was already present, {@code true} if it was added. + * * @hide */ - public void addRoute(RouteInfo route) { + public boolean addRoute(RouteInfo route) { if (route != null) { String routeIface = route.getInterface(); if (routeIface != null && !routeIface.equals(mIfaceName)) { @@ -350,8 +353,28 @@ public final class LinkProperties implements Parcelable { "Route added with non-matching interface: " + routeIface + " vs. " + mIfaceName); } - mRoutes.add(routeWithInterface(route)); + route = routeWithInterface(route); + if (!mRoutes.contains(route)) { + mRoutes.add(route); + return true; + } } + return false; + } + + /** + * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must + * specify an interface and the interface must match the interface of this + * {@code LinkProperties}, or it will not be removed. + * + * @return {@code true} if the route was removed, {@code false} if it was not present. + * + * @hide + */ + public boolean removeRoute(RouteInfo route) { + return route != null && + Objects.equals(mIfaceName, route.getInterface()) && + mRoutes.remove(route); } /** diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 535bbe2..8142670 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -699,15 +699,15 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker { break; } - try { - if (enable) { - return mPhoneService.enableApnType(apnType); - } else { - return mPhoneService.disableApnType(apnType); - } - } catch (RemoteException e) { - if (retry == 0) getPhoneService(true); - } +// try { +// if (enable) { +// return mPhoneService.enableApnType(apnType); +// } else { +// return mPhoneService.disableApnType(apnType); +// } +// } catch (RemoteException e) { +// if (retry == 0) getPhoneService(true); +// } } loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 318aabe..0a422c6 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -190,13 +190,20 @@ public class Network implements Parcelable { } }; + @Override public boolean equals(Object obj) { if (obj instanceof Network == false) return false; Network other = (Network)obj; return this.netId == other.netId; } + @Override public int hashCode() { return netId * 11; } + + @Override + public String toString() { + return Integer.toString(netId); + } } diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 7e8b1f1..3d0874b 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -80,6 +80,11 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; + /* centralize place where base network score, and network score scaling, will be + * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE + */ + public static final int WIFI_BASE_SCORE = 60; + /** * Sent by the NetworkAgent to ConnectivityService to pass the current * network score. diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index b02f88e..15c0a71 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.Locale; import android.util.Log; +import android.util.Pair; + /** * Native methods for managing network interfaces. @@ -218,24 +220,17 @@ public class NetworkUtils { } /** - * Get InetAddress masked with prefixLength. Will never return null. - * @param IP address which will be masked with specified prefixLength - * @param prefixLength the prefixLength used to mask the IP + * Masks a raw IP address byte array with the specified prefix length. */ - public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { - if (address == null) { - throw new RuntimeException("getNetworkPart doesn't accept null address"); - } - - byte[] array = address.getAddress(); - + public static void maskRawAddress(byte[] array, int prefixLength) { if (prefixLength < 0 || prefixLength > array.length * 8) { - throw new RuntimeException("getNetworkPart - bad prefixLength"); + throw new RuntimeException("IP address with " + array.length + + " bytes has invalid prefix length " + prefixLength); } int offset = prefixLength / 8; - int reminder = prefixLength % 8; - byte mask = (byte)(0xFF << (8 - reminder)); + int remainder = prefixLength % 8; + byte mask = (byte)(0xFF << (8 - remainder)); if (offset < array.length) array[offset] = (byte)(array[offset] & mask); @@ -244,6 +239,16 @@ public class NetworkUtils { for (; offset < array.length; offset++) { array[offset] = 0; } + } + + /** + * Get InetAddress masked with prefixLength. Will never return null. + * @param address the IP address to mask with + * @param prefixLength the prefixLength used to mask the IP + */ + public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { + byte[] array = address.getAddress(); + maskRawAddress(array, prefixLength); InetAddress netPart = null; try { @@ -255,6 +260,30 @@ public class NetworkUtils { } /** + * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". + * @hide + */ + public static Pair<InetAddress, Integer> parseIpAndMask(String ipAndMaskString) { + InetAddress address = null; + int prefixLength = -1; + try { + String[] pieces = ipAndMaskString.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + address = InetAddress.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } + + if (address == null || prefixLength == -1) { + throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString); + } + + return new Pair<InetAddress, Integer>(address, prefixLength); + } + + /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/net/PSKKeyManager.java b/core/java/android/net/PSKKeyManager.java new file mode 100644 index 0000000..92dd141 --- /dev/null +++ b/core/java/android/net/PSKKeyManager.java @@ -0,0 +1,186 @@ +/* + * 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.net; + +import java.net.Socket; +import javax.crypto.SecretKey; +import javax.net.ssl.SSLEngine; + +/** + * Provider of key material for pre-shared key (PSK) key exchange used in TLS-PSK cipher suites. + * + * <h3>Overview of TLS-PSK</h3> + * + * <p>TLS-PSK is a set of TLS/SSL cipher suites which rely on a symmetric pre-shared key (PSK) to + * secure the TLS/SSL connection and mutually authenticate its peers. These cipher suites may be + * a more natural fit compared to conventional public key based cipher suites in some scenarios + * where communication between peers is bootstrapped via a separate step (for example, a pairing + * step) and requires both peers to authenticate each other. In such scenarios a symmetric key (PSK) + * can be exchanged during the bootstrapping step, removing the need to generate and exchange public + * key pairs and X.509 certificates.</p> + * + * <p>When a TLS-PSK cipher suite is used, both peers have to use the same key for the TLS/SSL + * handshake to succeed. Thus, both peers are implicitly authenticated by a successful handshake. + * This removes the need to use a {@code TrustManager} in conjunction with this {@code KeyManager}. + * </p> + * + * <h3>Supporting multiple keys</h3> + * + * <p>A peer may have multiple keys to choose from. To help choose the right key, during the handshake + * the server can provide a <em>PSK identity hint</em> to the client, and the client can provide a + * <em>PSK identity</em> to the server. The contents of these two pieces of information are specific + * to application-level protocols.</p> + * + * <p><em>NOTE: Both the PSK identity hint and the PSK identity are transmitted in cleartext. + * Moreover, these data are received and processed prior to peer having been authenticated. Thus, + * they must not contain or leak key material or other sensitive information, and should be + * treated (e.g., parsed) with caution, as untrusted data.</em></p> + * + * <p>The high-level flow leading to peers choosing a key during TLS/SSL handshake is as follows: + * <ol> + * <li>Server receives a handshake request from client. + * <li>Server replies, optionally providing a PSK identity hint to client.</li> + * <li>Client chooses the key.</li> + * <li>Client provides a PSK identity of the chosen key to server.</li> + * <li>Server chooses the key.</li> + * </ol></p> + * + * <p>In the flow above, either peer can signal that they do not have a suitable key, in which case + * the the handshake will be aborted immediately. This may enable a network attacker who does not + * know the key to learn which PSK identity hints or PSK identities are supported. If this is a + * concern then a randomly generated key should be used in the scenario where no key is available. + * This will lead to the handshake aborting later, due to key mismatch -- same as in the scenario + * where a key is available -- making it appear to the attacker that all PSK identity hints and PSK + * identities are supported.</p> + * + * <h3>Maximum sizes</h3> + * + * <p>The maximum supported sizes are as follows: + * <ul> + * <li>256 bytes for keys (see {@link #MAX_KEY_LENGTH_BYTES}),</li> + * <li>128 bytes for PSK identity and PSK identity hint (in modified UTF-8 representation) (see + * {@link #MAX_IDENTITY_LENGTH_BYTES} and {@link #MAX_IDENTITY_HINT_LENGTH_BYTES}).</li> + * </ul></p> + * + * <h3>Example</h3> + * The following example illustrates how to create an {@code SSLContext} which enables the use of + * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained + * from it. + * <pre> {@code + * PSKKeyManager myPskKeyManager = ...; + * + * SSLContext sslContext = SSLContext.getInstance("TLS"); + * sslContext.init( + * new KeyManager[] {myPskKeyManager}, + * new TrustManager[0], // No TrustManagers needed in TLS-PSK + * null // Use the default source of entropy + * ); + * + * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...); + * // Enable a TLS-PSK cipher suite (no TLS-PSK cipher suites are enabled by default) + * sslSocket.setEnabledCipherSuites(new String[] {"TLS_PSK_WITH_AES_128_CBC_SHA"}); + * sslSocket.startHandshake(); + * }</pre> + */ +public interface PSKKeyManager extends com.android.org.conscrypt.PSKKeyManager { + // IMPLEMENTATION DETAILS: This class exists only because the default implemenetation of the + // TLS/SSL JSSE provider (currently Conscrypt) cannot depend on Android framework classes. + // As a result, this framework class simply extends the PSKKeyManager interface from Conscrypt + // without adding any new methods or fields. Moreover, for technical reasons (Conscrypt classes + // are "hidden") this class replaces the Javadoc of Conscrypt's PSKKeyManager. + + /** + * Maximum supported length (in bytes) for PSK identity hint (in modified UTF-8 representation). + */ + int MAX_IDENTITY_HINT_LENGTH_BYTES = + com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_HINT_LENGTH_BYTES; + + /** Maximum supported length (in bytes) for PSK identity (in modified UTF-8 representation). */ + int MAX_IDENTITY_LENGTH_BYTES = + com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_LENGTH_BYTES; + + /** Maximum supported length (in bytes) for PSK. */ + int MAX_KEY_LENGTH_BYTES = com.android.org.conscrypt.PSKKeyManager.MAX_KEY_LENGTH_BYTES; + + /** + * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided + * socket. + * + * @return PSK identity hint to be provided to the client or {@code null} to provide no hint. + */ + @Override + String chooseServerKeyIdentityHint(Socket socket); + + /** + * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided + * engine. + * + * @return PSK identity hint to be provided to the client or {@code null} to provide no hint. + */ + @Override + String chooseServerKeyIdentityHint(SSLEngine engine); + + /** + * Gets the PSK identity to report to the server to help agree on the PSK for the provided + * socket. + * + * @param identityHint identity hint provided by the server or {@code null} if none provided. + * + * @return PSK identity to provide to the server. {@code null} is permitted but will be + * converted into an empty string. + */ + @Override + String chooseClientKeyIdentity(String identityHint, Socket socket); + + /** + * Gets the PSK identity to report to the server to help agree on the PSK for the provided + * engine. + * + * @param identityHint identity hint provided by the server or {@code null} if none provided. + * + * @return PSK identity to provide to the server. {@code null} is permitted but will be + * converted into an empty string. + */ + @Override + String chooseClientKeyIdentity(String identityHint, SSLEngine engine); + + /** + * Gets the PSK to use for the provided socket. + * + * @param identityHint identity hint provided by the server to help select the key or + * {@code null} if none provided. + * @param identity identity provided by the client to help select the key. + * + * @return key or {@code null} to signal to peer that no suitable key is available and to abort + * the handshake. + */ + @Override + SecretKey getKey(String identityHint, String identity, Socket socket); + + /** + * Gets the PSK to use for the provided engine. + * + * @param identityHint identity hint provided by the server to help select the key or + * {@code null} if none provided. + * @param identity identity provided by the client to help select the key. + * + * @return key or {@code null} to signal to peer that no suitable key is available and to abort + * the handshake. + */ + @Override + SecretKey getKey(String identityHint, String identity, SSLEngine engine); +} diff --git a/core/java/android/net/ProxyDataTracker.java b/core/java/android/net/ProxyDataTracker.java index 573a8f8..a578383 100644 --- a/core/java/android/net/ProxyDataTracker.java +++ b/core/java/android/net/ProxyDataTracker.java @@ -48,6 +48,7 @@ public class ProxyDataTracker extends BaseNetworkStateTracker { // TODO: investigate how to get these DNS addresses from the system. private static final String DNS1 = "8.8.8.8"; private static final String DNS2 = "8.8.4.4"; + private static final String INTERFACE_NAME = "ifb0"; private static final String REASON_ENABLED = "enabled"; private static final String REASON_DISABLED = "disabled"; private static final String REASON_PROXY_DOWN = "proxy_down"; @@ -107,10 +108,11 @@ public class ProxyDataTracker extends BaseNetworkStateTracker { mNetworkCapabilities = new NetworkCapabilities(); mNetworkInfo.setIsAvailable(true); try { - mLinkProperties.addDnsServer(InetAddress.getByName(DNS1)); - mLinkProperties.addDnsServer(InetAddress.getByName(DNS2)); + mLinkProperties.addDnsServer(InetAddress.getByName(DNS1)); + mLinkProperties.addDnsServer(InetAddress.getByName(DNS2)); + mLinkProperties.setInterfaceName(INTERFACE_NAME); } catch (UnknownHostException e) { - Log.e(TAG, "Could not add DNS address", e); + Log.e(TAG, "Could not add DNS address", e); } } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index c2b888c..63d6cd3 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -248,7 +248,7 @@ public final class RouteInfo implements Parcelable { * Retrieves the gateway or next hop {@link InetAddress} for this route. * * @return {@link InetAddress} specifying the gateway or next hop. This may be - & {@code null} for a directly-connected route." + * {@code null} for a directly-connected route." */ public InetAddress getGateway() { return mGateway; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 9218c11..53e87a6 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.INfcLockscreenDispatch; import android.os.Bundle; /** @@ -52,4 +53,6 @@ interface INfcAdapter void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras); void setP2pModes(int initatorModes, int targetModes); + + void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList); } diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl new file mode 100644 index 0000000..27e9a8c --- /dev/null +++ b/core/java/android/nfc/INfcLockscreenDispatch.aidl @@ -0,0 +1,12 @@ +package android.nfc; + +import android.nfc.Tag; + +/** + * @hide + */ +interface INfcLockscreenDispatch { + + boolean onTagDetected(in Tag tag); + +} diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index dd8e41c..be098a8 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -380,6 +380,16 @@ public final class NfcAdapter { public Uri[] createBeamUris(NfcEvent event); } + + /** + * A callback to be invoked when an application has registered for receiving + * tags at the lockscreen. + */ + public interface NfcLockscreenDispatch { + public boolean onTagDetected(Tag tag); + } + + /** * Helper to check if this device has FEATURE_NFC, but without using * a context. @@ -1417,6 +1427,26 @@ public final class NfcAdapter { } } + public boolean registerLockscreenDispatch(final NfcLockscreenDispatch lockscreenDispatch, + String[] techList) { + try { + sService.registerLockscreenDispatch(new INfcLockscreenDispatch.Stub() { + @Override + public boolean onTagDetected(Tag tag) throws RemoteException { + return lockscreenDispatch.onTagDetected(tag); + } + }, Tag.techListFromStrings(techList)); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to register LockscreenDispatch", e); + return false; + } + + return true; + } + /** * @hide */ diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index 0d261d1..43be702 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -35,6 +35,7 @@ import android.os.RemoteException; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; /** * Represents an NFC tag that has been discovered. @@ -195,6 +196,41 @@ public final class Tag implements Parcelable { return strings; } + static int[] techListFromStrings(String[] techStringList) throws IllegalArgumentException { + if (techStringList == null) { + throw new IllegalArgumentException("List cannot be null"); + } + int[] techIntList = new int[techStringList.length]; + HashMap<String, Integer> stringToCodeMap = getTechStringToCodeMap(); + for (int i = 0; i < techStringList.length; i++) { + Integer code = stringToCodeMap.get(techStringList[i]); + + if (code == null) { + throw new IllegalArgumentException("Unknown tech type " + techStringList[i]); + } + + techIntList[i] = code.intValue(); + } + return techIntList; + } + + private static HashMap<String, Integer> getTechStringToCodeMap() { + HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>(); + + techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP); + techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC); + techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT); + techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF); + techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE); + techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A); + techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B); + techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F); + techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V); + techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE); + + return techStringToCodeMap; + } + /** * For use by NfcService only. * @hide diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java new file mode 100644 index 0000000..f3a95b9 --- /dev/null +++ b/core/java/android/os/BatteryManagerInternal.java @@ -0,0 +1,49 @@ +/* + * 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.os; + +/** + * Battery manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class BatteryManagerInternal { + /** + * Returns true if the device is plugged into any of the specified plug types. + */ + public abstract boolean isPowered(int plugTypeSet); + + /** + * Returns the current plug type. + */ + public abstract int getPlugType(); + + /** + * Returns battery level as a percentage. + */ + public abstract int getBatteryLevel(); + + /** + * Returns whether we currently consider the battery level to be low. + */ + public abstract boolean getBatteryLevelLow(); + + /** + * Returns a non-zero value if an unsupported charger is attached. + */ + public abstract int getInvalidCharger(); +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e627d49..f7d2bfd 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -171,6 +171,10 @@ public abstract class BatteryStats implements Parcelable { 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 WIFI_SUPPL_STATE_TIME_DATA = "wsst"; + private static final String WIFI_SUPPL_STATE_COUNT_DATA = "wssc"; + private static final String WIFI_SIGNAL_STRENGTH_TIME_DATA = "wsgt"; + private static final String WIFI_SIGNAL_STRENGTH_COUNT_DATA = "wsgc"; 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"; @@ -563,8 +567,8 @@ public abstract class BatteryStats implements Parcelable { 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_SHIFT = 3; - public static final int STATE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_SIGNAL_STRENGTH_SHIFT; + public static final int STATE_PHONE_SIGNAL_STRENGTH_SHIFT = 3; + public static final int STATE_PHONE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_PHONE_SIGNAL_STRENGTH_SHIFT; // Constants from ServiceState.STATE_* public static final int STATE_PHONE_STATE_SHIFT = 6; public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT; @@ -582,7 +586,6 @@ public abstract class BatteryStats implements Parcelable { 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_SENSOR_ON_FLAG = 1<<23; @@ -591,17 +594,30 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE_SCREEN_ON_FLAG = 1<<20; public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18; - public static final int STATE_WIFI_ON_FLAG = 1<<17; public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16; public static final int MOST_INTERESTING_STATES = STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG - | STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG; + | STATE_PHONE_IN_CALL_FLAG | STATE_BLUETOOTH_ON_FLAG; public int states; - public static final int STATE2_VIDEO_ON_FLAG = 1<<0; - public static final int STATE2_LOW_POWER_FLAG = 1<<1; + // Constants from WIFI_SUPPL_STATE_* + public static final int STATE2_WIFI_SUPPL_STATE_SHIFT = 0; + public static final int STATE2_WIFI_SUPPL_STATE_MASK = 0xf; + // Values for NUM_WIFI_SIGNAL_STRENGTH_BINS + public static final int STATE2_WIFI_SIGNAL_STRENGTH_SHIFT = 4; + public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK = + 0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT; + + public static final int STATE2_LOW_POWER_FLAG = 1<<31; + public static final int STATE2_VIDEO_ON_FLAG = 1<<30; + public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29; + public static final int STATE2_WIFI_ON_FLAG = 1<<28; + + public static final int MOST_INTERESTING_STATES2 = + STATE2_LOW_POWER_FLAG | STATE2_WIFI_ON_FLAG; + public int states2; // The wake lock that was acquired at this point. @@ -1005,7 +1021,7 @@ public abstract class BatteryStats implements Parcelable { }; public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5; - + /** * Returns the time in microseconds that the screen has been on with * the given brightness @@ -1153,6 +1169,34 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getPhoneDataConnectionCount(int dataType, int which); + public static final int WIFI_SUPPL_STATE_INVALID = 0; + public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1; + public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2; + public static final int WIFI_SUPPL_STATE_INACTIVE = 3; + public static final int WIFI_SUPPL_STATE_SCANNING = 4; + public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5; + public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6; + public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7; + public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8; + public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9; + public static final int WIFI_SUPPL_STATE_COMPLETED = 10; + public static final int WIFI_SUPPL_STATE_DORMANT = 11; + public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12; + + public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED+1; + + static final String[] WIFI_SUPPL_STATE_NAMES = { + "invalid", "disconn", "disabled", "inactive", "scanning", + "authenticating", "associating", "associated", "4-way-handshake", + "group-handshake", "completed", "dormant", "uninit" + }; + + static final String[] WIFI_SUPPL_STATE_SHORT_NAMES = { + "inv", "dsc", "dis", "inact", "scan", + "auth", "ascing", "asced", "4-way", + "group", "compl", "dorm", "uninit" + }; + public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] { new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"), @@ -1163,13 +1207,11 @@ public abstract class BatteryStats implements Parcelable { 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_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", "Pcn", @@ -1178,11 +1220,10 @@ public abstract class BatteryStats implements Parcelable { 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_PHONE_SIGNAL_STRENGTH_MASK, + HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT, "phone_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), @@ -1190,8 +1231,17 @@ public abstract class BatteryStats implements Parcelable { public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS = new BitDescription[] { - new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"), new BitDescription(HistoryItem.STATE2_LOW_POWER_FLAG, "low_power", "lp"), + new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"), + new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Wr"), + new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"), + new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK, + HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss", + new String[] { "0", "1", "2", "3", "4" }, + new String[] { "0", "1", "2", "3", "4" }), + new BitDescription(HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK, + HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT, "wifi_suppl", "Wsp", + WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES), }; public static final String[] HISTORY_EVENT_NAMES = new String[] { @@ -1250,6 +1300,40 @@ public abstract class BatteryStats implements Parcelable { public abstract int getWifiStateCount(int wifiState, int which); /** + * Returns the time in microseconds that the wifi supplicant has been + * in a given state. + * + * {@hide} + */ + public abstract long getWifiSupplStateTime(int state, long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that the wifi supplicant has transitioned + * to a given state. + * + * {@hide} + */ + public abstract int getWifiSupplStateCount(int state, int which); + + public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5; + + /** + * Returns the time in microseconds that WIFI has been running with + * the given signal strength. + * + * {@hide} + */ + public abstract long getWifiSignalStrengthTime(int strengthBin, + long elapsedRealtimeUs, int which); + + /** + * Returns the number of times WIFI has entered the given signal strength. + * + * {@hide} + */ + public abstract int getWifiSignalStrengthCount(int strengthBin, int which); + + /** * Returns the time in microseconds that bluetooth has been on while the device was * running on battery. * @@ -1780,6 +1864,28 @@ public abstract class BatteryStats implements Parcelable { } dumpLine(pw, 0 /* uid */, category, WIFI_STATE_COUNT_DATA, args); + // Dump wifi suppl state stats + args = new Object[NUM_WIFI_SUPPL_STATES]; + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + args[i] = getWifiSupplStateTime(i, rawRealtime, which) / 1000; + } + dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_TIME_DATA, args); + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + args[i] = getWifiSupplStateCount(i, which); + } + dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_COUNT_DATA, args); + + // Dump wifi signal strength stats + args = new Object[NUM_WIFI_SIGNAL_STRENGTH_BINS]; + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + args[i] = getWifiSignalStrengthTime(i, rawRealtime, which) / 1000; + } + dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_TIME_DATA, args); + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + args[i] = getWifiSignalStrengthCount(i, which); + } + dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); + // Dump bluetooth state stats args = new Object[NUM_BLUETOOTH_STATES]; for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { @@ -2254,7 +2360,7 @@ public abstract class BatteryStats implements Parcelable { pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")"); sb.setLength(0); sb.append(prefix); - sb.append(" Signal levels:"); + sb.append(" Phone signal levels:"); didOne = false; for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { final long time = getPhoneSignalStrengthTime(i, rawRealtime, which); @@ -2372,7 +2478,55 @@ public abstract class BatteryStats implements Parcelable { sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); - sb.append(getPhoneDataConnectionCount(i, which)); + sb.append(getWifiStateCount(i, which)); + sb.append("x"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Wifi supplicant states:"); + didOne = false; + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + final long time = getWifiSupplStateTime(i, rawRealtime, which); + if (time == 0) { + continue; + } + sb.append("\n "); + didOne = true; + sb.append(WIFI_SUPPL_STATE_NAMES[i]); + sb.append(" "); + formatTimeMs(sb, time/1000); + sb.append("("); + sb.append(formatRatioLocked(time, whichBatteryRealtime)); + sb.append(") "); + sb.append(getWifiSupplStateCount(i, which)); + sb.append("x"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Wifi signal levels:"); + didOne = false; + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + final long time = getWifiSignalStrengthTime(i, rawRealtime, which); + if (time == 0) { + continue; + } + sb.append("\n "); + sb.append(prefix); + didOne = true; + sb.append("level("); + sb.append(i); + sb.append(") "); + formatTimeMs(sb, time/1000); + sb.append("("); + sb.append(formatRatioLocked(time, whichBatteryRealtime)); + sb.append(") "); + sb.append(getWifiSignalStrengthCount(i, which)); sb.append("x"); } if (!didOne) sb.append(" (no activity)"); @@ -3039,6 +3193,16 @@ public abstract class BatteryStats implements Parcelable { long lastTime = -1; long firstTime = -1; + void reset() { + oldState = oldState2 = 0; + oldLevel = -1; + oldStatus = -1; + oldHealth = -1; + oldPlug = -1; + oldTemp = -1; + oldVolt = -1; + } + public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin, boolean verbose) { if (!checkin) { @@ -3062,6 +3226,7 @@ public abstract class BatteryStats implements Parcelable { pw.print(":"); } pw.println("START"); + reset(); } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME || rec.cmd == HistoryItem.CMD_RESET) { if (checkin) { @@ -3069,6 +3234,7 @@ public abstract class BatteryStats implements Parcelable { } if (rec.cmd == HistoryItem.CMD_RESET) { pw.print("RESET:"); + reset(); } pw.print("TIME:"); if (checkin) { diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index e42c3fe..3252d19 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -692,10 +692,12 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { /** * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associatd with the given key. * * @param key a String, or null - * @param defaultValue Value to return if key does not exist + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. * @return the CharSequence value associated with the given key, or defaultValue * if no valid CharSequence object is currently mapped to that key. */ diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e84b695..975bfc2 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -292,6 +292,17 @@ public class Environment { } /** + * Returns the config directory for a user. This is for use by system services to store files + * relating to the user which should be readable by any app running as that user. + * + * @hide + */ + public static File getUserConfigDirectory(int userId) { + return new File(new File(new File( + getDataDirectory(), "misc"), "user"), Integer.toString(userId)); + } + + /** * Returns whether the Encrypted File System feature is enabled on the device or not. * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> * if disabled. diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java new file mode 100644 index 0000000..7f8bc9f --- /dev/null +++ b/core/java/android/os/FileBridge.java @@ -0,0 +1,165 @@ +/* + * 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.os; + +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; + +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import libcore.io.IoBridge; +import libcore.io.IoUtils; +import libcore.io.Memory; +import libcore.io.Streams; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.io.SyncFailedException; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Simple bridge that allows file access across process boundaries without + * returning the underlying {@link FileDescriptor}. This is useful when the + * server side needs to strongly assert that a client side is completely + * hands-off. + * + * @hide + */ +public class FileBridge extends Thread { + private static final String TAG = "FileBridge"; + + // TODO: consider extending to support bidirectional IO + + private static final int MSG_LENGTH = 8; + + /** CMD_WRITE [len] [data] */ + private static final int CMD_WRITE = 1; + /** CMD_FSYNC */ + private static final int CMD_FSYNC = 2; + + private FileDescriptor mTarget; + + private final FileDescriptor mServer = new FileDescriptor(); + private final FileDescriptor mClient = new FileDescriptor(); + + private volatile boolean mClosed; + + public FileBridge() { + try { + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient); + } catch (ErrnoException e) { + throw new RuntimeException("Failed to create bridge"); + } + } + + public boolean isClosed() { + return mClosed; + } + + public void setTargetFile(FileDescriptor target) { + mTarget = target; + } + + public FileDescriptor getClientSocket() { + return mClient; + } + + @Override + public void run() { + final byte[] temp = new byte[8192]; + try { + while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { + final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); + + if (cmd == CMD_WRITE) { + // Shuttle data into local file + int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); + while (len > 0) { + int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len)); + IoBridge.write(mTarget, temp, 0, n); + len -= n; + } + + } else if (cmd == CMD_FSYNC) { + // Sync and echo back to confirm + Os.fsync(mTarget); + IoBridge.write(mServer, temp, 0, MSG_LENGTH); + } + } + + // Client was closed; one last fsync + Os.fsync(mTarget); + + } catch (ErrnoException e) { + Log.e(TAG, "Failed during bridge: ", e); + } catch (IOException e) { + Log.e(TAG, "Failed during bridge: ", e); + } finally { + IoUtils.closeQuietly(mTarget); + IoUtils.closeQuietly(mServer); + IoUtils.closeQuietly(mClient); + mClosed = true; + } + } + + public static class FileBridgeOutputStream extends OutputStream { + private final FileDescriptor mClient; + private final byte[] mTemp = new byte[MSG_LENGTH]; + + public FileBridgeOutputStream(FileDescriptor client) { + mClient = client; + } + + @Override + public void close() throws IOException { + IoBridge.closeAndSignalBlockedThreads(mClient); + } + + @Override + public void flush() throws IOException { + Memory.pokeInt(mTemp, 0, CMD_FSYNC, ByteOrder.BIG_ENDIAN); + IoBridge.write(mClient, mTemp, 0, MSG_LENGTH); + + // Wait for server to ack + if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) { + if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == CMD_FSYNC) { + return; + } + } + + throw new SyncFailedException("Failed to fsync() across bridge"); + } + + @Override + public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException { + Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); + Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN); + Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN); + IoBridge.write(mClient, mTemp, 0, MSG_LENGTH); + IoBridge.write(mClient, buffer, byteOffset, byteCount); + } + + @Override + public void write(int oneByte) throws IOException { + Streams.writeSingleByte(this, oneByte); + } + } +} diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index cd47099..e77ef95 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -39,9 +39,6 @@ interface IUserManager { UserInfo getProfileParent(int userHandle); UserInfo getUserInfo(int userHandle); boolean isRestricted(); - void setGuestEnabled(boolean enable); - boolean isGuestEnabled(); - void wipeUser(int userHandle); int getUserSerialNumber(int userHandle); int getUserHandle(int userSerialNumber); Bundle getUserRestrictions(int userHandle); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index cd1cd30..92e80a5 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -314,6 +314,11 @@ public final class PowerManager { * 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"; diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 69b828f..08a15eb 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -16,8 +16,6 @@ package android.os; -import android.view.WindowManagerPolicy; - /** * Power manager local system service interface. * @@ -57,12 +55,9 @@ public abstract class PowerManagerInternal { public abstract boolean getLowPowerModeEnabled(); + public abstract void registerLowPowerModeObserver(LowPowerModeListener listener); + public interface LowPowerModeListener { public void onLowPowerModeChanged(boolean enabled); } - - public abstract void registerLowPowerModeObserver(LowPowerModeListener listener); - - // TODO: Remove this and retrieve as a local service instead. - public abstract void setPolicy(WindowManagerPolicy policy); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 112ec1d..86c749a 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -156,6 +156,12 @@ public class Process { public static final int LAST_ISOLATED_UID = 99999; /** + * Defines the gid shared by all applications running under the same profile. + * @hide + */ + public static final int SHARED_USER_GID = 9997; + + /** * First gid for applications to share resources. Used when forward-locking * is enabled but all UserHandles need to be able to read the resources. * @hide diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 57ed979..474192f 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -70,6 +70,8 @@ public final class Trace { public static final long TRACE_TAG_DALVIK = 1L << 14; /** @hide */ public static final long TRACE_TAG_RS = 1L << 15; + /** @hide */ + public static final long TRACE_TAG_BIONIC = 1L << 16; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 6e693a4..afbf983 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -16,7 +16,10 @@ package android.os; +import android.util.SparseArray; + import java.io.PrintWriter; +import java.util.HashMap; /** * Representation of a user on the device. @@ -66,6 +69,8 @@ public final class UserHandle implements Parcelable { final int mHandle; + private static final SparseArray<UserHandle> userHandles = new SparseArray<UserHandle>(); + /** * Checks to see if the user id is the same for the two uids, i.e., they belong to the same * user. @@ -124,6 +129,18 @@ public final class UserHandle implements Parcelable { return getUserId(Binder.getCallingUid()); } + /** @hide */ + public static final UserHandle getCallingUserHandle() { + int userId = getUserId(Binder.getCallingUid()); + UserHandle userHandle = userHandles.get(userId); + // Intentionally not synchronized to save time + if (userHandle == null) { + userHandle = new UserHandle(userId); + userHandles.put(userId, userHandle); + } + return userHandle; + } + /** * Returns the uid that is composed from the userId and the appId. * @hide @@ -145,6 +162,14 @@ public final class UserHandle implements Parcelable { } /** + * Returns the gid shared between all apps with this userId. + * @hide + */ + public static final int getUserGid(int userId) { + return getUid(userId, Process.SHARED_USER_GID); + } + + /** * Returns the shared app gid for a given uid or appId. * @hide */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f7a89ba..96db772 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap.Config; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.provider.Settings; import android.util.Log; import com.android.internal.R; @@ -225,14 +226,14 @@ public class UserManager { public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; /** - * Key for user restrictions. Specifies if a user is disallowed from configuring + * Key for user restrictions. Specifies if a user is disallowed from controlling * applications in Settings. The default value is <code>false</code>. * <p> * Type: Boolean * @see #setUserRestrictions(Bundle) * @see #getUserRestrictions() */ - public static final String DISALLOW_CONFIG_APPS = "no_config_apps"; + public static final String DISALLOW_APPS_CONTROL = "no_control_apps"; /** * Key for user restrictions. Specifies if a user is disallowed from mounting @@ -361,6 +362,16 @@ public class UserManager { } /** + * Checks if the calling app is running as a guest user. + * @return whether the caller is a guest user. + * @hide + */ + public boolean isGuestUser() { + UserInfo user = getUserInfo(UserHandle.myUserId()); + return user != null ? user.isGuest() : false; + } + + /** * Return whether the given user is actively running. This means that * the user is in the "started" state, not "stopped" -- it is currently * allowed to run code through scheduled alarms, receiving broadcasts, @@ -481,10 +492,11 @@ public class UserManager { } /** - * @hide * Returns whether the current user has been disallowed from performing certain actions * or setting certain settings. - * @param restrictionKey the string key representing the restriction + * + * @param restrictionKey The string key representing the restriction. + * @return {@code true} if the current user has the given restriction, {@code false} otherwise. */ public boolean hasUserRestriction(String restrictionKey) { return hasUserRestriction(restrictionKey, Process.myUserHandle()); @@ -549,6 +561,21 @@ public class UserManager { } /** + * Creates a guest user and configures it. + * @param context an application context + * @param name the name to set for the user + * @hide + */ + public UserInfo createGuest(Context context, String name) { + UserInfo guest = createUser(name, UserInfo.FLAG_GUEST); + if (guest != null) { + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); + } + return guest; + } + + /** * Creates a user with the specified name and options as a profile of another user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @@ -692,6 +719,26 @@ public class UserManager { /** * If the target user is a managed profile of the calling user or the caller + * is itself a managed profile, then this returns a copy of the label with + * badging for accessibility services like talkback. E.g. passing in "Email" + * and it might return "Work Email" for Email in the work profile. + * + * @param label The label to change. + * @param user The target user. + * @return A label that combines the original label and a badge as + * determined by the system. + */ + public String getBadgedLabelForUser(String label, UserHandle user) { + UserInfo userInfo = getUserIfProfile(user.getIdentifier()); + if (userInfo != null && userInfo.isManagedProfile()) { + return Resources.getSystem().getString( + R.string.managed_profile_label_badge, label); + } + return label; + } + + /** + * If the target user is a managed profile of the calling user or the caller * is itself a managed profile, then this returns a drawable to use as a small * icon to include in a view to distinguish it from the original icon. * @@ -826,50 +873,6 @@ public class UserManager { } /** - * Enable or disable the use of a guest account. If disabled, the existing guest account - * will be wiped. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. - * @param enable whether to enable a guest account. - * @hide - */ - public void setGuestEnabled(boolean enable) { - try { - mService.setGuestEnabled(enable); - } catch (RemoteException re) { - Log.w(TAG, "Could not change guest account availability to " + enable); - } - } - - /** - * Checks if a guest user is enabled for this device. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. - * @return whether a guest user is enabled - * @hide - */ - public boolean isGuestEnabled() { - try { - return mService.isGuestEnabled(); - } catch (RemoteException re) { - Log.w(TAG, "Could not retrieve guest enabled state"); - return false; - } - } - - /** - * Wipes all the data for a user, but doesn't remove the user. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. - * @param userHandle - * @hide - */ - public void wipeUser(int userHandle) { - try { - mService.wipeUser(userHandle); - } catch (RemoteException re) { - Log.w(TAG, "Could not wipe user " + userHandle); - } - } - - /** * Returns the maximum number of users that can be created on this device. A return value * of 1 means that it is a single user device. * @hide @@ -899,7 +902,9 @@ public class UserManager { ++switchableUserCount; } } - return switchableUserCount > 1; + final boolean guestEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.GUEST_USER_ENABLED, 0) == 1; + return switchableUserCount > 1 || guestEnabled; } /** diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index cb0f142..c1d4d4c 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -74,7 +74,6 @@ public abstract class Vibrator { * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type. * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls. - * @hide */ public void vibrate(long milliseconds, int streamHint) { vibrate(Process.myUid(), mPackageName, milliseconds, streamHint); @@ -126,7 +125,6 @@ public abstract class Vibrator { * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type. * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls. - * @hide */ public void vibrate(long[] pattern, int repeat, int streamHint) { vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint); diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 0418049..23b1e2c 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -1194,7 +1194,14 @@ public abstract class PreferenceActivity extends ListActivity implements * @param args Optional arguments to supply to the fragment. */ public void switchToHeader(String fragmentName, Bundle args) { - setSelectedHeader(null); + Header selectedHeader = null; + for (int i = 0; i < mHeaders.size(); i++) { + if (fragmentName.equals(mHeaders.get(i).fragment)) { + selectedHeader = mHeaders.get(i); + break; + } + } + setSelectedHeader(selectedHeader); switchToHeaderInner(fragmentName, args); } diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index 381a5f0..5a0b9e9 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Adapter; import android.widget.BaseAdapter; +import android.widget.FrameLayout; import android.widget.ListView; /** @@ -95,6 +96,9 @@ public class PreferenceGroupAdapter extends BaseAdapter private int mHighlightedPosition = -1; private Drawable mHighlightedDrawable; + private static ViewGroup.LayoutParams sWrapperLayoutParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + private static class PreferenceLayout implements Comparable<PreferenceLayout> { private int resId; private int widgetResId; @@ -235,14 +239,18 @@ public class PreferenceGroupAdapter extends BaseAdapter // If it's not one of the cached ones, set the convertView to null so that // the layout gets re-created by the Preference. - if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) { + if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 || + (getItemViewType(position) == getHighlightItemViewType())) { convertView = null; } View result = preference.getView(convertView, parent); if (position == mHighlightedPosition && mHighlightedDrawable != null) { - result.setBackgroundDrawable(mHighlightedDrawable); + ViewGroup wrapper = new FrameLayout(parent.getContext()); + wrapper.setLayoutParams(sWrapperLayoutParams); + wrapper.setBackgroundDrawable(mHighlightedDrawable); + wrapper.addView(result); + result = wrapper; } - result.setTag(preference.getKey()); return result; } @@ -273,8 +281,16 @@ public class PreferenceGroupAdapter extends BaseAdapter return true; } + private int getHighlightItemViewType() { + return getViewTypeCount() - 1; + } + @Override public int getItemViewType(int position) { + if (position == mHighlightedPosition) { + return getHighlightItemViewType(); + } + if (!mHasReturnedViewTypeCount) { mHasReturnedViewTypeCount = true; } @@ -302,7 +318,7 @@ public class PreferenceGroupAdapter extends BaseAdapter mHasReturnedViewTypeCount = true; } - return Math.max(1, mPreferenceLayouts.size()); + return Math.max(1, mPreferenceLayouts.size()) + 1; } } diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java index e32890d..67f6409 100644 --- a/core/java/android/preference/SeekBarPreference.java +++ b/core/java/android/preference/SeekBarPreference.java @@ -40,11 +40,19 @@ public class SeekBarPreference extends Preference Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - final TypedArray a = context.obtainStyledAttributes( + 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); + + a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.SeekBarPreference, defStyleAttr, defStyleRes); + final int layoutResId = a.getResourceId( + com.android.internal.R.styleable.SeekBarPreference_layout, + com.android.internal.R.layout.preference_widget_seekbar); + a.recycle(); + + setLayoutResource(layoutResId); } public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { @@ -52,7 +60,7 @@ public class SeekBarPreference extends Preference } public SeekBarPreference(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs, com.android.internal.R.attr.seekBarPreferenceStyle); } public SeekBarPreference(Context context) { diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl index 43b8c30..68c1dac 100644 --- a/core/java/android/print/ILayoutResultCallback.aidl +++ b/core/java/android/print/ILayoutResultCallback.aidl @@ -16,6 +16,7 @@ package android.print; +import android.os.ICancellationSignal; import android.print.PrintDocumentInfo; /** @@ -24,6 +25,8 @@ import android.print.PrintDocumentInfo; * @hide */ oneway interface ILayoutResultCallback { + void onLayoutStarted(ICancellationSignal cancellation, int sequence); void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence); void onLayoutFailed(CharSequence error, int sequence); + void onLayoutCanceled(int sequence); } diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index 2b95c12..9d384fb 100644 --- a/core/java/android/print/IPrintDocumentAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -37,5 +37,4 @@ oneway interface IPrintDocumentAdapter { void write(in PageRange[] pages, in ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence); void finish(); - void cancel(); } diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl index 8281c4e..8fb33e1 100644 --- a/core/java/android/print/IWriteResultCallback.aidl +++ b/core/java/android/print/IWriteResultCallback.aidl @@ -16,6 +16,7 @@ package android.print; +import android.os.ICancellationSignal; import android.print.PageRange; /** @@ -24,6 +25,8 @@ import android.print.PageRange; * @hide */ oneway interface IWriteResultCallback { + void onWriteStarted(ICancellationSignal cancellation, int sequence); void onWriteFinished(in PageRange[] pages, int sequence); void onWriteFailed(CharSequence error, int sequence); + void onWriteCanceled(int sequence); } diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index c6254e0..2810d55 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -151,6 +151,105 @@ public final class PrintAttributes implements Parcelable { mColorMode = colorMode; } + /** + * Gets whether this print attributes are in portrait orientation, + * which is the media size is in portrait and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return Whether this print attributes are in portrait. + * + * @hide + */ + public boolean isPortrait() { + return mMediaSize.isPortrait(); + } + + /** + * Gets a new print attributes instance which is in portrait orientation, + * which is the media size is in portrait and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return New instance in portrait orientation if this one is in + * landscape, otherwise this instance. + * + * @hide + */ + public PrintAttributes asPortrait() { + if (isPortrait()) { + return this; + } + + PrintAttributes attributes = new PrintAttributes(); + + // Rotate the media size. + attributes.setMediaSize(getMediaSize().asPortrait()); + + // Rotate the resolution. + Resolution oldResolution = getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + attributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = getMinMargins(); + Margins newMinMargins = new Margins( + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils(), + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils()); + attributes.setMinMargins(newMinMargins); + + attributes.setColorMode(getColorMode()); + + return attributes; + } + + /** + * Gets a new print attributes instance which is in landscape orientation, + * which is the media size is in landscape and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return New instance in landscape orientation if this one is in + * portrait, otherwise this instance. + * + * @hide + */ + public PrintAttributes asLandscape() { + if (!isPortrait()) { + return this; + } + + PrintAttributes attributes = new PrintAttributes(); + + // Rotate the media size. + attributes.setMediaSize(getMediaSize().asLandscape()); + + // Rotate the resolution. + Resolution oldResolution = getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + attributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = getMinMargins(); + Margins newMargins = new Margins( + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils(), + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils()); + attributes.setMinMargins(newMargins); + + attributes.setColorMode(getColorMode()); + + return attributes; + } + @Override public void writeToParcel(Parcel parcel, int flags) { if (mMediaSize != null) { diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 811751d..9361286 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -24,6 +24,7 @@ import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; +import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -41,6 +42,7 @@ import libcore.io.IoUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -50,12 +52,12 @@ import java.util.Map; * <p> * To obtain a handle to the print manager do the following: * </p> - * + * * <pre> * PrintManager printManager = * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); * </pre> - * + * * <h3>Print mechanics</h3> * <p> * The key idea behind printing on the platform is that the content to be printed @@ -344,7 +346,7 @@ public final class PrintManager { try { mService.cancelPrintJob(printJobId, mAppId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); + Log.e(LOG_TAG, "Error canceling a print job: " + printJobId, re); } } @@ -505,30 +507,17 @@ public final class PrintManager { private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub implements ActivityLifecycleCallbacks { - private final Object mLock = new Object(); - private CancellationSignal mLayoutOrWriteCancellation; - - private Activity mActivity; // Strong reference OK - cleared in finish() - - private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish + private Activity mActivity; // Strong reference OK - cleared in destroy - private Handler mHandler; // Strong reference OK - cleared in finish() + private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy - private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish + private Handler mHandler; // Strong reference OK - cleared in destroy - private LayoutSpec mLastLayoutSpec; + private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy - private WriteSpec mLastWriteSpec; - - private boolean mStartReqeusted; - private boolean mStarted; - - private boolean mFinishRequested; - private boolean mFinished; - - private boolean mDestroyed; + private DestroyableCallback mPendingCallback; public PrintDocumentAdapterDelegate(Activity activity, PrintDocumentAdapter documentAdapter) { @@ -542,11 +531,10 @@ public final class PrintManager { public void setObserver(IPrintDocumentAdapterObserver observer) { final boolean destroyed; synchronized (mLock) { - if (!mDestroyed) { - mObserver = observer; - } - destroyed = mDestroyed; + mObserver = observer; + destroyed = isDestroyedLocked(); } + if (destroyed) { try { observer.onDestroy(); @@ -559,126 +547,89 @@ public final class PrintManager { @Override public void start() { synchronized (mLock) { - // Started called or finish called or destroyed - nothing to do. - if (mStartReqeusted || mFinishRequested || mDestroyed) { - return; + // If destroyed the handler is null. + if (!isDestroyedLocked()) { + mHandler.obtainMessage(MyHandler.MSG_ON_START, + mDocumentAdapter).sendToTarget(); } - - mStartReqeusted = true; - - doPendingWorkLocked(); } } @Override public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, ILayoutResultCallback callback, Bundle metadata, int sequence) { - final boolean destroyed; - synchronized (mLock) { - destroyed = mDestroyed; - // If start called and not finished called and not destroyed - do some work. - if (mStartReqeusted && !mFinishRequested && !mDestroyed) { - // Layout cancels write and overrides layout. - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - - mLastLayoutSpec = new LayoutSpec(); - mLastLayoutSpec.callback = callback; - mLastLayoutSpec.oldAttributes = oldAttributes; - mLastLayoutSpec.newAttributes = newAttributes; - mLastLayoutSpec.metadata = metadata; - mLastLayoutSpec.sequence = sequence; - - // Cancel the previous cancellable operation.When the - // cancellation completes we will do the pending work. - if (cancelPreviousCancellableOperationLocked()) { - return; - } - doPendingWorkLocked(); - } + ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); + try { + callback.onLayoutStarted(cancellationTransport, sequence); + } catch (RemoteException re) { + // The spooler is dead - can't recover. + Log.e(LOG_TAG, "Error notifying for layout start", re); + return; } - if (destroyed) { - try { - callback.onLayoutFailed(null, sequence); - } catch (RemoteException re) { - Log.i(LOG_TAG, "Error notifying for cancelled layout", re); + + synchronized (mLock) { + // If destroyed the handler is null. + if (isDestroyedLocked()) { + return; } + + CancellationSignal cancellationSignal = CancellationSignal.fromTransport( + cancellationTransport); + + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mDocumentAdapter; + args.arg2 = oldAttributes; + args.arg3 = newAttributes; + args.arg4 = cancellationSignal; + args.arg5 = new MyLayoutResultCallback(callback, sequence); + args.arg6 = metadata; + + mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget(); } } @Override public void write(PageRange[] pages, ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence) { - final boolean destroyed; - synchronized (mLock) { - destroyed = mDestroyed; - // If start called and not finished called and not destroyed - do some work. - if (mStartReqeusted && !mFinishRequested && !mDestroyed) { - // Write cancels previous writes. - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - mLastWriteSpec = new WriteSpec(); - mLastWriteSpec.callback = callback; - mLastWriteSpec.pages = pages; - mLastWriteSpec.fd = fd; - mLastWriteSpec.sequence = sequence; - - // Cancel the previous cancellable operation.When the - // cancellation completes we will do the pending work. - if (cancelPreviousCancellableOperationLocked()) { - return; - } - - doPendingWorkLocked(); - } - } - if (destroyed) { - try { - callback.onWriteFailed(null, sequence); - } catch (RemoteException re) { - Log.i(LOG_TAG, "Error notifying for cancelled write", re); - } + ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); + try { + callback.onWriteStarted(cancellationTransport, sequence); + } catch (RemoteException re) { + // The spooler is dead - can't recover. + Log.e(LOG_TAG, "Error notifying for write start", re); + return; } - } - @Override - public void finish() { synchronized (mLock) { - // Start not called or finish called or destroyed - nothing to do. - if (!mStartReqeusted || mFinishRequested || mDestroyed) { + // If destroyed the handler is null. + if (isDestroyedLocked()) { return; } - mFinishRequested = true; + CancellationSignal cancellationSignal = CancellationSignal.fromTransport( + cancellationTransport); - // When the current write or layout complete we - // will do the pending work. - if (mLastLayoutSpec != null || mLastWriteSpec != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Waiting for current operation"); - } - return; - } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mDocumentAdapter; + args.arg2 = pages; + args.arg3 = fd; + args.arg4 = cancellationSignal; + args.arg5 = new MyWriteResultCallback(callback, fd, sequence); - doPendingWorkLocked(); + mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget(); } } @Override - public void cancel() { - // Start not called or finish called or destroyed - nothing to do. - if (!mStartReqeusted || mFinishRequested || mDestroyed) { - return; - } - // Request cancellation of pending work if needed. + public void finish() { synchronized (mLock) { - cancelPreviousCancellableOperationLocked(); + // If destroyed the handler is null. + if (!isDestroyedLocked()) { + mHandler.obtainMessage(MyHandler.MSG_ON_FINISH, + mDocumentAdapter).sendToTarget(); + } } } @@ -719,20 +670,14 @@ public final class PrintManager { // Note the the spooler has a death recipient that observes if // this process gets killed so we cover the case of onDestroy not // being called due to this process being killed to reclaim memory. - final IPrintDocumentAdapterObserver observer; + IPrintDocumentAdapterObserver observer = null; synchronized (mLock) { if (activity == mActivity) { - mDestroyed = true; observer = mObserver; - clearLocked(); - } else { - observer = null; - activity = null; + destroyLocked(); } } if (observer != null) { - activity.getApplication().unregisterActivityLifecycleCallbacks( - PrintDocumentAdapterDelegate.this); try { observer.onDestroy(); } catch (RemoteException re) { @@ -741,67 +686,39 @@ public final class PrintManager { } } - private boolean isFinished() { - return mDocumentAdapter == null; + private boolean isDestroyedLocked() { + return (mActivity == null); } - private void clearLocked() { + private void destroyLocked() { + mActivity.getApplication().unregisterActivityLifecycleCallbacks( + PrintDocumentAdapterDelegate.this); mActivity = null; + mDocumentAdapter = null; + + // This method is only called from the main thread, so + // clearing the messages guarantees that any time a + // message is handled we are not in a destroyed state. + mHandler.removeMessages(MyHandler.MSG_ON_START); + mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT); + mHandler.removeMessages(MyHandler.MSG_ON_WRITE); + mHandler.removeMessages(MyHandler.MSG_ON_FINISH); mHandler = null; - mLayoutOrWriteCancellation = null; - mLastLayoutSpec = null; - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - } - private boolean cancelPreviousCancellableOperationLocked() { - if (mLayoutOrWriteCancellation != null) { - mLayoutOrWriteCancellation.cancel(); - if (DEBUG) { - Log.i(LOG_TAG, "Cancelling previous operation"); - } - return true; - } - return false; - } + mObserver = null; - private void doPendingWorkLocked() { - if (mStartReqeusted && !mStarted) { - mStarted = true; - mHandler.sendEmptyMessage(MyHandler.MSG_START); - } else if (mLastLayoutSpec != null) { - mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT); - } else if (mLastWriteSpec != null) { - mHandler.sendEmptyMessage(MyHandler.MSG_WRITE); - } else if (mFinishRequested && !mFinished) { - mFinished = true; - mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); + if (mPendingCallback != null) { + mPendingCallback.destroy(); + mPendingCallback = null; } } - private class LayoutSpec { - ILayoutResultCallback callback; - PrintAttributes oldAttributes; - PrintAttributes newAttributes; - Bundle metadata; - int sequence; - } - - private class WriteSpec { - IWriteResultCallback callback; - PageRange[] pages; - ParcelFileDescriptor fd; - int sequence; - } - private final class MyHandler extends Handler { - public static final int MSG_START = 1; - public static final int MSG_LAYOUT = 2; - public static final int MSG_WRITE = 3; - public static final int MSG_FINISH = 4; + public static final int MSG_ON_START = 1; + public static final int MSG_ON_LAYOUT = 2; + public static final int MSG_ON_WRITE = 3; + public static final int MSG_ON_FINISH = 4; public MyHandler(Looper looper) { super(looper, null, true); @@ -809,84 +726,71 @@ public final class PrintManager { @Override public void handleMessage(Message message) { - if (isFinished()) { - return; - } switch (message.what) { - case MSG_START: { - final PrintDocumentAdapter adapter; - synchronized (mLock) { - adapter = mDocumentAdapter; - } - if (adapter != null) { - adapter.onStart(); + case MSG_ON_START: { + if (DEBUG) { + Log.i(LOG_TAG, "onStart()"); } + + ((PrintDocumentAdapter) message.obj).onStart(); } break; - case MSG_LAYOUT: { - final PrintDocumentAdapter adapter; - final CancellationSignal cancellation; - final LayoutSpec layoutSpec; + case MSG_ON_LAYOUT: { + SomeArgs args = (SomeArgs) message.obj; + PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; + PrintAttributes oldAttributes = (PrintAttributes) args.arg2; + PrintAttributes newAttributes = (PrintAttributes) args.arg3; + CancellationSignal cancellation = (CancellationSignal) args.arg4; + LayoutResultCallback callback = (LayoutResultCallback) args.arg5; + Bundle metadata = (Bundle) args.arg6; + args.recycle(); - synchronized (mLock) { - adapter = mDocumentAdapter; - layoutSpec = mLastLayoutSpec; - mLastLayoutSpec = null; - cancellation = new CancellationSignal(); - mLayoutOrWriteCancellation = cancellation; + if (DEBUG) { + StringBuilder builder = new StringBuilder(); + builder.append("PrintDocumentAdapter#onLayout() {\n"); + builder.append("\n oldAttributes:").append(oldAttributes); + builder.append("\n newAttributes:").append(newAttributes); + builder.append("\n preview:").append(metadata.getBoolean( + PrintDocumentAdapter.EXTRA_PRINT_PREVIEW)); + builder.append("\n}"); + Log.i(LOG_TAG, builder.toString()); } - if (layoutSpec != null && adapter != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Performing layout"); - } - adapter.onLayout(layoutSpec.oldAttributes, - layoutSpec.newAttributes, cancellation, - new MyLayoutResultCallback(layoutSpec.callback, - layoutSpec.sequence), layoutSpec.metadata); - } + adapter.onLayout(oldAttributes, newAttributes, cancellation, + callback, metadata); } break; - case MSG_WRITE: { - final PrintDocumentAdapter adapter; - final CancellationSignal cancellation; - final WriteSpec writeSpec; + case MSG_ON_WRITE: { + SomeArgs args = (SomeArgs) message.obj; + PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; + PageRange[] pages = (PageRange[]) args.arg2; + ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3; + CancellationSignal cancellation = (CancellationSignal) args.arg4; + WriteResultCallback callback = (WriteResultCallback) args.arg5; + args.recycle(); - synchronized (mLock) { - adapter = mDocumentAdapter; - writeSpec = mLastWriteSpec; - mLastWriteSpec = null; - cancellation = new CancellationSignal(); - mLayoutOrWriteCancellation = cancellation; + if (DEBUG) { + StringBuilder builder = new StringBuilder(); + builder.append("PrintDocumentAdapter#onWrite() {\n"); + builder.append("\n pages:").append(Arrays.toString(pages)); + builder.append("\n}"); + Log.i(LOG_TAG, builder.toString()); } - if (writeSpec != null && adapter != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Performing write"); - } - adapter.onWrite(writeSpec.pages, writeSpec.fd, - cancellation, new MyWriteResultCallback(writeSpec.callback, - writeSpec.fd, writeSpec.sequence)); - } + adapter.onWrite(pages, fd, cancellation, callback); } break; - case MSG_FINISH: { + case MSG_ON_FINISH: { if (DEBUG) { - Log.i(LOG_TAG, "Performing finish"); + Log.i(LOG_TAG, "onFinish()"); } - final PrintDocumentAdapter adapter; - final Activity activity; + + ((PrintDocumentAdapter) message.obj).onFinish(); + + // Done printing, so destroy this instance as it + // should not be used anymore. synchronized (mLock) { - adapter = mDocumentAdapter; - activity = mActivity; - clearLocked(); - } - if (adapter != null) { - adapter.onFinish(); - } - if (activity != null) { - activity.getApplication().unregisterActivityLifecycleCallbacks( - PrintDocumentAdapterDelegate.this); + destroyLocked(); } } break; @@ -898,7 +802,12 @@ public final class PrintManager { } } - private final class MyLayoutResultCallback extends LayoutResultCallback { + private interface DestroyableCallback { + public void destroy(); + } + + private final class MyLayoutResultCallback extends LayoutResultCallback + implements DestroyableCallback { private ILayoutResultCallback mCallback; private final int mSequence; @@ -910,25 +819,31 @@ public final class PrintManager { @Override public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { - if (info == null) { - throw new NullPointerException("document info cannot be null"); - } final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + if (info == null) { + throw new NullPointerException("document info cannot be null"); + } + try { callback.onLayoutFinished(info, changed, mSequence); } catch (RemoteException re) { Log.e(LOG_TAG, "Error calling onLayoutFinished", re); } + } finally { + destroy(); } } @@ -936,46 +851,64 @@ public final class PrintManager { public void onLayoutFailed(CharSequence error) { final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { - try { - callback.onLayoutFailed(error, mSequence); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onLayoutFailed", re); - } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onLayoutFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); + } finally { + destroy(); } } @Override public void onLayoutCancelled() { + final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } - clearLocked(); + callback = mCallback; + } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onLayoutCanceled(mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); + } finally { + destroy(); } } - private void clearLocked() { - mLayoutOrWriteCancellation = null; - mCallback = null; - doPendingWorkLocked(); + @Override + public void destroy() { + synchronized (mLock) { + mCallback = null; + mPendingCallback = null; + } } } - private final class MyWriteResultCallback extends WriteResultCallback { + private final class MyWriteResultCallback extends WriteResultCallback + implements DestroyableCallback { private ParcelFileDescriptor mFd; - private int mSequence; private IWriteResultCallback mCallback; + private final int mSequence; public MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence) { @@ -988,26 +921,32 @@ public final class PrintManager { public void onWriteFinished(PageRange[] pages) { final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); - } - if (pages == null) { - throw new IllegalArgumentException("pages cannot be null"); } - if (pages.length == 0) { - throw new IllegalArgumentException("pages cannot be empty"); + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; } - if (callback != null) { + + try { + if (pages == null) { + throw new IllegalArgumentException("pages cannot be null"); + } + if (pages.length == 0) { + throw new IllegalArgumentException("pages cannot be empty"); + } + try { callback.onWriteFinished(pages, mSequence); } catch (RemoteException re) { Log.e(LOG_TAG, "Error calling onWriteFinished", re); } + } finally { + destroy(); } } @@ -1015,41 +954,58 @@ public final class PrintManager { public void onWriteFailed(CharSequence error) { final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { - try { - callback.onWriteFailed(error, mSequence); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onWriteFailed", re); - } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onWriteFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFailed", re); + } finally { + destroy(); } } @Override public void onWriteCancelled() { + final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } - clearLocked(); + callback = mCallback; + } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onWriteCanceled(mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteCanceled", re); + } finally { + destroy(); } } - private void clearLocked() { - mLayoutOrWriteCancellation = null; - IoUtils.closeQuietly(mFd); - mCallback = null; - mFd = null; - doPendingWorkLocked(); + @Override + public void destroy() { + synchronized (mLock) { + IoUtils.closeQuietly(mFd); + mCallback = null; + mFd = null; + mPendingCallback = null; + } } } } diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java index d32b71b..abb441b 100644 --- a/core/java/android/print/PrinterDiscoverySession.java +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -72,9 +72,9 @@ public final class PrinterDiscoverySession { } } - public final void startPrinterDisovery(List<PrinterId> priorityList) { + public final void startPrinterDiscovery(List<PrinterId> priorityList) { if (isDestroyed()) { - Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + Log.w(LOG_TAG, "Ignoring start printers discovery - session destroyed"); return; } if (!mIsPrinterDiscoveryStarted) { @@ -122,7 +122,7 @@ public final class PrinterDiscoverySession { try { mPrintManager.stopPrinterStateTracking(printerId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error stoping printer state tracking", re); + Log.e(LOG_TAG, "Error stopping printer state tracking", re); } } diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index eb0ac2e..1557ab0 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -201,9 +201,9 @@ public abstract class PrintService extends Service { * should build another one using the {@link PrintJobInfo.Builder} class. You * can specify any standard properties and add advanced, printer specific, * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String) - * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link + * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link * PrintJobInfo.Builder#putAdvancedOption(String, int) - * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options + * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options * are not interpreted by the system, they will not be visible to applications, * and can only be accessed by your print service via {@link * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)} @@ -212,14 +212,26 @@ public abstract class PrintService extends Service { * <p> * If the advanced print options activity offers changes to the standard print * options, you can get the current {@link android.print.PrinterInfo} using the - * "android.intent.extra.print.EXTRA_PRINTER_INFO" extra which will allow you to - * present the user with UI options supported by the current printer. For example, - * if the current printer does not support a give media size, you should not - * offer it in the advanced print options dialog. + * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user + * with UI options supported by the current printer. For example, if the current + * printer does not support a given media size, you should not offer it in the + * advanced print options UI. * </p> + * + * @see #EXTRA_PRINTER_INFO */ public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO"; + /** + * If you declared an optional activity with advanced print options via the + * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity} + * attribute, this extra is used to pass in the currently selected printer's + * {@link android.print.PrinterInfo} to your activity allowing you to inspect it. + * + * @see #EXTRA_PRINT_JOB_INFO + */ + public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO"; + private Handler mHandler; private IPrintServiceClient mClient; diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index a34d9c3..3853003 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -25,6 +25,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.Build; import android.provider.BrowserContract.Bookmarks; import android.provider.BrowserContract.Combined; import android.provider.BrowserContract.History; @@ -155,8 +156,8 @@ public class Browser { * @param title Title for the bookmark. Can be null or empty string. * @param url Url for the bookmark. Can be null or empty string. */ - public static final void saveBookmark(Context c, - String title, + public static final void saveBookmark(Context c, + String title, String url) { Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI); i.putExtra("title", title); @@ -233,10 +234,10 @@ public class Browser { * * @param cr The ContentResolver used to access the database. */ - public static final Cursor getAllBookmarks(ContentResolver cr) throws + public static final Cursor getAllBookmarks(ContentResolver cr) throws IllegalStateException { return cr.query(Bookmarks.CONTENT_URI, - new String[] { Bookmarks.URL }, + new String[] { Bookmarks.URL }, Bookmarks.IS_FOLDER + " = 0", null, null); } @@ -397,19 +398,17 @@ public class Browser { // TODO make a single request to the provider to do this in a single transaction Cursor cursor = null; try { - + // Select non-bookmark history, ordered by date cursor = cr.query(History.CONTENT_URI, new String[] { History._ID, History.URL, History.DATE_LAST_VISITED }, null, null, History.DATE_LAST_VISITED + " ASC"); if (cursor.moveToFirst() && cursor.getCount() >= MAX_HISTORY_COUNT) { - final WebIconDatabase iconDb = WebIconDatabase.getInstance(); /* eliminate oldest history items */ for (int i = 0; i < TRUNCATE_N_OLDEST; i++) { cr.delete(ContentUris.withAppendedId(History.CONTENT_URI, cursor.getLong(0)), - null, null); - iconDb.releaseIconForPageUrl(cursor.getString(1)); + null, null); if (!cursor.moveToNext()) break; } } @@ -469,13 +468,6 @@ public class Browser { cursor = cr.query(History.CONTENT_URI, new String[] { History.URL }, whereClause, null, null); if (cursor.moveToFirst()) { - final WebIconDatabase iconDb = WebIconDatabase.getInstance(); - do { - // Delete favicons - // TODO don't release if the URL is bookmarked - iconDb.releaseIconForPageUrl(cursor.getString(0)); - } while (cursor.moveToNext()); - cr.delete(History.CONTENT_URI, whereClause, null); } } catch (IllegalStateException e) { @@ -520,7 +512,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param url url to remove. */ - public static final void deleteFromHistory(ContentResolver cr, + public static final void deleteFromHistory(ContentResolver cr, String url) { cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url }); } @@ -554,7 +546,7 @@ public class Browser { Log.e(LOGTAG, "clearSearches", e); } } - + /** * Request all icons from the database. This call must either be called * in the main thread or have had Looper.prepare() invoked in the calling @@ -563,12 +555,12 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param where Clause to be used to limit the query from the database. * Must be an allowable string to be passed into a database query. - * @param listener IconListener that gets the icons once they are + * @param listener IconListener that gets the icons once they are * retrieved. */ public static final void requestAllIcons(ContentResolver cr, String where, WebIconDatabase.IconListener listener) { - WebIconDatabase.getInstance().bulkRequestIconForPageUrl(cr, where, listener); + // Do nothing: this is no longer used. } /** diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 8c7e879..0d69b3b 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -811,7 +811,6 @@ public final class ContactsContract { * 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"; @@ -1156,8 +1155,6 @@ public final class ContactsContract { * address book index, which is usually the first letter of the sort key. * When this parameter is supplied, the row counts are returned in the * cursor extras bundle. - * - * @hide */ public final static class ContactCounts { @@ -1167,7 +1164,24 @@ public final class ContactsContract { * first letter of the sort key. This parameter does not affect the main * content of the cursor. * - * @hide + * <p> + * <pre> + * Example: + * Uri uri = Contacts.CONTENT_URI.buildUpon() + * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true") + * .build(); + * Cursor cursor = getContentResolver().query(uri, + * new String[] {Contacts.DISPLAY_NAME}, + * null, null, null); + * Bundle bundle = cursor.getExtras(); + * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) && + * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) { + * String sections[] = + * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); + * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); + * } + * </pre> + * </p> */ public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras"; @@ -1175,8 +1189,6 @@ public final class ContactsContract { * The array of address book index titles, which are returned in the * same order as the data in the cursor. * <p>TYPE: String[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles"; @@ -1184,8 +1196,6 @@ public final class ContactsContract { * The array of group counts for the corresponding group. Contains the same number * of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array. * <p>TYPE: int[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts"; } @@ -1683,7 +1693,7 @@ public final class ContactsContract { */ public static final class Entity implements BaseColumns, ContactsColumns, ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns, - StatusColumns, ContactOptionsColumns, ContactStatusColumns { + StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns { /** * no public constructor since this is a utility class */ @@ -7734,8 +7744,8 @@ public final class ContactsContract { /** * <p> - * API allowing applications to send pinning information for specified contacts to the - * Contacts Provider. + * Contact-specific information about whether or not a contact has been pinned by the user + * at a particular position within the system contact application's user interface. * </p> * * <p> @@ -7760,46 +7770,25 @@ public final class ContactsContract { * pinned positions can be positive integers that range anywhere from 0 to * {@link PinnedPositions#UNPINNED}. * </p> - * - * <p> - * When using {@link PinnedPositions#UPDATE_URI} to update the pinned positions of - * certain contacts, it may make sense for your application to star any pinned contacts - * by default. To specify this behavior, set the boolean query parameter - * {@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 { - /** * <p> - * This URI allows applications to update pinned positions for a provided set of contacts. - * </p> - * - * <p> - * The list of contactIds to pin and their corresponding pinned positions should be - * provided in key-value pairs stored in a {@link ContentValues} object where the key - * is a valid contactId, while each pinned position is a positive integer. + * The method to invoke in order to undemote a formerly demoted contact. The contact id of + * the contact must be provided as an argument. If the contact was not previously demoted, + * nothing will be done. * </p> * * <p> * Example: * <pre> - * ContentValues values = new ContentValues(); - * values.put("10", 20); - * values.put("12", 2); - * values.put("15", PinnedPositions.UNPINNED); - * int count = resolver.update(PinnedPositions.UPDATE_URI, values, null, null); + * final long contactId = 10; + * resolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD, + * String.valueOf(contactId), null); * </pre> - * - * This pins the contact with id 10 at position 20, the contact with id 12 at position 2, - * and unpins the contact with id 15. * </p> */ - public static final Uri UPDATE_URI = Uri.withAppendedPath(AUTHORITY_URI, - "pinned_position_update"); + public static final String UNDEMOTE_METHOD = "undemote"; /** * Default value for the pinned position of an unpinned contact. Also equal to @@ -7814,65 +7803,16 @@ public final class ContactsContract { * just hidden from view. */ public static final int DEMOTED = -1; - - /** - * <p> Clients can provide this value as a pinned position to undemote a formerly demoted - * contact. If the contact was formerly demoted, it will be restored to an - * {@link #UNPINNED} position. If it was otherwise already pinned at another position, - * it will not be affected. - * </p> - * - * <p> - * Example: - * <pre> - * ContentValues values = new ContentValues(); - * values.put("15", PinnedPositions.UNDEMOTE); - * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon() - * .build(), values, null, null); - * </pre> - * - * This restores the contact with id 15 to an {@link #UNPINNED} position, meaning that - * other apps (e.g. the Dialer) that were formerly hiding this contact from view based on - * its {@link #DEMOTED} position will start displaying it again. - * </p> - */ - public static final String UNDEMOTE = "undemote"; - - /** - * <p> - * A boolean query parameter that can be used with {@link #UPDATE_URI}. - * If "1" or "true", any contact that is pinned or unpinned will be correspondingly - * starred or unstarred. Otherwise, starring information will not be affected by pinned - * updates. This is false by default. - * </p> - * - * <p> - * Example: - * <pre> - * ContentValues values = new ContentValues(); - * values.put("10", 20); - * values.put("15", PinnedPositions.UNPINNED); - * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon() - * .appendQueryParameter(PinnedPositions.FORCE_STAR_WHEN_PINNING, "true").build(), - * values, null, null); - * </pre> - * - * This will pin the contact with id 10 at position 20 and star it automatically if not - * already starred, and unpin the contact with id 15, and unstar it automatically if not - * already unstarred. - * </p> - */ - public static final String STAR_WHEN_PINNING = "star_when_pinning"; } /** - * Helper methods to display QuickContact dialogs that allow users to pivot on + * Helper methods to display QuickContact dialogs that display all the information belonging to * a specific {@link Contacts} entry. */ public static final class QuickContact { /** - * Action used to trigger person pivot dialog. - * @hide + * Action used to launch the system contacts application and bring up a QuickContact dialog + * for the provided {@link Contacts} entry. */ public static final String ACTION_QUICK_CONTACT = "com.android.contacts.action.QUICK_CONTACT"; @@ -7892,9 +7832,8 @@ public final class ContactsContract { public static final String EXTRA_MODE = "mode"; /** - * Extra used to indicate a list of specific MIME-types to exclude and - * not display. Stored as a {@link String} array. - * @hide + * Extra used to indicate a list of specific MIME-types to exclude and not display in the + * QuickContacts dialog. Stored as a {@link String} array. */ public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes"; @@ -7952,8 +7891,7 @@ public final class ContactsContract { actualContext = ((ContextWrapper) actualContext).getBaseContext(); } final int intentFlags = (actualContext instanceof Activity) - ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET - : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; + ? 0 : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; // Launch pivot dialog through intent for now final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index cfab1b3..325917e 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -40,6 +40,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Arrays; /** * The Media provider contains meta data for all available media on both internal @@ -671,6 +672,7 @@ public final class MediaStore { if (sThumbBuf == null) { sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; } + Arrays.fill(sThumbBuf, (byte)0); if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); if (bitmap == null) { @@ -1886,6 +1888,9 @@ public final class MediaStore { * The MIME type for entries in this table. */ public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; + + // Not instantiable. + private Radio() { } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 55c66ba..e76d70f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2460,6 +2460,18 @@ public final class Settings { public static final String POINTER_SPEED = "pointer_speed"; /** + * Whether lock-to-app will be triggered by long-press on recents. + * @hide + */ + public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled"; + + /** + * Whether lock-to-app will lock the keyguard when exiting. + * @hide + */ + public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked"; + + /** * I am the lolrus. * <p> * Nonzero values indicate that the user has a bukkit. @@ -4470,6 +4482,12 @@ public final class Settings { INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF; /** + * Whether the device should wake when the wake gesture sensor detects motion. + * @hide + */ + public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled"; + + /** * The current night mode that has been selected by the user. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. @@ -4570,6 +4588,14 @@ public final class Settings { public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications"; /** + * If enabled, apps should try to skip any introductory hints on first launch. This might + * apply to users that are already familiar with the environment or temporary users. + * <p> + * Type : int (0 to show hints, 1 to skip showing hints) + */ + public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -6210,6 +6236,14 @@ public final class Settings { public static final String DEVICE_NAME = "device_name"; /** + * Whether it should be possible to create a guest user on the device. + * <p> + * Type: int (0 for disabled, 1 for enabled) + * @hide + */ + public static final String GUEST_USER_ENABLED = "guest_user_enabled"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * @@ -6523,6 +6557,53 @@ public final class Settings { public static boolean putFloat(ContentResolver cr, String name, float value) { return putString(cr, name, Float.toString(value)); } + + + /** + * Subscription to be used for voice call on a multi sim device. The supported values + * are 0 = SUB1, 1 = SUB2 and etc. + * @hide + */ + public static final String MULTI_SIM_VOICE_CALL_SUBSCRIPTION = "multi_sim_voice_call"; + + /** + * Used to provide option to user to select subscription during dial. + * The supported values are 0 = disable or 1 = enable prompt. + * @hide + */ + public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt"; + + /** + * Subscription to be used for data call on a multi sim device. The supported values + * are 0 = SUB1, 1 = SUB2 and etc. + * @hide + */ + public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call"; + + /** + * Subscription to be used for SMS on a multi sim device. The supported values + * are 0 = SUB1, 1 = SUB2 and etc. + * @hide + */ + public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms"; + + /** + * Used to provide option to user to select subscription during send SMS. + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String MULTI_SIM_SMS_PROMPT = "multi_sim_sms_prompt"; + + + + /** User preferred subscriptions setting. + * This holds the details of the user selected subscription from the card and + * the activation status. Each settings string have the coma separated values + * iccId,appType,appId,activationStatus,3gppIndex,3gpp2Index + * @hide + */ + public static final String[] MULTI_SIM_USER_PREFERRED_SUBS = {"user_preferred_sub1", + "user_preferred_sub2","user_preferred_sub3"}; } /** diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index dd2030b..5fd597b 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -22,46 +22,56 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.util.Slog; /** * A class that coordinates access to the fingerprint hardware. - * @hide */ public class FingerprintManager { private static final String TAG = "FingerprintManager"; private static final boolean DEBUG = true; - private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint"; - private static final String FINGERPRINT_SERVICE_CLASS = - "com.android.service.fingerprint.FingerprintService"; private static final int MSG_ENROLL_RESULT = 100; - private static final int MSG_SCANNED = 101; - private static final int MSG_ERROR = 102; - private static final int MSG_REMOVED = 103; + private static final int MSG_ACQUIRED = 101; + private static final int MSG_PROCESSED = 102; + private static final int MSG_ERROR = 103; + private static final int MSG_REMOVED = 104; + // Errors generated by layers above HAL public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; - public static final int FINGERPRINT_ERROR = -1; // One of the error messages below. - // Progress messages. - public static final int FINGERPRINT_SCANNED = 1; - public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2; + // Message types. Must agree with HAL (fingerprint.h) + public static final int FINGERPRINT_ERROR = -1; + public static final int FINGERPRINT_ACQUIRED = 1; + public static final int FINGERPRINT_PROCESSED = 2; + public static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; - // Error messages. Must agree with fingerprint HAL definitions. + // Error messages. Must agree with HAL (fingerprint.h) public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; - public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2; + public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; public static final int FINGERPRINT_ERROR_TIMEOUT = 3; public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + // FINGERPRINT_ACQUIRED messages. Must agree with HAL (fingerprint.h) + public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 4; + public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 8; + public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 16; + private IFingerprintService mService; private FingerprintManagerReceiver mClientReceiver; private Context mContext; + private IBinder mToken = new Binder(); private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { @@ -70,8 +80,11 @@ public class FingerprintManager { case MSG_ENROLL_RESULT: mClientReceiver.onEnrollResult(msg.arg1, msg.arg2); break; - case MSG_SCANNED: - mClientReceiver.onScanned(msg.arg1, msg.arg2); + case MSG_ACQUIRED: + mClientReceiver.onAcquired(msg.arg1); + break; + case MSG_PROCESSED: + mClientReceiver.onProcessed(msg.arg1); break; case MSG_ERROR: mClientReceiver.onError(msg.arg1); @@ -83,45 +96,29 @@ public class FingerprintManager { } }; - public FingerprintManager(Context context) { + /** + * @hide + */ + public FingerprintManager(Context context, IFingerprintService service) { mContext = context; - // Connect to service... - Intent intent = new Intent(); - intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS); - if (!context.bindServiceAsUser(intent, mFingerprintConnection, - Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF)) { - if (DEBUG) Log.v(TAG, "Can't bind to " + FINGERPRINT_SERVICE_CLASS); + mService = service; + if (mService == null) { + Slog.v(TAG, "FingerprintManagerService was null"); } } - private final ServiceConnection mFingerprintConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Log.v(TAG, "Connected to FingerprintService"); - mService = IFingerprintService.Stub.asInterface(service); - try { - mService.startListening(mServiceReceiver, getCurrentUserId()); - } catch (RemoteException e) { - if (DEBUG) Log.v(TAG, "Failed to set callback", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Log.v(TAG, "Disconnected from FingerprintService"); - mService = null; - } - }; - private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { public void onEnrollResult(int fingerprintId, int remaining) { mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget(); } - public void onScanned(int fingerprintId, int confidence) { - mHandler.obtainMessage(MSG_SCANNED, fingerprintId, confidence) - .sendToTarget();; + public void onAcquired(int acquireInfo) { + mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0).sendToTarget(); + } + + public void onProcessed(int fingerprintId) { + mHandler.obtainMessage(MSG_PROCESSED, fingerprintId, 0).sendToTarget(); } public void onError(int error) { @@ -152,12 +149,14 @@ public class FingerprintManager { */ public void enroll(long timeout) { if (mServiceReceiver == null) { - throw new IllegalStateException("enroll: Call registerCallback() first"); + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; } if (mService != null) try { - mService.enroll(timeout, getCurrentUserId()); + mService.enroll(mToken, timeout, getCurrentUserId()); } catch (RemoteException e) { Log.v(TAG, "Remote exception while enrolling: ", e); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -167,10 +166,19 @@ public class FingerprintManager { * @param fingerprintId */ public void remove(int fingerprintId) { - if (mService != null) try { - mService.remove(fingerprintId, getCurrentUserId()); - } catch (RemoteException e) { - Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + if (mServiceReceiver == null) { + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; + } + if (mService != null) { + try { + mService.remove(mToken, fingerprintId, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + } + } else { + Log.w(TAG, "remove(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -182,10 +190,13 @@ public class FingerprintManager { mClientReceiver = receiver; if (mService != null) { try { - mService.startListening(mServiceReceiver, getCurrentUserId()); + mService.startListening(mToken, mServiceReceiver, getCurrentUserId()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in startListening(): ", e); } + } else { + Log.w(TAG, "startListening(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); } } @@ -202,15 +213,38 @@ public class FingerprintManager { * Stops the client from listening to fingerprint events. */ public void stopListening() { - mClientReceiver = null; if (mService != null) { try { - mService.stopListening(getCurrentUserId()); + mService.stopListening(mToken, getCurrentUserId()); + mClientReceiver = null; } catch (RemoteException e) { Log.v(TAG, "Remote exception in stopListening(): ", e); } } else { Log.w(TAG, "stopListening(): Service not connected!"); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + } + } + + public void enrollCancel() { + if (mServiceReceiver == null) { + sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); + return; } + if (mService != null) { + try { + mService.enrollCancel(mToken, getCurrentUserId()); + mClientReceiver = null; + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in enrollCancel(): ", e); + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + } + } else { + Log.w(TAG, "enrollCancel(): Service not connected!"); + } + } + + private void sendError(int msg, int arg1, int arg2) { + mHandler.obtainMessage(msg, arg1, arg2); } }
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java index 5960791..e5193f5 100644 --- a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java +++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java @@ -13,7 +13,6 @@ package android.service.fingerprint; * 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. - * @hide */ public class FingerprintManagerReceiver { @@ -31,18 +30,32 @@ public class FingerprintManagerReceiver { public void onEnrollResult(int fingerprintId, int remaining) { } /** - * Fingerprint scan detected. Most clients will use this function to detect a fingerprint + * Fingerprint touch detected, but not processed yet. Clients will use this message to + * determine a good or bad scan before the fingerprint is processed. This is meant for the + * client to provide feedback about the scan or alert the user that recognition is to follow. * - * @param fingerprintId is the finger the hardware has detected. - * @param confidence from 0 (no confidence) to 65535 (high confidence). Fingerprint 0 has - * special meaning - the finger wasn't recognized. + * @param acquiredInfo one of: + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_GOOD}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_PARTIAL}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_INSUFFICIENT}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_IMAGER_DIRTY}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_SLOW}, + * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_FAST} */ - public void onScanned(int fingerprintId, int confidence) { } + public void onAcquired(int acquiredInfo) { } + + /** + * Fingerprint has been detected and processed. A non-zero return indicates a valid + * fingerprint was detected. + * + * @param fingerprintId the finger id, or 0 if not recognized. + */ + public void onProcessed(int fingerprintId) { } /** * An error was detected during scan or enrollment. One of * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}, - * {@link FingerprintManager#FINGERPRINT_ERROR_BAD_CAPTURE} or + * {@link FingerprintManager#FINGERPRINT_ERROR_UNABLE_TO_PROCESS} or * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT} * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE} * diff --git a/core/java/android/service/fingerprint/FingerprintService.java b/core/java/android/service/fingerprint/FingerprintService.java deleted file mode 100644 index c7fa7cd..0000000 --- a/core/java/android/service/fingerprint/FingerprintService.java +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.fingerprint; - -import android.app.Service; -import android.content.ContentResolver; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Slog; - -import java.io.PrintWriter; -import java.util.HashMap; - -/** - * A service to manage multiple clients that want to access the fingerprint HAL API. - * The service is responsible for maintaining a list of clients and dispatching all - * fingerprint -related events. - * - * @hide - */ -public class FingerprintService extends Service { - private final String TAG = FingerprintService.class.getSimpleName() + - "[" + getClass().getSimpleName() + "]"; - private static final boolean DEBUG = true; - HashMap<IFingerprintServiceReceiver, ClientData> mClients = - new HashMap<IFingerprintServiceReceiver, ClientData>(); - - private static final int MSG_NOTIFY = 10; - - Handler mHandler = new Handler() { - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_NOTIFY: - handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); - break; - - default: - Slog.w(TAG, "Unknown message:" + msg.what); - } - } - }; - - private static final int STATE_IDLE = 0; - private static final int STATE_LISTENING = 1; - private static final int STATE_ENROLLING = 2; - private static final int STATE_DELETING = 3; - private static final long MS_PER_SEC = 1000; - - private static final class ClientData { - public IFingerprintServiceReceiver receiver; - int state; - int userId; - } - - @Override - public final IBinder onBind(Intent intent) { - if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); - return new FingerprintServiceWrapper(); - } - - // JNI methods to communicate from FingerprintManagerService to HAL - native int nativeEnroll(int timeout); - native int nativeRemove(int fingerprintId); - - // JNI methods for communicating from HAL to clients - void notify(int msg, int arg1, int arg2) { - mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); - } - - void handleNotify(int msg, int arg1, int arg2) { - for (int i = 0; i < mClients.size(); i++) { - ClientData clientData = mClients.get(i); - switch (msg) { - case FingerprintManager.FINGERPRINT_ERROR: { - if (clientData.state != STATE_IDLE) { - // FINGERPRINT_ERROR_HW_UNAVAILABLE - // FINGERPRINT_ERROR_BAD_CAPTURE - // FINGERPRINT_ERROR_TIMEOUT - // FINGERPRINT_ERROR_NO_SPACE - final int error = arg1; - clientData.state = STATE_IDLE; - if (clientData.receiver != null) { - try { - clientData.receiver.onError(error); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - } - break; - case FingerprintManager.FINGERPRINT_SCANNED: { - final int fingerId = arg1; - final int confidence = arg2; - if (clientData.state == STATE_LISTENING && clientData.receiver != null) { - try { - clientData.receiver.onScanned(fingerId, confidence); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { - if (clientData.state == STATE_ENROLLING) { - final int fingerId = arg1; - final int remaining = arg2; - if (remaining == 0) { - FingerprintUtils.addFingerprintIdForUser(fingerId, - getContentResolver(), clientData.userId); - clientData.state = STATE_IDLE; // Nothing left to do - } - if (clientData.receiver != null) { - try { - clientData.receiver.onEnrollResult(fingerId, remaining); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { - int fingerId = arg1; - if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); - if (clientData.state == STATE_DELETING) { - FingerprintUtils.removeFingerprintIdForUser(fingerId, getContentResolver(), - clientData.userId); - if (clientData.receiver != null) { - try { - clientData.receiver.onRemoved(fingerId); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - } - } - } - } - break; - } - } - } - - int enroll(IFingerprintServiceReceiver receiver, long timeout, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_ENROLLING; - return nativeEnroll((int) (timeout / MS_PER_SEC)); - } - return -1; - } - - int remove(IFingerprintServiceReceiver receiver, int fingerId, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_DELETING; - // The fingerprint id will be removed when we get confirmation from the HAL - return nativeRemove(fingerId); - } - return -1; - } - - void startListening(IFingerprintServiceReceiver receiver, int userId) { - ClientData clientData = new ClientData(); - clientData.state = STATE_LISTENING; - clientData.receiver = receiver; - clientData.userId = userId; - mClients.put(receiver, clientData); - } - - void stopListening(IFingerprintServiceReceiver receiver, int userId) { - ClientData clientData = mClients.get(receiver); - if (clientData != null) { - clientData.state = STATE_IDLE; - clientData.userId = -1; - clientData.receiver = null; - } - mClients.remove(receiver); - } - - private final class FingerprintServiceWrapper extends IFingerprintService.Stub { - IFingerprintServiceReceiver mReceiver; - public int enroll(long timeout, int userId) { - return mReceiver != null ? FingerprintService.this.enroll(mReceiver, timeout, userId) - : FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER; - } - - public int remove(int fingerprintId, int userId) { - return FingerprintService.this.remove(mReceiver, fingerprintId, userId); - } - - public void startListening(IFingerprintServiceReceiver receiver, int userId) { - mReceiver = receiver; - FingerprintService.this.startListening(receiver, userId); - } - - public void stopListening(int userId) { - FingerprintService.this.stopListening(mReceiver, userId); - } - } -} diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java index 81a2aac..f4b5526 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -18,10 +18,12 @@ package android.service.fingerprint; import android.content.ContentResolver; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import java.util.Arrays; +public class FingerprintUtils { private static final boolean DEBUG = true; private static final String TAG = "FingerprintUtils"; @@ -30,13 +32,16 @@ class FingerprintUtils { String fingerIdsRaw = Settings.Secure.getStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, userId); - String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); - int result[] = new int[fingerStringIds.length]; - for (int i = 0; i < result.length; i++) { - try { - result[i] = Integer.decode(fingerStringIds[i]); - } catch (NumberFormatException e) { - if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + int result[] = {}; + if (!TextUtils.isEmpty(fingerIdsRaw)) { + String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); + result = new int[fingerStringIds.length]; + for (int i = 0; i < result.length; i++) { + try { + result[i] = Integer.decode(fingerStringIds[i]); + } catch (NumberFormatException e) { + if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + } } } return result; diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl index e92c20c..43d5e9a 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -22,17 +22,20 @@ import android.service.fingerprint.IFingerprintServiceReceiver; * Communication channel from client to the fingerprint service. * @hide */ -interface IFingerprintService { - // Returns 0 if successfully started, -1 otherwise - int enroll(long timeout, int userId); +oneway interface IFingerprintService { + // Any errors resulting from this call will be returned to the listener + void enroll(IBinder token, long timeout, int userId); + + // Any errors resulting from this call will be returned to the listener + void enrollCancel(IBinder token, int userId); - // Returns 0 if fingerprintId's template can be removed, -1 otherwise - int remove(int fingerprintId, int userId); + // Any errors resulting from this call will be returned to the listener + void remove(IBinder token, int fingerprintId, int userId); // Start listening for fingerprint events. This has the side effect of starting // the hardware if not already started. - oneway void startListening(IFingerprintServiceReceiver receiver, int userId); + void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId); // Stops listening for fingerprints - oneway void stopListening(int userId); + void stopListening(IBinder token, int userId); } diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl index 4826b59..af4128f 100644 --- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl @@ -24,7 +24,8 @@ import android.os.UserHandle; */ oneway interface IFingerprintServiceReceiver { void onEnrollResult(int fingerprintId, int remaining); - void onScanned(int fingerprintId, int confidence); + void onAcquired(int acquiredInfo); + void onProcessed(int fingerprintId); void onError(int error); void onRemoved(int fingerprintId); } diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index ed835e4..8fa45e6 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -17,7 +17,6 @@ package android.service.trust; import android.Manifest; -import android.annotation.SystemApi; import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; @@ -57,10 +56,7 @@ import android.util.Slog; * <pre> * <trust-agent xmlns:android="http://schemas.android.com/apk/res/android" * android:settingsActivity=".TrustAgentSettings" /></pre> - * - * @hide */ -@SystemApi public class TrustAgentService extends Service { private final String TAG = TrustAgentService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; @@ -84,6 +80,11 @@ public class TrustAgentService extends Service { private ITrustAgentServiceCallback mCallback; + private Runnable mPendingGrantTrustTask; + + // Lock used to access mPendingGrantTrustTask and mCallback. + private final Object mLock = new Object(); + private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { @@ -131,12 +132,24 @@ public class TrustAgentService extends Service { * @param initiatedByUser indicates that the user has explicitly initiated an action that proves * the user is about to use the device. */ - public final void grantTrust(CharSequence message, long durationMs, boolean initiatedByUser) { - if (mCallback != null) { - try { - mCallback.grantTrust(message.toString(), durationMs, initiatedByUser); - } catch (RemoteException e) { - onError("calling enableTrust()"); + public final void grantTrust( + final CharSequence message, final long durationMs, final boolean initiatedByUser) { + synchronized (mLock) { + if (mCallback != null) { + try { + mCallback.grantTrust(message.toString(), durationMs, initiatedByUser); + } catch (RemoteException e) { + onError("calling enableTrust()"); + } + } else { + // Remember trust has been granted so we can effectively grant it once the service + // is bound. + mPendingGrantTrustTask = new Runnable() { + @Override + public void run() { + grantTrust(message, durationMs, initiatedByUser); + } + }; } } } @@ -145,11 +158,16 @@ public class TrustAgentService extends Service { * Call to revoke trust on the device. */ public final void revokeTrust() { - if (mCallback != null) { - try { - mCallback.revokeTrust(); - } catch (RemoteException e) { - onError("calling revokeTrust()"); + synchronized (mLock) { + if (mPendingGrantTrustTask != null) { + mPendingGrantTrustTask = null; + } + if (mCallback != null) { + try { + mCallback.revokeTrust(); + } catch (RemoteException e) { + onError("calling revokeTrust()"); + } } } } @@ -168,7 +186,13 @@ public class TrustAgentService extends Service { } public void setCallback(ITrustAgentServiceCallback callback) { - mCallback = callback; + synchronized (mLock) { + mCallback = callback; + if (mPendingGrantTrustTask != null) { + mPendingGrantTrustTask.run(); + mPendingGrantTrustTask = null; + } + } } } diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/service/voice/DspInfo.java new file mode 100644 index 0000000..0862309 --- /dev/null +++ b/core/java/android/service/voice/DspInfo.java @@ -0,0 +1,56 @@ +/* + * 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.service.voice; + +import java.util.UUID; + +/** + * Properties of the DSP hardware on the device. + * @hide + */ +public class DspInfo { + /** + * Unique voice engine Id (changes with each version). + */ + public final UUID voiceEngineId; + + /** + * Human readable voice detection engine implementor. + */ + public final String voiceEngineImplementor; + /** + * Human readable voice detection engine description. + */ + public final String voiceEngineDescription; + /** + * Human readable voice detection engine version + */ + public final int voiceEngineVersion; + /** + * Rated power consumption when detection is active. + */ + public final int powerConsumptionMw; + + public DspInfo(UUID voiceEngineId, String voiceEngineImplementor, + String voiceEngineDescription, int version, int powerConsumptionMw) { + this.voiceEngineId = voiceEngineId; + this.voiceEngineImplementor = voiceEngineImplementor; + this.voiceEngineDescription = voiceEngineDescription; + this.voiceEngineVersion = version; + this.powerConsumptionMw = powerConsumptionMw; + } +} diff --git a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java new file mode 100644 index 0000000..ebe41ce --- /dev/null +++ b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java @@ -0,0 +1,246 @@ +/* + * 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.service.voice; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Slog; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; + +/** @hide */ +public class KeyphraseEnrollmentInfo { + private static final String TAG = "KeyphraseEnrollmentInfo"; + /** + * Name under which a Hotword enrollment component publishes information about itself. + * This meta-data should reference an XML resource containing a + * <code><{@link + * android.R.styleable#VoiceEnrollmentApplication + * voice-enrollment-application}></code> tag. + */ + private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment"; + /** + * Activity Action: Show activity for managing the keyphrases for hotword detection. + * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase + * detection. + */ + public static final String ACTION_MANAGE_VOICE_KEYPHRASES = + "com.android.intent.action.MANAGE_VOICE_KEYPHRASES"; + /** + * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase. + */ + public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL = + "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL"; + /** + * Intent extra: The hint text to be shown on the voice keyphrase management UI. + */ + public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT = + "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT"; + /** + * Intent extra: The voice locale to use while managing the keyphrase. + */ + public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = + "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE"; + + private KeyphraseInfo[] mKeyphrases; + private String mEnrollmentPackage; + private String mParseError; + + public KeyphraseEnrollmentInfo(PackageManager pm) { + // Find the apps that supports enrollment for hotword keyhphrases, + // Pick a privileged app and obtain the information about the supported keyphrases + // from its metadata. + List<ResolveInfo> ris = pm.queryIntentActivities( + new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY); + if (ris == null || ris.isEmpty()) { + // No application capable of enrolling for voice keyphrases is present. + mParseError = "No enrollment application found"; + return; + } + + boolean found = false; + ApplicationInfo ai = null; + for (ResolveInfo ri : ris) { + try { + ai = pm.getApplicationInfo( + ri.activityInfo.packageName, PackageManager.GET_META_DATA); + if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) { + // The application isn't privileged (/system/priv-app). + // The enrollment application needs to be a privileged system app. + Slog.w(TAG, ai.packageName + "is not a privileged system app"); + continue; + } + if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) { + // The application trying to manage keyphrases doesn't + // require the MANAGE_VOICE_KEYPHRASES permission. + Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES"); + continue; + } + mEnrollmentPackage = ai.packageName; + found = true; + break; + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "error parsing voice enrollment meta-data", e); + } + } + + if (!found) { + mKeyphrases = null; + mParseError = "No suitable enrollment application found"; + return; + } + + XmlResourceParser parser = null; + try { + parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA); + if (parser == null) { + mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for " + + ai.packageName; + return; + } + + Resources res = pm.getResourcesForApplication(ai); + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"voice-enrollment-application".equals(nodeName)) { + mParseError = "Meta-data does not start with voice-enrollment-application tag"; + return; + } + + TypedArray array = res.obtainAttributes(attrs, + com.android.internal.R.styleable.VoiceEnrollmentApplication); + int searchKeyphraseId = array.getInt( + com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, + -1); + if (searchKeyphraseId != -1) { + String searchKeyphrase = array.getString(com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphrase); + String searchKeyphraseSupportedLocales = + array.getString(com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); + String[] supportedLocales = new String[0]; + // Get all the supported locales from the comma-delimted string. + if (searchKeyphraseSupportedLocales != null + && !searchKeyphraseSupportedLocales.isEmpty()) { + supportedLocales = searchKeyphraseSupportedLocales.split(","); + } + mKeyphrases = new KeyphraseInfo[1]; + mKeyphrases[0] = new KeyphraseInfo( + searchKeyphraseId, searchKeyphrase, supportedLocales); + } else { + mParseError = "searchKeyphraseId not specified in meta-data"; + return; + } + } catch (XmlPullParserException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } catch (IOException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } catch (PackageManager.NameNotFoundException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } finally { + if (parser != null) parser.close(); + } + } + + public String getParseError() { + return mParseError; + } + + /** + * @return An array of available keyphrases that can be enrolled on the system. + * It may be null if no keyphrases can be enrolled. + */ + public KeyphraseInfo[] getKeyphrases() { + return mKeyphrases; + } + + /** + * Returns an intent to launch an activity that manages the given keyphrase + * for the locale. + * + * @param enroll Indicates if the intent should enroll the user or un-enroll them. + * @param keyphrase The keyphrase that the user needs to be enrolled to. + * @param locale The locale for which the enrollment needs to be performed. + * @return An {@link Intent} to manage the keyphrase. This can be null if managing the + * given keyphrase/locale combination isn't possible. + */ + public Intent getManageKeyphraseIntent(boolean enroll, String keyphrase, String locale) { + if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { + Slog.w(TAG, "No enrollment application exists"); + return null; + } + + if (isKeyphraseEnrollmentSupported(keyphrase, locale)) { + Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES) + .setPackage(mEnrollmentPackage) + .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase) + .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale); + if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true); + return intent; + } + return null; + } + + /** + * Indicates if enrollment is supported for the given keyphrase & locale. + * + * @param keyphrase The keyphrase that the user needs to be enrolled to. + * @param locale The locale for which the enrollment needs to be performed. + * @return true, if an enrollment client supports the given keyphrase and the given locale. + */ + public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) { + if (mKeyphrases == null || mKeyphrases.length == 0) { + Slog.w(TAG, "Enrollment application doesn't support keyphrases"); + return false; + } + for (KeyphraseInfo keyphraseInfo : mKeyphrases) { + // Check if the given keyphrase is supported in the locale provided by + // the enrollment application. + String supportedKeyphrase = keyphraseInfo.keyphrase; + if (supportedKeyphrase.equalsIgnoreCase(keyphrase) + && keyphraseInfo.supportedLocales.contains(locale)) { + return true; + } + } + Slog.w(TAG, "Enrollment application doesn't support the given keyphrase"); + return false; + } +} diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java new file mode 100644 index 0000000..d266e1a --- /dev/null +++ b/core/java/android/service/voice/KeyphraseInfo.java @@ -0,0 +1,27 @@ +package android.service.voice; + +import android.util.ArraySet; + +/** + * A Voice Keyphrase. + * @hide + */ +public class KeyphraseInfo { + public final int id; + public final String keyphrase; + public final ArraySet<String> supportedLocales; + + public KeyphraseInfo(int id, String keyphrase, String[] supportedLocales) { + this.id = id; + this.keyphrase = keyphrase; + this.supportedLocales = new ArraySet<String>(supportedLocales.length); + for (String locale : supportedLocales) { + this.supportedLocales.add(locale); + } + } + + @Override + public String toString() { + return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales; + } +} diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java new file mode 100644 index 0000000..2d049b9 --- /dev/null +++ b/core/java/android/service/voice/SoundTriggerManager.java @@ -0,0 +1,73 @@ +/* + * 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.service.voice; + +import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; + +import java.util.ArrayList; + +/** + * Manager for {@link SoundTrigger} APIs. + * Currently this just acts as an abstraction over all SoundTrigger API calls. + * @hide + */ +public class SoundTriggerManager { + /** The {@link DspInfo} for the system, or null if none exists. */ + public DspInfo dspInfo; + + public SoundTriggerManager() { + ArrayList <ModuleProperties> modules = new ArrayList<>(); + int status = SoundTrigger.listModules(modules); + if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { + // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here. + dspInfo = null; + } else { + // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the + // DSP hardware. + ModuleProperties properties = modules.get(0); + dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description, + properties.version, properties.powerConsumptionMw); + } + } + + /** + * @return True, if the keyphrase is supported on DSP for the given locale. + */ + public boolean isKeyphraseSupported(String keyphrase, String locale) { + // TODO(sansid): We also need to look into a SoundTrigger API that let's us + // query this. For now just return supported if there's a DSP available. + return dspInfo != null; + } + + /** + * @return True, if the keyphrase is has been enrolled for the given locale. + */ + public boolean isKeyphraseEnrolled(String keyphrase, String locale) { + // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models. + // They have been enrolled. + return false; + } + + /** + * @return True, if a recognition for the keyphrase is active for the given locale. + */ + public boolean isKeyphraseActive(String keyphrase, String locale) { + // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active. + return false; + } +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index e15489b..e0329f8 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -17,7 +17,6 @@ package android.service.voice; import android.annotation.SdkConstant; -import android.app.Instrumentation; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -25,8 +24,11 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; + +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractionManagerService; + /** * Top-level service of the current global voice interactor, which is providing * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. @@ -51,6 +53,16 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; + // TODO(sansid): Unhide these. + /** @hide */ + public static final int KEYPHRASE_UNAVAILABLE = 0; + /** @hide */ + public static final int KEYPHRASE_UNENROLLED = 1; + /** @hide */ + public static final int KEYPHRASE_ENROLLED = 2; + /** @hide */ + public static final int KEYPHRASE_ACTIVE = 3; + /** * Name under which a VoiceInteractionService component publishes information about itself. * This meta-data should reference an XML resource containing a @@ -64,6 +76,9 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; + private SoundTriggerManager mSoundTriggerManager; + private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; + public void startSession(Bundle args) { try { mSystemService.startSession(mInterface, args); @@ -76,6 +91,8 @@ public class VoiceInteractionService extends Service { super.onCreate(); mSystemService = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); + mSoundTriggerManager = new SoundTriggerManager(); } @Override @@ -85,4 +102,44 @@ public class VoiceInteractionService extends Service { } return null; } + + /** + * Gets the state of always-on hotword detection for the given keyphrase and locale + * on this system. + * Availability implies that the hardware on this system is capable of listening for + * the given keyphrase or not. + * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED} + * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}. + * + * @param keyphrase The keyphrase whose availability is being checked. + * @param locale The locale for which the availability is being checked. + * @return Indicates if always-on hotword detection is available for the given keyphrase. + * TODO(sansid): Unhide this. + * @hide + */ + public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) { + // The available keyphrases is a combination of DSP availability and + // the keyphrases that have an enrollment application for them. + if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale) + || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) { + return KEYPHRASE_UNAVAILABLE; + } + if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) { + return KEYPHRASE_UNENROLLED; + } + if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) { + return KEYPHRASE_ENROLLED; + } else { + return KEYPHRASE_ACTIVE; + } + } + + /** + * @return Details of keyphrases available for enrollment. + * @hide + */ + @VisibleForTesting + protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { + return mKeyphraseEnrollmentInfo; + } } diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java new file mode 100644 index 0000000..c886e5d --- /dev/null +++ b/core/java/android/speech/tts/Markup.java @@ -0,0 +1,537 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that provides markup to a synthesis request to control aspects of speech. + * <p> + * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently + * available set of features and should be used to construct instances of the Markup class. + * </p> + * <p> + * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of + * parameters, and a list of children. + * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node + * can be either a part of sentence (often a leaf node), or node altering some property of its + * children (node with children). The top level node has to be of type "utterance" and its children + * are synthesized in order. + * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not + * support Markup at all, it should use the plain text of the top level node. If an engine does not + * recognize or support a node type, it will try to use the plain text of that node if provided. If + * the plain text is null, it will synthesize its children in order. + * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the + * parameters may be for example "month: 7" and "day: 10". + * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a + * measure may have a node of type "decimal" as its child) or to modify some property of its + * children. See "plain text" on how they are processed if the parent of the children is unknown to + * the engine. + * <p> + */ +public final class Markup implements Parcelable { + + private String mType; + private String mPlainText; + + private Bundle mParameters = new Bundle(); + private List<Markup> mNestedMarkups = new ArrayList<Markup>(); + + private static final String TYPE = "type"; + private static final String PLAIN_TEXT = "plain_text"; + private static final String MARKUP = "markup"; + + private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)"; + private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX); + + /** + * Constructs an empty markup. + */ + public Markup() {} + + /** + * Constructs a markup of the given type. + */ + public Markup(String type) { + setType(type); + } + + /** + * Returns the type of this node; can be null. + */ + public String getType() { + return mType; + } + + /** + * Sets the type of this node. can be null. May only contain [0-9a-z_]. + */ + public void setType(String type) { + if (type != null) { + Matcher matcher = legalIdentifierPattern.matcher(type); + if (!matcher.matches()) { + throw new IllegalArgumentException("Type cannot be empty and may only contain " + + "0-9, a-z and underscores."); + } + } + mType = type; + } + + /** + * Returns this node's plain text; can be null. + */ + public String getPlainText() { + return mPlainText; + } + + /** + * Sets this nodes's plain text; can be null. + */ + public void setPlainText(String plainText) { + mPlainText = plainText; + } + + /** + * Adds or modifies a parameter. + * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text". + * @param value The value. + * @throws An {@link IllegalArgumentException} if the key is null or empty. + * @return this + */ + public Markup setParameter(String key, String value) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be null or empty."); + } + if (key.equals("type")) { + throw new IllegalArgumentException("Key cannot be \"type\"."); + } + if (key.equals("plain_text")) { + throw new IllegalArgumentException("Key cannot be \"plain_text\"."); + } + Matcher matcher = legalIdentifierPattern.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores."); + } + + if (value != null) { + mParameters.putString(key, value); + } else { + removeParameter(key); + } + return this; + } + + /** + * Removes the parameter with the given key + */ + public void removeParameter(String key) { + mParameters.remove(key); + } + + /** + * Returns the value of the parameter. + * @param key The parameter key. + * @return The value of the parameter or null if the parameter is not set. + */ + public String getParameter(String key) { + return mParameters.getString(key); + } + + /** + * Returns the number of parameters that have been set. + */ + public int parametersSize() { + return mParameters.size(); + } + + /** + * Appends a child to the list of children + * @param markup The child. + * @return This instance. + * @throws {@link IllegalArgumentException} if markup is null. + */ + public Markup addNestedMarkup(Markup markup) { + if (markup == null) { + throw new IllegalArgumentException("Nested markup cannot be null"); + } + mNestedMarkups.add(markup); + return this; + } + + /** + * Removes the given node from its children. + * @param markup The child to remove. + * @return True if this instance was modified by this operation, false otherwise. + */ + public boolean removeNestedMarkup(Markup markup) { + return mNestedMarkups.remove(markup); + } + + /** + * Returns the index'th child. + * @param i The index of the child. + * @return The child. + * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize() + */ + public Markup getNestedMarkup(int i) { + return mNestedMarkups.get(i); + } + + + /** + * Returns the number of children. + */ + public int nestedMarkupSize() { + return mNestedMarkups.size(); + } + + /** + * Returns a string representation of this Markup instance. Can be deserialized back to a Markup + * instance with markupFromString(). + */ + public String toString() { + StringBuilder out = new StringBuilder(); + if (mType != null) { + out.append(TYPE + ": \"" + mType + "\""); + } + if (mPlainText != null) { + out.append(out.length() > 0 ? " " : ""); + out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\""); + } + // Sort the parameters alphabetically by key so we have a stable output. + SortedMap<String, String> sortedMap = new TreeMap<String, String>(); + for (String key : mParameters.keySet()) { + sortedMap.put(key, mParameters.getString(key)); + } + for (Map.Entry<String, String> entry : sortedMap.entrySet()) { + out.append(out.length() > 0 ? " " : ""); + out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\""); + } + for (Markup m : mNestedMarkups) { + out.append(out.length() > 0 ? " " : ""); + String nestedStr = m.toString(); + if (nestedStr.isEmpty()) { + out.append(MARKUP + " {}"); + } else { + out.append(MARKUP + " { " + m.toString() + " }"); + } + } + return out.toString(); + } + + /** + * Escapes backslashes and double quotes in the plain text and parameter values before this + * instance is written to a string. + * @param str The string to escape. + * @return The escaped string. + */ + private static String escapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '"') { + out.append("\\\""); + } else if (str.charAt(i) == '\\') { + out.append("\\\\"); + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * The reverse of the escape method, returning plain text and parameter values to their original + * form. + * @param str An escaped string. + * @return The unescaped string. + */ + private static String unescapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '\\') { + i++; + if (i >= str.length()) { + throw new IllegalArgumentException("Unterminated escape sequence in string: " + + str); + } + c = str.charAt(i); + if (c == '\\') { + out.append("\\"); + } else if (c == '"') { + out.append("\""); + } else { + throw new IllegalArgumentException("Unsupported escape sequence: \\" + c + + " in string " + str); + } + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * Returns true if the given string consists only of whitespace. + * @param str The string to check. + * @return True if the given string consists only of whitespace. + */ + private static boolean isWhitespace(String str) { + return Pattern.matches("\\s*", str); + } + + /** + * Parses the given string, and overrides the values of this instance with those contained + * in the given string. + * @param str The string to parse; can have superfluous whitespace. + * @return An empty string on success, else the remainder of the string that could not be + * parsed. + */ + private String fromReadableString(String str) { + while (!isWhitespace(str)) { + String newStr = matchValue(str); + if (newStr == null) { + newStr = matchMarkup(str); + + if (newStr == null) { + return str; + } + } + str = newStr; + } + return ""; + } + + // Matches: key : "value" + // where key is an identifier and value can contain escaped quotes + // there may be superflouous whitespace + // The value string may contain quotes and backslashes. + private static final String OPTIONAL_WHITESPACE = "\\s*"; + private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)"; + private static final String KEY_VALUE_REGEX = + "\\A" + OPTIONAL_WHITESPACE + // start of string + IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key: + "\"" + VALUE_REGEX + "\""; // "value" + private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX); + + /** + * Tries to match a key-value pair at the start of the string. If found, add that as a parameter + * of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed key-value pair on success, else null. + */ + private String matchValue(String str) { + // Matches: key: "value" + Matcher matcher = KEY_VALUE_PATTERN.matcher(str); + if (!matcher.find()) { + return null; + } + String key = matcher.group(1); + String value = matcher.group(2); + + if (key == null || value == null) { + return null; + } + String unescapedValue = unescapeQuotedString(value); + if (key.equals(TYPE)) { + this.mType = unescapedValue; + } else if (key.equals(PLAIN_TEXT)) { + this.mPlainText = unescapedValue; + } else { + setParameter(key, unescapedValue); + } + + return str.substring(matcher.group(0).length()); + } + + // matches 'markup {' + private static final Pattern OPEN_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{"); + // matches '}' + private static final Pattern CLOSE_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}"); + + /** + * Tries to parse a Markup specification from the start of the string. If so, add that markup to + * the list of nested Markup's of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed Markup on success, else null. + */ + private String matchMarkup(String str) { + // find and strip "markup {" + Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str); + + if (!matcher.find()) { + return null; + } + String strRemainder = str.substring(matcher.group(0).length()); + // parse and strip markup contents + Markup nestedMarkup = new Markup(); + strRemainder = nestedMarkup.fromReadableString(strRemainder); + + // find and strip "}" + Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder); + if (!matcherClose.find()) { + return null; + } + strRemainder = strRemainder.substring(matcherClose.group(0).length()); + + // Everything parsed, add markup + this.addNestedMarkup(nestedMarkup); + + // Return remainder + return strRemainder; + } + + /** + * Returns a Markup instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Markup instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + public static Markup markupFromString(String string) throws IllegalArgumentException { + Markup m = new Markup(); + if (m.fromReadableString(string).isEmpty()) { + return m; + } else { + throw new IllegalArgumentException("Cannot parse input to Markup"); + } + } + + /** + * Compares the specified object with this Markup for equality. + * @return True if the given object is a Markup instance with the same type, plain text, + * parameters and the nested markups are also equal to each other and in the same order. + */ + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Markup) ) return false; + Markup m = (Markup) o; + + if (nestedMarkupSize() != this.nestedMarkupSize()) { + return false; + } + + if (!(mType == null ? m.mType == null : mType.equals(m.mType))) { + return false; + } + if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) { + return false; + } + if (!equalBundles(mParameters, m.mParameters)) { + return false; + } + + for (int i = 0; i < this.nestedMarkupSize(); i++) { + if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) { + return false; + } + } + + return true; + } + + /** + * Checks if two bundles are equal to each other. Used by equals(o). + */ + private boolean equalBundles(Bundle one, Bundle two) { + if (one == null || two == null) { + return false; + } + + if(one.size() != two.size()) { + return false; + } + + Set<String> valuesOne = one.keySet(); + for(String key : valuesOne) { + Object valueOne = one.get(key); + Object valueTwo = two.get(key); + if (valueOne instanceof Bundle && valueTwo instanceof Bundle && + !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) { + return false; + } else if (valueOne == null) { + if (valueTwo != null || !two.containsKey(key)) { + return false; + } + } else if(!valueOne.equals(valueTwo)) { + return false; + } + } + return true; + } + + /** + * Returns an unmodifiable list of the children. + * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException} + * if an attempt is made to modify it + */ + public List<Markup> getNestedMarkups() { + return Collections.unmodifiableList(mNestedMarkups); + } + + /** + * @hide + */ + public Markup(Parcel in) { + mType = in.readString(); + mPlainText = in.readString(); + mParameters = in.readBundle(); + in.readList(mNestedMarkups, Markup.class.getClassLoader()); + } + + /** + * Creates a deep copy of the given markup. + */ + public Markup(Markup markup) { + mType = markup.mType; + mPlainText = markup.mPlainText; + mParameters = markup.mParameters; + for (Markup nested : markup.getNestedMarkups()) { + addNestedMarkup(new Markup(nested)); + } + } + + /** + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mType); + dest.writeString(mPlainText); + dest.writeBundle(mParameters); + dest.writeList(mNestedMarkups); + } + + /** + * @hide + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Markup createFromParcel(Parcel in) { + return new Markup(in); + } + + public Markup[] newArray(int size) { + return new Markup[size]; + } + }; +} + diff --git a/core/java/android/speech/tts/RequestConfig.java b/core/java/android/speech/tts/RequestConfig.java index 72109d3..84880c0 100644 --- a/core/java/android/speech/tts/RequestConfig.java +++ b/core/java/android/speech/tts/RequestConfig.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.media.AudioManager; @@ -8,7 +23,6 @@ import android.os.Bundle; * * This class is immutable, and can only be constructed using * {@link RequestConfig.Builder}. - * @hide */ public final class RequestConfig { diff --git a/core/java/android/speech/tts/RequestConfigHelper.java b/core/java/android/speech/tts/RequestConfigHelper.java index 2e548af..bc65280 100644 --- a/core/java/android/speech/tts/RequestConfigHelper.java +++ b/core/java/android/speech/tts/RequestConfigHelper.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.speech.tts.TextToSpeechClient.EngineStatus; @@ -7,7 +22,6 @@ import java.util.Locale; /** * Set of common heuristics for selecting {@link VoiceInfo} from * {@link TextToSpeechClient#getEngineStatus()} output. - * @hide */ public final class RequestConfigHelper { private RequestConfigHelper() {} @@ -32,7 +46,8 @@ public final class RequestConfigHelper { /** * Score positively voices that exactly match the given locale - * @param locale Reference locale. If null, the default locale will be used. + * @param locale Reference locale. If null, the system default locale for the + * current user will be used ({@link Locale#getDefault()}). */ public ExactLocaleMatcher(Locale locale) { if (locale == null) { @@ -56,7 +71,8 @@ public final class RequestConfigHelper { /** * Score positively voices with similar locale. - * @param locale Reference locale. If null, default will be used. + * @param locale Reference locale. If null, the system default locale for the + * current user will be used ({@link Locale#getDefault()}). */ public LanguageMatcher(Locale locale) { if (locale == null) { @@ -150,10 +166,10 @@ public final class RequestConfigHelper { } /** - * Get highest quality voice for the default locale. + * Get highest quality voice for the TTS default locale. * * Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with - * {@link LanguageMatcher} set to device default locale. + * {@link LanguageMatcher} set to the {@link EngineStatus#getDefaultLocale()}. * * @param engineStatus * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call. @@ -165,7 +181,7 @@ public final class RequestConfigHelper { public static RequestConfig highestQuality(EngineStatus engineStatus, boolean hasToBeEmbedded) { return highestQuality(engineStatus, hasToBeEmbedded, - new LanguageMatcher(Locale.getDefault())); + new LanguageMatcher(engineStatus.getDefaultLocale())); } } diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java index 7b319b7..bc2f239 100644 --- a/core/java/android/speech/tts/SynthesisCallback.java +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -43,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}, {@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); @@ -57,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. @@ -65,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); @@ -73,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} 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(); @@ -99,7 +108,6 @@ public interface SynthesisCallback { * * @param errorCode Error code to pass to the client. One of the ERROR_ values from * {@link TextToSpeechClient.Status} - * @hide */ public void error(int errorCode); @@ -120,7 +128,6 @@ public interface SynthesisCallback { * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR} if client already * called {@link #start(int, int, int)}, {@link TextToSpeechClient.Status#STOPPED} * if stop was requested. - * @hide */ public int fallback(); @@ -132,7 +139,6 @@ public interface SynthesisCallback { * {@link TextToSpeechService#onSynthesizeTextV2}. * * Useful for checking if a fallback from network request is possible. - * @hide */ public boolean hasStarted(); @@ -144,7 +150,6 @@ public interface SynthesisCallback { * {@link TextToSpeechService#onSynthesizeTextV2}. * * Useful for checking if a fallback from network request is possible. - * @hide */ public boolean hasFinished(); } diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java index beb7307..938458c 100644 --- a/core/java/android/speech/tts/SynthesisRequestV2.java +++ b/core/java/android/speech/tts/SynthesisRequestV2.java @@ -1,24 +1,42 @@ +/* + * 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.speech.tts; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.speech.tts.TextToSpeechClient.UtteranceId; +import android.util.Log; /** * Service-side representation of a synthesis request from a V2 API client. Contains: * <ul> - * <li>The utterance to synthesize</li> + * <li>The markup object to synthesize containing the utterance.</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> - * @hide */ public final class SynthesisRequestV2 implements Parcelable { - /** Synthesis utterance. */ - private final String mText; + + private static final String TAG = "SynthesisRequestV2"; + + /** Synthesis markup */ + private final Markup mMarkup; /** Synthesis id. */ private final String mUtteranceId; @@ -35,9 +53,9 @@ public final class SynthesisRequestV2 implements Parcelable { /** * Constructor for test purposes. */ - public SynthesisRequestV2(String text, String utteranceId, String voiceName, + public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName, Bundle voiceParams, Bundle audioParams) { - this.mText = text; + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = voiceName; this.mVoiceParams = voiceParams; @@ -50,15 +68,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @hide */ public SynthesisRequestV2(Parcel in) { - this.mText = in.readString(); + this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader()); 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; + /** + * Constructor to request the synthesis of a sentence. + */ + SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) { + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = rconfig.getVoice().getName(); this.mVoiceParams = rconfig.getVoiceParams(); @@ -72,7 +93,7 @@ public final class SynthesisRequestV2 implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText); + dest.writeValue(mMarkup); dest.writeString(mUtteranceId); dest.writeString(mVoiceName); dest.writeBundle(mVoiceParams); @@ -83,7 +104,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @return the text which should be synthesized. */ public String getText() { - return mText; + if (mMarkup.getPlainText() == null) { + Log.e(TAG, "Plaintext of markup is null."); + return ""; + } + return mMarkup.getPlainText(); + } + + /** + * @return the markup which should be synthesized. + */ + public Markup getMarkup() { + return mMarkup; } /** @@ -135,9 +167,7 @@ public final class SynthesisRequestV2 implements Parcelable { } }; - /** - * @hide - */ + /** @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 cc86ed7..c527acf 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -53,7 +53,10 @@ import java.util.Set; * notified of the completion of the initialization.<br> * 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"; diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java index 2a24229..f726743 100644 --- a/core/java/android/speech/tts/TextToSpeechClient.java +++ b/core/java/android/speech/tts/TextToSpeechClient.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; /** @@ -71,7 +72,6 @@ import java.util.concurrent.atomic.AtomicInteger; * 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. - * @hide */ public class TextToSpeechClient { private static final String TAG = TextToSpeechClient.class.getSimpleName(); @@ -358,9 +358,13 @@ public class TextToSpeechClient { /** Name of the TTS engine package */ private final String mPackageName; - private EngineStatus(String packageName, List<VoiceInfo> voices) { + /** Engine default locale */ + private final Locale mDefaultLocale; + + private EngineStatus(String packageName, List<VoiceInfo> voices, Locale defaultLocale) { this.mVoices = Collections.unmodifiableList(voices); this.mPackageName = packageName; + this.mDefaultLocale = defaultLocale; } /** @@ -376,6 +380,16 @@ public class TextToSpeechClient { public String getEnginePackage() { return mPackageName; } + + /** + * Get the default locale to use for TTS with this TTS engine. + * Unless the user changed the TTS settings for this engine, the value returned should be + * the same as the system default locale for the current user + * ({@link Locale#getDefault()}). + */ + public Locale getDefaultLocale() { + return mDefaultLocale; + } } /** Unique synthesis request identifier. */ @@ -513,7 +527,6 @@ public class TextToSpeechClient { } } - /** * Connects the client to TTS service. This method returns immediately, and connects to the * service in the background. @@ -640,7 +653,9 @@ public class TextToSpeechClient { return null; } - return new EngineStatus(mServiceConnection.getEngineName(), voices); + return new EngineStatus(mServiceConnection.getEngineName(), voices, + mEnginesHelper.getLocalePrefForEngine( + mServiceConnection.getEngineName())); } private class Connection implements ServiceConnection { @@ -698,7 +713,9 @@ public class TextToSpeechClient { public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) { synchronized (mLock) { mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(), - voicesInfo); + voicesInfo, + mEnginesHelper.getLocalePrefForEngine( + mServiceConnection.getEngineName())); mMainHandler.obtainMessage(InternalHandler.WHAT_ENGINE_STATUS_CHANGED, mEngineStatus).sendToTarget(); } @@ -795,15 +812,14 @@ public class TextToSpeechClient { return mService != null && mEstablished; } - boolean runAction(Action action) { + <T> ActionResult<T> runAction(Action<T> action) { synchronized (mLock) { try { - action.run(mService); - return true; + return new ActionResult<T>(true, action.run(mService)); } catch (Exception ex) { Log.e(TAG, action.getName() + " failed", ex); disconnect(); - return false; + return new ActionResult<T>(false); } } } @@ -823,7 +839,7 @@ public class TextToSpeechClient { } } - private abstract class Action { + private abstract class Action<T> { private final String mName; public Action(String name) { @@ -831,7 +847,21 @@ public class TextToSpeechClient { } public String getName() {return mName;} - abstract void run(ITextToSpeechService service) throws RemoteException; + abstract T run(ITextToSpeechService service) throws RemoteException; + } + + private class ActionResult<T> { + boolean mSuccess; + T mResult; + + ActionResult(boolean success) { + mSuccess = success; + } + + ActionResult(boolean success, T result) { + mSuccess = success; + mResult = result; + } } private IBinder getCallerIdentity() { @@ -841,18 +871,17 @@ public class TextToSpeechClient { return null; } - private boolean runAction(Action action) { + private <T> ActionResult<T> runAction(Action<T> action) { synchronized (mLock) { if (mServiceConnection == null) { Log.w(TAG, action.getName() + " failed: not bound to TTS engine"); - return false; + return new ActionResult<T>(false); } if (!mServiceConnection.isEstablished()) { Log.w(TAG, action.getName() + " failed: not fully bound to TTS engine"); - return false; + return new ActionResult<T>(false); } - mServiceConnection.runAction(action); - return true; + return mServiceConnection.runAction(action); } } @@ -863,13 +892,14 @@ public class TextToSpeechClient { * other utterances in the queue. */ public void stop() { - runAction(new Action(ACTION_STOP_NAME) { + runAction(new Action<Void>(ACTION_STOP_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { if (service.stop(getCallerIdentity()) != Status.SUCCESS) { Log.e(TAG, "Stop failed"); } mCallbacks.clear(); + return null; } }); } @@ -877,7 +907,7 @@ public class TextToSpeechClient { private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak"; /** - * Speaks the string using the specified queuing strategy using current + * Speaks the string using the specified queuing strategy and the 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. @@ -888,15 +918,38 @@ public class TextToSpeechClient { * 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 + * @param callbacks Synthesis request callbacks. If null, the 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) { + queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks); + } + + /** + * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using + * the specified queuing strategy and the 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 markup The Markup to be spoken. The written equivalent of the spoken + * text should be 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, the default request + * callbacks object will be used. + */ + public void queueSpeak(final Markup markup, + final UtteranceId utteranceId, + final RequestConfig config, + final RequestCallbacks callbacks) { + runAction(new Action<Void>(ACTION_QUEUE_SPEAK_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -904,15 +957,16 @@ public class TextToSpeechClient { int addCallbackStatus = addCallback(utteranceId, c); if (addCallbackStatus != Status.SUCCESS) { c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); - return; + return null; } int queueResult = service.speakV2( getCallerIdentity(), - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } + return null; } }); } @@ -932,15 +986,40 @@ public class TextToSpeechClient { * @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 + * @param callbacks Synthesis request callbacks. If null, the 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) { + queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks); + } + + /** + * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance}) + * 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 markup The Markup that should be synthesized. The written equivalent of + * the spoken text should be 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, the default request + * callbacks object will be used. + */ + public void queueSynthesizeToFile( + final Markup markup, + final UtteranceId utteranceId, + final File outputFile, final RequestConfig config, + final RequestCallbacks callbacks) { + runAction(new Action<Void>(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -948,7 +1027,7 @@ public class TextToSpeechClient { int addCallbackStatus = addCallback(utteranceId, c); if (addCallbackStatus != Status.SUCCESS) { c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); - return; + return null; } ParcelFileDescriptor fileDescriptor = null; @@ -956,7 +1035,7 @@ public class TextToSpeechClient { if (outputFile.exists() && !outputFile.canWrite()) { Log.e(TAG, "No permissions to write to " + outputFile); removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); - return; + return null; } fileDescriptor = ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_WRITE_ONLY | @@ -965,8 +1044,7 @@ public class TextToSpeechClient { int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(), fileDescriptor, - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), - config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); fileDescriptor.close(); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); @@ -978,10 +1056,18 @@ public class TextToSpeechClient { Log.e(TAG, "Closing file " + outputFile + " failed", e); removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); } + return null; } }); } + private static Markup createMarkupFromString(String str) { + return new Utterance() + .append(new Utterance.TtsText(str)) + .setNoWarningOnFallback(true) + .createMarkup(); + } + private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence"; /** @@ -998,9 +1084,9 @@ public class TextToSpeechClient { */ public void queueSilence(final long durationInMs, final UtteranceId utteranceId, final RequestCallbacks callbacks) { - runAction(new Action(ACTION_QUEUE_SILENCE_NAME) { + runAction(new Action<Void>(ACTION_QUEUE_SILENCE_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -1016,6 +1102,7 @@ public class TextToSpeechClient { if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } + return null; } }); } @@ -1039,9 +1126,9 @@ public class TextToSpeechClient { */ public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId, final RequestConfig config, final RequestCallbacks callbacks) { - runAction(new Action(ACTION_QUEUE_AUDIO_NAME) { + runAction(new Action<Void>(ACTION_QUEUE_AUDIO_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -1057,10 +1144,35 @@ public class TextToSpeechClient { if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } + return null; } }); } + private static final String ACTION_IS_SPEAKING_NAME = "isSpeaking"; + + /** + * Checks whether the TTS engine is busy speaking. Note that a speech item is + * considered complete once it's audio data has been sent to the audio mixer, or + * written to a file. There might be a finite lag between this point, and when + * the audio hardware completes playback. + * + * @return {@code true} if the TTS engine is speaking. + */ + public boolean isSpeaking() { + ActionResult<Boolean> result = runAction(new Action<Boolean>(ACTION_IS_SPEAKING_NAME) { + @Override + public Boolean run(ITextToSpeechService service) throws RemoteException { + return service.isSpeaking(); + } + }); + if (!result.mSuccess) { + return false; // We can't really say, return false + } + return result.mResult; + } + + class InternalHandler extends Handler { final static int WHAT_ENGINE_STATUS_CHANGED = 1; final static int WHAT_SERVICE_DISCONNECTED = 2; diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index a728472..20f3ad7 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -74,6 +74,26 @@ import java.util.Set; * * {@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 { @@ -225,7 +245,6 @@ public abstract class TextToSpeechService extends Service { * 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. - * @hide */ protected List<VoiceInfo> checkVoicesInfo() { if (implementsV2API()) { @@ -293,7 +312,6 @@ public abstract class TextToSpeechService extends Service { * 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. - * @hide */ protected void onVoicesInfoChange() { @@ -308,7 +326,6 @@ public abstract class TextToSpeechService extends Service { * @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. - * @hide */ protected void onSynthesizeTextV2(SynthesisRequestV2 request, VoiceInfo selectedVoice, @@ -335,6 +352,12 @@ public abstract class TextToSpeechService extends Service { params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true"); } + String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK); + if (noWarning == null || noWarning.equals("false")) { + Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " + + "back to the given plain text."); + } + // Build V1 request SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params); Locale locale = selectedVoice.getLocale(); @@ -350,7 +373,6 @@ public abstract class TextToSpeechService extends Service { /** * If true, this service implements proper V2 TTS API service. If it's false, * V2 API will be provided through adapter. - * @hide */ protected boolean implementsV2API() { return false; @@ -389,9 +411,6 @@ public abstract class TextToSpeechService extends Service { } } - /** - * @hide - */ public VoiceInfo getVoicesInfoWithName(String name) { synchronized (mVoicesInfoLock) { if (mVoicesInfoLookup != null) { @@ -411,7 +430,6 @@ public abstract class TextToSpeechService extends Service { * Use this method only if you know that set of available languages changed. * * Can be called on multiple threads. - * @hide */ public void forceVoicesInfoCheck() { synchronized (mVoicesInfoLock) { @@ -442,8 +460,8 @@ public abstract class TextToSpeechService extends Service { } private String[] getSettingsLocale() { - final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName); - return TtsEngines.parseLocalePref(locale); + final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName); + return TtsEngines.toOldLocaleStringFormat(locale); } private int getSecureSettingInt(String name, int defaultValue) { @@ -844,14 +862,53 @@ public abstract class TextToSpeechService extends Service { } } + /** + * Estimate of the character count equivalent of a Markup instance. Calculated + * by summing the characters of all Markups of type "text". Each other node + * is counted as a single character, as the character count of other nodes + * is non-trivial to calculate and we don't want to accept arbitrarily large + * requests. + */ + private int estimateSynthesisLengthFromMarkup(Markup m) { + int size = 0; + if (m.getType() != null && + m.getType().equals("text") && + m.getParameter("text") != null) { + size += m.getParameter("text").length(); + } else if (m.getType() == null || + !m.getType().equals("utterance")) { + size += 1; + } + for (Markup nested : m.getNestedMarkups()) { + size += estimateSynthesisLengthFromMarkup(nested); + } + return size; + } + @Override public boolean isValid() { - if (mSynthesisRequest.getText() == null) { - Log.e(TAG, "null synthesis text"); + if (mSynthesisRequest.getMarkup() == null) { + Log.e(TAG, "No markup in request."); return false; } - if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) { - Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars"); + String type = mSynthesisRequest.getMarkup().getType(); + if (type == null) { + Log.w(TAG, "Top level markup node should have type \"utterance\", not null"); + return false; + } else if (!type.equals("utterance")) { + Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " + + "\"" + type + "\""); + return false; + } + + int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup()); + if (estimate >= TextToSpeech.getMaxSpeechInputLength()) { + Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars."); + return false; + } + + if (estimate <= 0) { + Log.e(TAG, "null synthesis text"); return false; } diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index 9b929a3..7474efe 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -28,6 +28,7 @@ import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; + import static android.provider.Settings.Secure.getString; import android.provider.Settings; @@ -42,8 +43,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.MissingResourceException; /** @@ -53,16 +56,52 @@ import java.util.MissingResourceException; * Comments in this class the use the shorthand "system engines" for engines that * are a part of the system image. * + * This class is thread-safe/ + * * @hide */ public class TtsEngines { private static final String TAG = "TtsEngines"; private static final boolean DBG = false; - private static final String LOCALE_DELIMITER = "-"; + /** Locale delimiter used by the old-style 3 char locale string format (like "eng-usa") */ + private static final String LOCALE_DELIMITER_OLD = "-"; + + /** Locale delimiter used by the new-style locale string format (Locale.toString() results, + * like "en_US") */ + private static final String LOCALE_DELIMITER_NEW = "_"; private final Context mContext; + /** Mapping of various language strings to the normalized Locale form */ + private static final Map<String, String> sNormalizeLanguage; + + /** Mapping of various country strings to the normalized Locale form */ + private static final Map<String, String> sNormalizeCountry; + + // Populate the sNormalize* maps + static { + HashMap<String, String> normalizeLanguage = new HashMap<String, String>(); + for (String language : Locale.getISOLanguages()) { + try { + normalizeLanguage.put(new Locale(language).getISO3Language(), language); + } catch (MissingResourceException e) { + continue; + } + } + sNormalizeLanguage = Collections.unmodifiableMap(normalizeLanguage); + + HashMap<String, String> normalizeCountry = new HashMap<String, String>(); + for (String country : Locale.getISOCountries()) { + try { + normalizeCountry.put(new Locale("", country).getISO3Country(), country); + } catch (MissingResourceException e) { + continue; + } + } + sNormalizeCountry = Collections.unmodifiableMap(normalizeCountry); + } + public TtsEngines(Context ctx) { mContext = ctx; } @@ -282,139 +321,139 @@ public class TtsEngines { } /** - * Returns the locale string for a given TTS engine. Attempts to read the + * Returns the default locale for a given TTS engine. Attempts to read the * value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the - * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If - * both these values are empty, the default phone locale is returned. + * default phone locale is returned. * * @param engineName the engine to return the locale for. - * @return the locale string preference for this engine. Will be non null - * and non empty. + * @return the locale preference for this engine. Will be non null. */ - public String getLocalePrefForEngine(String engineName) { - String locale = parseEnginePrefFromList( - getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE), + public Locale getLocalePrefForEngine(String engineName) { + return getLocalePrefForEngine(engineName, + getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE)); + } + + /** + * Returns the default locale for a given TTS engine from given settings string. */ + public Locale getLocalePrefForEngine(String engineName, String prefValue) { + String localeString = parseEnginePrefFromList( + prefValue, engineName); - if (TextUtils.isEmpty(locale)) { + if (TextUtils.isEmpty(localeString)) { // The new style setting is unset, attempt to return the old style setting. - locale = getV1Locale(); + return Locale.getDefault(); + } + + Locale result = parseLocaleString(localeString); + if (result == null) { + Log.w(TAG, "Failed to parse locale " + localeString + ", returning en_US instead"); + result = Locale.US; } - if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + locale); + if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + result); - return locale; + return result; } + /** * True if a given TTS engine uses the default phone locale as a default locale. Attempts to - * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the - * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If - * both these values are empty, this methods returns true. + * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}. If + * its value is empty, this methods returns true. * * @param engineName the engine to return the locale for. */ public boolean isLocaleSetToDefaultForEngine(String engineName) { - return (TextUtils.isEmpty(parseEnginePrefFromList( + return TextUtils.isEmpty(parseEnginePrefFromList( getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE), - engineName)) && - TextUtils.isEmpty( - Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.TTS_DEFAULT_LANG))); + engineName)); } - /** - * Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}. - * Varies from {@link String#split} in that it will always return an array - * of length 3 with non null values. + * Parses a locale encoded as a string, and tries its best to return a valid {@link Locale} + * object, even if the input string is encoded using the old-style 3 character format e.g. + * "deu-deu". At the end, we test if the resulting locale can return ISO3 language and + * country codes ({@link Locale#getISO3Language()} and {@link Locale#getISO3Country()}), + * if it fails to do so, we return null. */ - public static String[] parseLocalePref(String pref) { - String[] returnVal = new String[] { "", "", ""}; - if (!TextUtils.isEmpty(pref)) { - String[] split = pref.split(LOCALE_DELIMITER); - System.arraycopy(split, 0, returnVal, 0, split.length); - } - - if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] + - "," + returnVal[2] +")"); - - return returnVal; - } - - /** - * @return the old style locale string constructed from - * {@link Settings.Secure#TTS_DEFAULT_LANG}, - * {@link Settings.Secure#TTS_DEFAULT_COUNTRY} and - * {@link Settings.Secure#TTS_DEFAULT_VARIANT}. If no such locale is set, - * then return the default phone locale. - */ - private String getV1Locale() { - final ContentResolver cr = mContext.getContentResolver(); - - final String lang = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_LANG); - final String country = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_COUNTRY); - final String variant = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_VARIANT); + public Locale parseLocaleString(String localeString) { + String language = "", country = "", variant = ""; + if (!TextUtils.isEmpty(localeString)) { + String[] split = localeString.split( + "[" + LOCALE_DELIMITER_OLD + LOCALE_DELIMITER_NEW + "]"); + language = split[0].toLowerCase(); + if (split.length == 0) { + Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Only" + + " separators"); + return null; + } + if (split.length > 3) { + Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Too" + + " many separators"); + return null; + } + if (split.length >= 2) { + country = split[1].toUpperCase(); + } + if (split.length >= 3) { + variant = split[2]; + } - if (TextUtils.isEmpty(lang)) { - return getDefaultLocale(); } - String v1Locale = lang; - if (!TextUtils.isEmpty(country)) { - v1Locale += LOCALE_DELIMITER + country; - } else { - return v1Locale; + String normalizedLanguage = sNormalizeLanguage.get(language); + if (normalizedLanguage != null) { + language = normalizedLanguage; } - if (!TextUtils.isEmpty(variant)) { - v1Locale += LOCALE_DELIMITER + variant; + String normalizedCountry= sNormalizeCountry.get(country); + if (normalizedCountry != null) { + country = normalizedCountry; } - return v1Locale; + if (DBG) Log.d(TAG, "parseLocalePref(" + language + "," + country + + "," + variant +")"); + + Locale result = new Locale(language, country, variant); + try { + result.getISO3Language(); + result.getISO3Country(); + return result; + } catch(MissingResourceException e) { + Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object."); + return null; + } } /** - * Return the default device locale in form of 3 letter codes delimited by - * {@link #LOCALE_DELIMITER}: + * Return the old-style string form of the locale. It consists of 3 letter codes: * <ul> - * <li> "ISO 639-2/T language code" if locale have no country entry</li> - * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code " - * if locale have no variant entry</li> - * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code - * {@link #LOCALE_DELIMITER} variant" if locale have variant entry</li> + * <li>"ISO 639-2/T language code" if the locale has no country entry</li> + * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code" + * if the locale has no variant entry</li> + * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country + * code{@link #LOCALE_DELIMITER}variant" if the locale has a variant entry</li> * </ul> + * If we fail to generate those codes using {@link Locale#getISO3Country()} and + * {@link Locale#getISO3Language()}, then we return new String[]{"eng","USA",""}; */ - public String getDefaultLocale() { - final Locale locale = Locale.getDefault(); - + static public String[] toOldLocaleStringFormat(Locale locale) { + String[] ret = new String[]{"","",""}; try { // Note that the default locale might have an empty variant // or language, and we take care that the construction is // the same as {@link #getV1Locale} i.e no trailing delimiters // or spaces. - String defaultLocale = locale.getISO3Language(); - if (TextUtils.isEmpty(defaultLocale)) { - Log.w(TAG, "Default locale is empty."); - return ""; - } + ret[0] = locale.getISO3Language(); + ret[1] = locale.getISO3Country(); + ret[2] = locale.getVariant(); - if (!TextUtils.isEmpty(locale.getISO3Country())) { - defaultLocale += LOCALE_DELIMITER + locale.getISO3Country(); - } else { - // Do not allow locales of the form lang--variant with - // an empty country. - return defaultLocale; - } - if (!TextUtils.isEmpty(locale.getVariant())) { - defaultLocale += LOCALE_DELIMITER + locale.getVariant(); - } - - return defaultLocale; + return ret; } catch (MissingResourceException e) { // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US). - return "eng-usa"; + return new String[]{"eng","USA",""}; } } @@ -443,16 +482,21 @@ public class TtsEngines { return null; } - public synchronized void updateLocalePrefForEngine(String name, String newLocale) { + /** + * Serialize the locale to a string and store it as a default locale for the given engine. If + * the passed locale is null, an empty string will be serialized; that empty string, when + * read back, will evaluate to {@link Locale#getDefault()}. + */ + public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) { final String prefList = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE); if (DBG) { - Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale + + Log.d(TAG, "updateLocalePrefForEngine(" + engineName + ", " + newLocale + "), originally: " + prefList); } final String newPrefList = updateValueInCommaSeparatedList(prefList, - name, newLocale); + engineName, (newLocale != null) ? newLocale.toString() : ""); if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString()); diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java new file mode 100644 index 0000000..0a29283 --- /dev/null +++ b/core/java/android/speech/tts/Utterance.java @@ -0,0 +1,595 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class acts as a builder for {@link Markup} instances. + * <p> + * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and + * {@link Utterance.TtsText}). + * <p>Each semiotic class can be supplied with morphosyntactic features + * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this + * information during synthesis. + * Examples where morphosyntactic features matter: + * <ul> + * <li>In French, the number one is verbalized differently based on the gender of the noun + * it modifies. "un homme" (one man) versus "une femme" (one woman). + * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be + * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You + * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative + * form ("einen") instead of the nominative form "ein". + * </p> + * <p> + * Utterance usage example: + * Markup m1 = new Utterance().append("The Eiffel Tower is") + * .append(new TtsCardinal(324)) + * .append("meters tall."); + * Markup m2 = new Utterance().append("Sie haben") + * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE) + * .append("Tag frei."); + * </p> + */ +public class Utterance { + + /*** + * Toplevel type of markup representation. + */ + public static final String TYPE_UTTERANCE = "utterance"; + /*** + * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that + * no warning will be given when the synthesizer does not support Markup. This is used when + * the user only provides a string to the API instead of a markup. + */ + public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback"; + + // Gender. + public final static int GENDER_UNKNOWN = 0; + public final static int GENDER_NEUTRAL = 1; + public final static int GENDER_MALE = 2; + public final static int GENDER_FEMALE = 3; + + // Animacy. + public final static int ANIMACY_UNKNOWN = 0; + public final static int ANIMACY_ANIMATE = 1; + public final static int ANIMACY_INANIMATE = 2; + + // Multiplicity. + public final static int MULTIPLICITY_UNKNOWN = 0; + public final static int MULTIPLICITY_SINGLE = 1; + public final static int MULTIPLICITY_DUAL = 2; + public final static int MULTIPLICITY_PLURAL = 3; + + // Case. + public final static int CASE_UNKNOWN = 0; + public final static int CASE_NOMINATIVE = 1; + public final static int CASE_ACCUSATIVE = 2; + public final static int CASE_DATIVE = 3; + public final static int CASE_ABLATIVE = 4; + public final static int CASE_GENITIVE = 5; + public final static int CASE_VOCATIVE = 6; + public final static int CASE_LOCATIVE = 7; + public final static int CASE_INSTRUMENTAL = 8; + + private List<AbstractTts<? extends AbstractTts<?>>> says = + new ArrayList<AbstractTts<? extends AbstractTts<?>>>(); + Boolean mNoWarningOnFallback = null; + + /** + * Objects deriving from this class can be appended to a Utterance. This class uses generics + * so method from this class can return instances of its child classes, resulting in a better + * API (CRTP pattern). + */ + public static abstract class AbstractTts<C extends AbstractTts<C>> { + + protected Markup mMarkup = new Markup(); + + /** + * Empty constructor. + */ + protected AbstractTts() { + } + + /** + * Construct with Markup. + * @param markup + */ + protected AbstractTts(Markup markup) { + mMarkup = markup; + } + + /** + * Returns the type of this class, e.g. "cardinal" or "measure". + * @return The type. + */ + public String getType() { + return mMarkup.getType(); + } + + /** + * A fallback plain text can be provided, in case the engine does not support this class + * type, or even Markup altogether. + * @param plainText A string with the plain text. + * @return This instance. + */ + @SuppressWarnings("unchecked") + public C setPlainText(String plainText) { + mMarkup.setPlainText(plainText); + return (C) this; + } + + /** + * Returns the plain text (fallback) string. + * @return Plain text string or null if not set. + */ + public String getPlainText() { + return mMarkup.getPlainText(); + } + + /** + * Populates the plainText if not set and builds a Markup instance. + * @return The Markup object describing this instance. + */ + public Markup getMarkup() { + return new Markup(mMarkup); + } + + @SuppressWarnings("unchecked") + protected C setParameter(String key, String value) { + mMarkup.setParameter(key, value); + return (C) this; + } + + protected String getParameter(String key) { + return mMarkup.getParameter(key); + } + + @SuppressWarnings("unchecked") + protected C removeParameter(String key) { + mMarkup.removeParameter(key); + return (C) this; + } + + /** + * Returns a string representation of this instance, can be deserialized to an equal + * Utterance instance. + */ + public String toString() { + return mMarkup.toString(); + } + + /** + * Returns a generated plain text alternative for this instance if this instance isn't + * better representated by the list of it's children. + * @return Best effort plain text representation of this instance, can be null. + */ + public String generatePlainText() { + return null; + } + } + + public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>> + extends AbstractTts<C> { + // Keys. + private static final String KEY_GENDER = "gender"; + private static final String KEY_ANIMACY = "animacy"; + private static final String KEY_MULTIPLICITY = "multiplicity"; + private static final String KEY_CASE = "case"; + + protected AbstractTtsSemioticClass() { + super(); + } + + protected AbstractTtsSemioticClass(Markup markup) { + super(markup); + } + + @SuppressWarnings("unchecked") + public C setGender(int gender) { + if (gender < 0 || gender > 3) { + throw new IllegalArgumentException("Only four types of gender can be set: " + + "unknown, neutral, maculine and female."); + } + if (gender != GENDER_UNKNOWN) { + setParameter(KEY_GENDER, String.valueOf(gender)); + } else { + setParameter(KEY_GENDER, null); + } + return (C) this; + } + + public int getGender() { + String gender = mMarkup.getParameter(KEY_GENDER); + return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setAnimacy(int animacy) { + if (animacy < 0 || animacy > 2) { + throw new IllegalArgumentException( + "Only two types of animacy can be set: unknown, animate and inanimate"); + } + if (animacy != ANIMACY_UNKNOWN) { + setParameter(KEY_ANIMACY, String.valueOf(animacy)); + } else { + setParameter(KEY_ANIMACY, null); + } + return (C) this; + } + + public int getAnimacy() { + String animacy = getParameter(KEY_ANIMACY); + return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setMultiplicity(int multiplicity) { + if (multiplicity < 0 || multiplicity > 3) { + throw new IllegalArgumentException( + "Only four types of multiplicity can be set: unknown, single, dual and " + + "plural."); + } + if (multiplicity != MULTIPLICITY_UNKNOWN) { + setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity)); + } else { + setParameter(KEY_MULTIPLICITY, null); + } + return (C) this; + } + + public int getMultiplicity() { + String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY); + return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setCase(int grammaticalCase) { + if (grammaticalCase < 0 || grammaticalCase > 8) { + throw new IllegalArgumentException( + "Only nine types of grammatical case can be set."); + } + if (grammaticalCase != CASE_UNKNOWN) { + setParameter(KEY_CASE, String.valueOf(grammaticalCase)); + } else { + setParameter(KEY_CASE, null); + } + return (C) this; + } + + public int getCase() { + String grammaticalCase = mMarkup.getParameter(KEY_CASE); + return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN; + } + } + + /** + * Class that contains regular text, synthesis engine pronounces it using its regular pipeline. + * Parameters: + * <ul> + * <li>Text: the text to synthesize</li> + * </ul> + */ + public static class TtsText extends AbstractTtsSemioticClass<TtsText> { + + // The type of this node. + protected static final String TYPE_TEXT = "text"; + // The text parameter stores the text to be synthesized. + private static final String KEY_TEXT = "text"; + + /** + * Default constructor. + */ + public TtsText() { + mMarkup.setType(TYPE_TEXT); + } + + /** + * Constructor that sets the text to be synthesized. + * @param text The text to be synthesized. + */ + public TtsText(String text) { + this(); + setText(text); + } + + /** + * Constructs a TtsText with the values of the Markup, does not check if the given Markup is + * of the right type. + */ + private TtsText(Markup markup) { + super(markup); + } + + /** + * Sets the text to be synthesized. + * @return This instance. + */ + public TtsText setText(String text) { + setParameter(KEY_TEXT, text); + return this; + } + + /** + * Returns the text to be synthesized. + * @return This instance. + */ + public String getText() { + return getParameter(KEY_TEXT); + } + + /** + * Generates a best effort plain text, in this case simply the text. + */ + @Override + public String generatePlainText() { + return getText(); + } + } + + /** + * Contains a cardinal. + * Parameters: + * <ul> + * <li>integer: the integer to synthesize</li> + * </ul> + */ + public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> { + + // The type of this node. + protected static final String TYPE_CARDINAL = "cardinal"; + // The parameter integer stores the integer to synthesize. + private static final String KEY_INTEGER = "integer"; + + /** + * Default constructor. + */ + public TtsCardinal() { + mMarkup.setType(TYPE_CARDINAL); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(int integer) { + this(); + setInteger(integer); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(String integer) { + this(); + setInteger(integer); + } + + /** + * Constructs a TtsText with the values of the Markup. + * Does not check if the given Markup is of the right type. + */ + private TtsCardinal(Markup markup) { + super(markup); + } + + /** + * Sets the integer. + * @return This instance. + */ + public TtsCardinal setInteger(int integer) { + return setInteger(String.valueOf(integer)); + } + + /** + * Sets the integer. + * @param integer A non-empty string of digits with an optional '-' in front. + * @return This instance. + */ + public TtsCardinal setInteger(String integer) { + if (!integer.matches("-?\\d+")) { + throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\""); + } + setParameter(KEY_INTEGER, integer); + return this; + } + + /** + * Returns the integer parameter. + */ + public String getInteger() { + return getParameter(KEY_INTEGER); + } + + /** + * Generates a best effort plain text, in this case simply the integer. + */ + @Override + public String generatePlainText() { + return getInteger(); + } + } + + /** + * Default constructor. + */ + public Utterance() {} + + /** + * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the + * this same method on its children. + */ + private String constructPlainText(Markup m) { + StringBuilder plainText = new StringBuilder(); + if (m.getPlainText() != null) { + plainText.append(m.getPlainText()); + } else { + for (Markup nestedMarkup : m.getNestedMarkups()) { + String nestedPlainText = constructPlainText(nestedMarkup); + if (!nestedPlainText.isEmpty()) { + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(nestedPlainText); + } + } + } + return plainText.toString(); + } + + /** + * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the + * user has not provided one already. + * @return A Markup instance representing this utterance. + */ + public Markup createMarkup() { + Markup markup = new Markup(TYPE_UTTERANCE); + StringBuilder plainText = new StringBuilder(); + for (AbstractTts<? extends AbstractTts<?>> say : says) { + // Get a copy of this markup, and generate a plaintext for it if is not set. + Markup sayMarkup = say.getMarkup(); + if (sayMarkup.getPlainText() == null) { + sayMarkup.setPlainText(say.generatePlainText()); + } + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(constructPlainText(sayMarkup)); + markup.addNestedMarkup(sayMarkup); + } + if (mNoWarningOnFallback != null) { + markup.setParameter(KEY_NO_WARNING_ON_FALLBACK, + mNoWarningOnFallback ? "true" : "false"); + } + markup.setPlainText(plainText.toString()); + return markup; + } + + /** + * Appends an element to this Utterance instance. + * @return this instance + */ + public Utterance append(AbstractTts<? extends AbstractTts<?>> say) { + says.add(say); + return this; + } + + private Utterance append(Markup markup) { + if (markup.getType().equals(TtsText.TYPE_TEXT)) { + append(new TtsText(markup)); + } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) { + append(new TtsCardinal(markup)); + } else { + // Unknown node, a class we don't know about. + if (markup.getPlainText() != null) { + append(new TtsText(markup.getPlainText())); + } else { + // No plainText specified; add its children + // seperately. In case of a new prosody node, + // we would still verbalize it correctly. + for (Markup nested : markup.getNestedMarkups()) { + append(nested); + } + } + } + return this; + } + + /** + * Returns a string representation of this Utterance instance. Can be deserialized back to an + * Utterance instance with utteranceFromString(). Can be used to store utterances to be used + * at a later time. + */ + public String toString() { + String out = "type: \"" + TYPE_UTTERANCE + "\""; + if (mNoWarningOnFallback != null) { + out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\""; + } + for (AbstractTts<? extends AbstractTts<?>> say : says) { + out += " markup { " + say.getMarkup().toString() + " }"; + } + return out; + } + + /** + * Returns an Utterance instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Utterance instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + static public Utterance utteranceFromString(String string) throws IllegalArgumentException { + Utterance utterance = new Utterance(); + Markup markup = Markup.markupFromString(string); + if (!markup.getType().equals(TYPE_UTTERANCE)) { + throw new IllegalArgumentException("Top level markup should be of type \"" + + TYPE_UTTERANCE + "\", but was of type \"" + + markup.getType() + "\".") ; + } + for (Markup nestedMarkup : markup.getNestedMarkups()) { + utterance.append(nestedMarkup); + } + return utterance; + } + + /** + * Appends a new TtsText with the given text. + * @param text The text to synthesize. + * @return This instance. + */ + public Utterance append(String text) { + return append(new TtsText(text)); + } + + /** + * Appends a TtsCardinal representing the given number. + * @param integer The integer to synthesize. + * @return this + */ + public Utterance append(int integer) { + return append(new TtsCardinal(integer)); + } + + /** + * Returns the n'th element in this Utterance. + * @param i The index. + * @return The n'th element in this Utterance. + * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size() + */ + public AbstractTts<? extends AbstractTts<?>> get(int i) { + return says.get(i); + } + + /** + * Returns the number of elements in this Utterance. + * @return The number of elements in this Utterance. + */ + public int size() { + return says.size(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Utterance) ) return false; + Utterance utt = (Utterance) o; + + if (says.size() != utt.says.size()) { + return false; + } + + for (int i = 0; i < says.size(); i++) { + if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) { + return false; + } + } + return true; + } + + /** + * Can be set to true or false, true indicating that the user provided only a string to the API, + * at which the system will not issue a warning if the synthesizer falls back onto the plain + * text when the synthesizer does not support Markup. + */ + public Utterance setNoWarningOnFallback(boolean noWarning) { + mNoWarningOnFallback = noWarning; + return this; + } +} diff --git a/core/java/android/speech/tts/VoiceInfo.java b/core/java/android/speech/tts/VoiceInfo.java index 57cb615..71629dc 100644 --- a/core/java/android/speech/tts/VoiceInfo.java +++ b/core/java/android/speech/tts/VoiceInfo.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.os.Bundle; @@ -16,7 +31,6 @@ import java.util.Locale; * 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. - * @hide */ public final class VoiceInfo implements Parcelable { /** Very low, but still intelligible quality of speech synthesis */ diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java index d426d12..8674c66 100644 --- a/core/java/android/text/GraphicsOperations.java +++ b/core/java/android/text/GraphicsOperations.java @@ -38,7 +38,7 @@ extends CharSequence * {@hide} */ void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, - float x, float y, int flags, Paint p); + float x, float y, boolean isRtl, Paint p); /** * Just like {@link Paint#measureText}. @@ -55,12 +55,12 @@ extends CharSequence * @hide */ float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, - int flags, float[] advances, int advancesIndex, Paint paint); + boolean isRtl, float[] advances, int advancesIndex, Paint paint); /** * Just like {@link Paint#getTextRunCursor}. * @hide */ - int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, + int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p); } diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index f8e3c83..2415b11 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -159,18 +159,17 @@ class MeasuredText { mPos = p + len; if (mEasy) { - int flags = mDir == Layout.DIR_LEFT_TO_RIGHT - ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL; - return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p); + boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; + return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p); } float totalAdvance = 0; int level = mLevels[p]; for (int q = p, i = p + 1, e = p + len;; ++i) { if (i == e || mLevels[i] != level) { - int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL; + boolean isRtl = (level & 0x1) != 0; totalAdvance += - paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q); + paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); if (i == e) { break; } diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 1d9aa05..6b984d6 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -503,7 +503,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Span watchers need to be called after text watchers, which may update the layout sendToSpanWatchers(start, end, newLen - origLen); - return this; + return this; } private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) { @@ -745,7 +745,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } - return 0; + return 0; } /** @@ -1117,20 +1117,20 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * {@hide} */ public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, - float x, float y, int flags, Paint p) { + float x, float y, boolean isRtl, Paint p) { checkRange("drawTextRun", start, end); int contextLen = contextEnd - contextStart; int len = end - start; if (contextEnd <= mGapStart) { - c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p); + c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p); } else if (contextStart >= mGapStart) { c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, - contextLen, x, y, flags, p); + contextLen, x, y, isRtl, p); } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); - c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p); + c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p); TextUtils.recycle(buf); } } @@ -1187,7 +1187,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * Don't call this yourself -- exists for Paint to use internally. * {@hide} */ - public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, + public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesPos, Paint p) { float ret; @@ -1197,15 +1197,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, - flags, advances, advancesPos); + isRtl, advances, advancesPos); } else if (start >= mGapStart) { ret = p.getTextRunAdvances(mText, start + mGapLength, len, - contextStart + mGapLength, contextLen, flags, advances, advancesPos); + contextStart + mGapLength, contextLen, isRtl, advances, advancesPos); } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); ret = p.getTextRunAdvances(buf, start - contextStart, len, - 0, contextLen, flags, advances, advancesPos); + 0, contextLen, isRtl, advances, advancesPos); TextUtils.recycle(buf); } @@ -1228,7 +1228,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * * @param contextStart the start index of the context * @param contextEnd the (non-inclusive) end index of the context - * @param flags either DIRECTION_RTL or DIRECTION_LTR + * @param dir either DIRECTION_RTL or DIRECTION_LTR * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, @@ -1238,7 +1238,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * @deprecated This is an internal method, refrain from using it in your code */ @Deprecated - public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, + public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p) { int ret; @@ -1246,15 +1246,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable int contextLen = contextEnd - contextStart; if (contextEnd <= mGapStart) { ret = p.getTextRunCursor(mText, contextStart, contextLen, - flags, offset, cursorOpt); + dir, offset, cursorOpt); } else if (contextStart >= mGapStart) { ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, - flags, offset + mGapLength, cursorOpt) - mGapLength; + dir, offset + mGapLength, cursorOpt) - mGapLength; } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); ret = p.getTextRunCursor(buf, 0, contextLen, - flags, offset - contextStart, cursorOpt) + contextStart; + dir, offset - contextStart, cursorOpt) + contextStart; TextUtils.recycle(buf); } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index d892f19..c19cf32 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -664,14 +664,14 @@ class TextLine { } } - int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; + int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE; if (mCharsValid) { return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, - flags, offset, cursorOpt); + dir, offset, cursorOpt); } else { return wp.getTextRunCursor(mText, mStart + spanStart, - mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart; + mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart; } } @@ -738,15 +738,14 @@ class TextLine { int contextLen = contextEnd - contextStart; if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) { - int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; if (mCharsValid) { ret = wp.getTextRunAdvances(mChars, start, runLen, - contextStart, contextLen, flags, null, 0); + contextStart, contextLen, runIsRtl, null, 0); } else { int delta = mStart; ret = wp.getTextRunAdvances(mText, delta + start, delta + end, delta + contextStart, delta + contextEnd, - flags, null, 0); + runIsRtl, null, 0); } } @@ -977,16 +976,15 @@ class TextLine { private void drawTextRun(Canvas c, TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { - int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR; if (mCharsValid) { int count = end - start; int contextCount = contextEnd - contextStart; c.drawTextRun(mChars, start, count, contextStart, contextCount, - x, y, flags, wp); + x, y, runIsRtl, wp); } else { int delta = mStart; c.drawTextRun(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, x, y, flags, wp); + delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp); } } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index f06ae71..48122d6 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -48,10 +48,11 @@ import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; import android.util.Printer; - import android.view.View; + import com.android.internal.R; import com.android.internal.util.ArrayUtils; + import libcore.icu.ICU; import java.lang.reflect.Array; @@ -229,7 +230,12 @@ public class TextUtils { public static boolean regionMatches(CharSequence one, int toffset, CharSequence two, int ooffset, int len) { - char[] temp = obtain(2 * len); + int tempLen = 2 * len; + if (tempLen < len) { + // Integer overflow; len is unreasonably large + throw new IndexOutOfBoundsException(); + } + char[] temp = obtain(tempLen); getChars(one, toffset, toffset + len, temp, 0); getChars(two, ooffset, ooffset + len, temp, len); diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index d1f35dd..9fec9a1 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -195,7 +195,7 @@ public class DateFormat { * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}. */ public static String getBestDateTimePattern(Locale locale, String skeleton) { - return ICU.getBestDateTimePattern(skeleton, locale.toString()); + return ICU.getBestDateTimePattern(skeleton, locale); } /** diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index deb138d..c1341e1 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -465,32 +465,39 @@ public class Linkify { String address; int base = 0; - while ((address = WebView.findAddress(string)) != null) { - int start = string.indexOf(address); + try { + while ((address = WebView.findAddress(string)) != null) { + int start = string.indexOf(address); - if (start < 0) { - break; - } + if (start < 0) { + break; + } - LinkSpec spec = new LinkSpec(); - int length = address.length(); - int end = start + length; - - spec.start = base + start; - spec.end = base + end; - string = string.substring(end); - base += end; - - String encodedAddress = null; - - try { - encodedAddress = URLEncoder.encode(address,"UTF-8"); - } catch (UnsupportedEncodingException e) { - continue; - } + LinkSpec spec = new LinkSpec(); + int length = address.length(); + int end = start + length; - spec.url = "geo:0,0?q=" + encodedAddress; - links.add(spec); + spec.start = base + start; + spec.end = base + end; + string = string.substring(end); + base += end; + + String encodedAddress = null; + + try { + encodedAddress = URLEncoder.encode(address,"UTF-8"); + } catch (UnsupportedEncodingException e) { + continue; + } + + spec.url = "geo:0,0?q=" + encodedAddress; + links.add(spec); + } + } catch (UnsupportedOperationException e) { + // findAddress may fail with an unsupported exception on platforms without a WebView. + // In this case, we will not append anything to the links variable: it would have died + // in WebView.findAddress. + return; } } diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java index fae527c..feb8efd 100644 --- a/core/java/android/transition/Explode.java +++ b/core/java/android/transition/Explode.java @@ -15,20 +15,16 @@ */ package android.transition; +import com.android.internal.R; + import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.graphics.Path; import android.graphics.Rect; import android.util.FloatMath; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; - /** * This transition tracks changes to the visibility of target views in the * start and end scenes and moves views in or out from the edges of the @@ -44,8 +40,7 @@ public class Explode extends Visibility { private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); private static final String TAG = "Explode"; - - private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds"; + private static final String PROPNAME_SCREEN_BOUNDS = "android:explode:screenBounds"; private int[] mTempLoc = new int[2]; @@ -56,8 +51,8 @@ public class Explode extends Visibility { private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; view.getLocationOnScreen(mTempLoc); - int left = mTempLoc[0] + Math.round(view.getTranslationX()); - int top = mTempLoc[1] + Math.round(view.getTranslationY()); + int left = mTempLoc[0]; + int top = mTempLoc[1]; int right = left + view.getWidth(); int bottom = top + view.getHeight(); transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom)); @@ -75,27 +70,6 @@ public class Explode extends Visibility { captureValues(transitionValues); } - private Animator createAnimation(final View view, float startX, float startY, float endX, - float endY, float terminalX, float terminalY, TimeInterpolator interpolator) { - view.setTranslationX(startX); - view.setTranslationY(startY); - if (startY == endY && startX == endX) { - return null; - } - Path path = new Path(); - path.moveTo(startX, startY); - path.lineTo(endX, endY); - ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, - View.TRANSLATION_Y, path); - pathAnimator.setInterpolator(interpolator); - OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY, - endX, endY); - pathAnimator.addListener(listener); - pathAnimator.addPauseListener(listener); - - return pathAnimator; - } - @Override public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { @@ -103,29 +77,43 @@ public class Explode extends Visibility { return null; } Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS); + float endX = view.getTranslationX(); + float endY = view.getTranslationY(); calculateOut(sceneRoot, bounds, mTempLoc); + float startX = endX + mTempLoc[0]; + float startY = endY + mTempLoc[1]; - final float endX = view.getTranslationX(); - final float startX = endX + mTempLoc[0]; - final float endY = view.getTranslationY(); - final float startY = endY + mTempLoc[1]; - - return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate); + return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top, + startX, startY, endX, endY, sDecelerate); } @Override public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS); + int viewPosX = bounds.left; + int viewPosY = bounds.top; + float startX = view.getTranslationX(); + float startY = view.getTranslationY(); + float endX = startX; + float endY = startY; + int[] interruptedPosition = (int[]) startValues.view.getTag(R.id.transitionPosition); + if (interruptedPosition != null) { + // We want to have the end position relative to the interrupted position, not + // the position it was supposed to start at. + endX += interruptedPosition[0] - bounds.left; + endY += interruptedPosition[1] - bounds.top; + bounds.offsetTo(interruptedPosition[0], interruptedPosition[1]); + } calculateOut(sceneRoot, bounds, mTempLoc); + endX += mTempLoc[0]; + endY += mTempLoc[1]; - final float startX = view.getTranslationX(); - final float endX = startX + mTempLoc[0]; - final float startY = view.getTranslationY(); - final float endY = startY + mTempLoc[1]; - - return createAnimation(view, startX, startY, endX, endY, startX, startY, - sAccelerate); + return TranslationAnimationCreator.createAnimation(view, startValues, + viewPosX, viewPosY, startX, startY, endX, endY, sAccelerate); } private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) { @@ -153,8 +141,8 @@ public class Explode extends Visibility { if (xVector == 0 && yVector == 0) { // Random direction when View is centered on focal View. - xVector = (float)(Math.random() * 2) - 1; - yVector = (float)(Math.random() * 2) - 1; + xVector = (float) (Math.random() * 2) - 1; + yVector = (float) (Math.random() * 2) - 1; } float vectorSize = calculateDistance(xVector, yVector); xVector /= vectorSize; @@ -176,53 +164,4 @@ public class Explode extends Visibility { private static float calculateDistance(float x, float y) { return FloatMath.sqrt((x * x) + (y * y)); } - - private static class OutAnimatorListener extends AnimatorListenerAdapter { - private final View mView; - private boolean mCanceled = false; - private float mPausedX; - private float mPausedY; - private final float mTerminalX; - private final float mTerminalY; - private final float mEndX; - private final float mEndY; - - public OutAnimatorListener(View view, float terminalX, float terminalY, - float endX, float endY) { - mView = view; - mTerminalX = terminalX; - mTerminalY = terminalY; - mEndX = endX; - mEndY = endY; - } - - @Override - public void onAnimationCancel(Animator animator) { - mView.setTranslationX(mTerminalX); - mView.setTranslationY(mTerminalY); - mCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animator) { - if (!mCanceled) { - mView.setTranslationX(mTerminalX); - mView.setTranslationY(mTerminalY); - } - } - - @Override - public void onAnimationPause(Animator animator) { - mPausedX = mView.getTranslationX(); - mPausedY = mView.getTranslationY(); - mView.setTranslationY(mEndX); - mView.setTranslationY(mEndY); - } - - @Override - public void onAnimationResume(Animator animator) { - mView.setTranslationX(mPausedX); - mView.setTranslationY(mPausedY); - } - } } diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java index 8269258..0d2e487 100644 --- a/core/java/android/transition/Slide.java +++ b/core/java/android/transition/Slide.java @@ -16,14 +16,7 @@ package android.transition; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.graphics.Rect; -import android.util.Log; -import android.util.Property; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -41,71 +34,60 @@ import android.view.animation.DecelerateInterpolator; */ public class Slide extends Visibility { private static final String TAG = "Slide"; - private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); - + private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition"; private CalculateSlide mSlideCalculator = sCalculateBottom; private interface CalculateSlide { - /** Returns the translation value for view when it out of the scene */ - float getGone(ViewGroup sceneRoot, View view); - /** Returns the translation value for view when it is in the scene */ - float getHere(View view); + /** Returns the translation value for view when it goes out of the scene */ + float getGoneX(ViewGroup sceneRoot, View view); - /** Returns the property to animate translation */ - Property<View, Float> getProperty(); + /** Returns the translation value for view when it goes out of the scene */ + float getGoneY(ViewGroup sceneRoot, View view); } private static abstract class CalculateSlideHorizontal implements CalculateSlide { - @Override - public float getHere(View view) { - return view.getTranslationX(); - } @Override - public Property<View, Float> getProperty() { - return View.TRANSLATION_X; + public float getGoneY(ViewGroup sceneRoot, View view) { + return view.getTranslationY(); } } private static abstract class CalculateSlideVertical implements CalculateSlide { - @Override - public float getHere(View view) { - return view.getTranslationY(); - } @Override - public Property<View, Float> getProperty() { - return View.TRANSLATION_Y; + public float getGoneX(ViewGroup sceneRoot, View view) { + return view.getTranslationX(); } } private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { @Override - public float getGone(ViewGroup sceneRoot, View view) { + public float getGoneX(ViewGroup sceneRoot, View view) { return view.getTranslationX() - sceneRoot.getWidth(); } }; private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { @Override - public float getGone(ViewGroup sceneRoot, View view) { + public float getGoneY(ViewGroup sceneRoot, View view) { return view.getTranslationY() - sceneRoot.getHeight(); } }; private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { @Override - public float getGone(ViewGroup sceneRoot, View view) { + public float getGoneX(ViewGroup sceneRoot, View view) { return view.getTranslationX() + sceneRoot.getWidth(); } }; private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { @Override - public float getGone(ViewGroup sceneRoot, View view) { + public float getGoneY(ViewGroup sceneRoot, View view) { return view.getTranslationY() + sceneRoot.getHeight(); } }; @@ -125,8 +107,28 @@ public class Slide extends Visibility { setSlideEdge(slideEdge); } + private void captureValues(TransitionValues transitionValues) { + View view = transitionValues.view; + int[] position = new int[2]; + view.getLocationOnScreen(position); + transitionValues.values.put(PROPNAME_SCREEN_POSITION, position); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + /** * Change the edge that Views appear and disappear from. + * * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}. @@ -153,77 +155,35 @@ public class Slide extends Visibility { setPropagation(propagation); } - private Animator createAnimation(final View view, Property<View, Float> property, - float start, float end, float terminalValue, TimeInterpolator interpolator) { - view.setTranslationY(start); - if (start == end) { - return null; - } - final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end); - - SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end); - anim.addListener(listener); - anim.addPauseListener(listener); - anim.setInterpolator(interpolator); - return anim; - } - @Override public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (endValues == null) { return null; } - float end = mSlideCalculator.getHere(view); - float start = mSlideCalculator.getGone(sceneRoot, view); - return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate); + int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); + float endX = view.getTranslationX(); + float endY = view.getTranslationY(); + float startX = mSlideCalculator.getGoneX(sceneRoot, view); + float startY = mSlideCalculator.getGoneY(sceneRoot, view); + return TranslationAnimationCreator + .createAnimation(view, endValues, position[0], position[1], + startX, startY, endX, endY, sDecelerate); } @Override public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { - float start = mSlideCalculator.getHere(view); - float end = mSlideCalculator.getGone(sceneRoot, view); - - return createAnimation(view, mSlideCalculator.getProperty(), start, end, start, - sAccelerate); - } - - private static class SlideAnimatorListener extends AnimatorListenerAdapter { - private boolean mCanceled = false; - private float mPausedY; - private final View mView; - private final float mEndY; - private final float mTerminalY; - - public SlideAnimatorListener(View view, float terminalY, float endY) { - mView = view; - mTerminalY = terminalY; - mEndY = endY; - } - - @Override - public void onAnimationCancel(Animator animator) { - mView.setTranslationY(mTerminalY); - mCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animator) { - if (!mCanceled) { - mView.setTranslationY(mTerminalY); - } - } - - @Override - public void onAnimationPause(Animator animator) { - mPausedY = mView.getTranslationY(); - mView.setTranslationY(mEndY); - } - - @Override - public void onAnimationResume(Animator animator) { - mView.setTranslationY(mPausedY); + if (startValues == null) { + return null; } + int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); + float startX = view.getTranslationX(); + float startY = view.getTranslationY(); + float endX = mSlideCalculator.getGoneX(sceneRoot, view); + float endY = mSlideCalculator.getGoneY(sceneRoot, view); + return TranslationAnimationCreator + .createAnimation(view, startValues, position[0], position[1], + startX, startY, endX, endY, sAccelerate); } } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index e9c2bba..0017eb1 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -89,8 +89,8 @@ import java.util.List; * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each * of which lists a specific <code>targetId</code>, <code>targetClass</code>, - * <code>targetViewName</code>, <code>excludeId</code>, <code>excludeClass</code>, or - * <code>excludeViewName</code>, which this transition acts upon. + * <code>targetName</code>, <code>excludeId</code>, <code>excludeClass</code>, or + * <code>excludeName</code>, which this transition acts upon. * Use of targets is optional, but can be used to either limit the time spent checking * attributes on unchanging views, or limiting the types of animations run on specific views. * In this case, we know that only the <code>grayscaleContainer</code> will be @@ -115,9 +115,9 @@ public abstract class Transition implements Cloneable { /** * With {@link #setMatchOrder(int...)}, chooses to match by - * {@link android.view.View#getViewName()}. Null names will not be matched. + * {@link android.view.View#getTransitionName()}. Null names will not be matched. */ - public static final int MATCH_VIEW_NAME = 0x2; + public static final int MATCH_NAME = 0x2; /** * With {@link #setMatchOrder(int...)}, chooses to match by @@ -135,7 +135,7 @@ public abstract class Transition implements Cloneable { private static final int MATCH_LAST = MATCH_ITEM_ID; private static final int[] DEFAULT_MATCH_ORDER = { - MATCH_VIEW_NAME, + MATCH_NAME, MATCH_INSTANCE, MATCH_ID, MATCH_ITEM_ID, @@ -206,6 +206,10 @@ public abstract class Transition implements Cloneable { // like CircularPropagation EpicenterCallback mEpicenterCallback; + // For Fragment shared element transitions, linking views explicitly by mismatching + // transitionNames. + ArrayMap<String, String> mNameOverrides; + /** * Constructs a Transition object with no target objects. A transition with * no targets defaults to running on all target objects in the scene hierarchy @@ -374,17 +378,17 @@ public abstract class Transition implements Cloneable { /** * Sets the order in which Transition matches View start and end values. * <p> - * The default behavior is to match first by {@link android.view.View#getViewName()}, + * The default behavior is to match first by {@link android.view.View#getTransitionName()}, * then by View instance, then by {@link android.view.View#getId()} and finally * by its item ID if it is in a direct child of ListView. The caller can * choose to have only some or all of the values of {@link #MATCH_INSTANCE}, - * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only + * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only * the match algorithms supplied will be used to determine whether Views are the * the same in both the start and end Scene. Views that do not match will be considered * as entering or leaving the Scene. * </p> * @param matches A list of zero or more of {@link #MATCH_INSTANCE}, - * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. + * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. * If none are provided, then the default match order will be set. */ public void setMatchOrder(int... matches) { @@ -496,9 +500,9 @@ public abstract class Transition implements Cloneable { } /** - * Match start/end values by Adapter viewName. Adds matched values to startValuesList + * Match start/end values by Adapter transitionName. Adds matched values to startValuesList * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using - * startNames and endNames as a guide for which Views have unique viewNames. + * startNames and endNames as a guide for which Views have unique transitionNames. */ private void matchNames(ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList, @@ -559,7 +563,7 @@ public abstract class Transition implements Cloneable { case MATCH_INSTANCE: matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd); break; - case MATCH_VIEW_NAME: + case MATCH_NAME: matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd, startValues.nameValues, endValues.nameValues); break; @@ -603,76 +607,76 @@ public abstract class Transition implements Cloneable { for (int i = 0; i < startValuesList.size(); ++i) { TransitionValues start = startValuesList.get(i); TransitionValues end = endValuesList.get(i); - // Only bother trying to animate with values that differ between start/end - if (start != null || end != null) { - if (start == null || !start.equals(end)) { - if (DBG) { - View view = (end != null) ? end.view : start.view; - Log.d(LOG_TAG, " differing start/end values for view " + - view); - if (start == null || end == null) { - Log.d(LOG_TAG, " " + ((start == null) ? - "start null, end non-null" : "start non-null, end null")); - } else { - for (String key : start.values.keySet()) { - Object startValue = start.values.get(key); - Object endValue = end.values.get(key); - if (startValue != endValue && !startValue.equals(endValue)) { - Log.d(LOG_TAG, " " + key + ": start(" + startValue + - "), end(" + endValue +")"); - } + // Only bother trying to animate with valid values that differ between start/end + boolean isInvalidStart = start != null && !isValidTarget(start.view); + boolean isInvalidEnd = end != null && !isValidTarget(end.view); + boolean isChanged = start != end && (start == null || !start.equals(end)); + if (isChanged && !isInvalidStart && !isInvalidEnd) { + if (DBG) { + View view = (end != null) ? end.view : start.view; + Log.d(LOG_TAG, " differing start/end values for view " + view); + if (start == null || end == null) { + Log.d(LOG_TAG, " " + ((start == null) ? + "start null, end non-null" : "start non-null, end null")); + } else { + for (String key : start.values.keySet()) { + Object startValue = start.values.get(key); + Object endValue = end.values.get(key); + if (startValue != endValue && !startValue.equals(endValue)) { + Log.d(LOG_TAG, " " + key + ": start(" + startValue + + "), end(" + endValue + ")"); } } } - // TODO: what to do about targetIds and itemIds? - Animator animator = createAnimator(sceneRoot, start, end); - if (animator != null) { - // Save animation info for future cancellation purposes - View view = null; - TransitionValues infoValues = null; - if (end != null) { - view = end.view; - String[] properties = getTransitionProperties(); - if (view != null && properties != null && properties.length > 0) { - infoValues = new TransitionValues(); - infoValues.view = view; - TransitionValues newValues = endValues.viewValues.get(view); - if (newValues != null) { - for (int j = 0; j < properties.length; ++j) { - infoValues.values.put(properties[j], - newValues.values.get(properties[j])); - } + } + // TODO: what to do about targetIds and itemIds? + Animator animator = createAnimator(sceneRoot, start, end); + if (animator != null) { + // Save animation info for future cancellation purposes + View view = null; + TransitionValues infoValues = null; + if (end != null) { + view = end.view; + String[] properties = getTransitionProperties(); + if (view != null && properties != null && properties.length > 0) { + infoValues = new TransitionValues(); + infoValues.view = view; + TransitionValues newValues = endValues.viewValues.get(view); + if (newValues != null) { + for (int j = 0; j < properties.length; ++j) { + infoValues.values.put(properties[j], + newValues.values.get(properties[j])); } - int numExistingAnims = runningAnimators.size(); - for (int j = 0; j < numExistingAnims; ++j) { - Animator anim = runningAnimators.keyAt(j); - AnimationInfo info = runningAnimators.get(anim); - if (info.values != null && info.view == view && - ((info.name == null && getName() == null) || - info.name.equals(getName()))) { - if (info.values.equals(infoValues)) { - // Favor the old animator - animator = null; - break; - } + } + int numExistingAnims = runningAnimators.size(); + for (int j = 0; j < numExistingAnims; ++j) { + Animator anim = runningAnimators.keyAt(j); + AnimationInfo info = runningAnimators.get(anim); + if (info.values != null && info.view == view && + ((info.name == null && getName() == null) || + info.name.equals(getName()))) { + if (info.values.equals(infoValues)) { + // Favor the old animator + animator = null; + break; } } } - } else { - view = (start != null) ? start.view : null; } - if (animator != null) { - if (mPropagation != null) { - long delay = mPropagation - .getStartDelay(sceneRoot, this, start, end); - startDelays.put(mAnimators.size(), delay); - minStartDelay = Math.min(delay, minStartDelay); - } - AnimationInfo info = new AnimationInfo(view, getName(), - sceneRoot.getWindowId(), infoValues); - runningAnimators.put(animator, info); - mAnimators.add(animator); + } else { + view = (start != null) ? start.view : null; + } + if (animator != null) { + if (mPropagation != null) { + long delay = mPropagation + .getStartDelay(sceneRoot, this, start, end); + startDelays.put(mAnimators.size(), delay); + minStartDelay = Math.min(delay, minStartDelay); } + AnimationInfo info = new AnimationInfo(view, getName(), this, + sceneRoot.getWindowId(), infoValues); + runningAnimators.put(animator, info); + mAnimators.add(animator); } } } @@ -714,8 +718,8 @@ public abstract class Transition implements Cloneable { } } } - if (mTargetNameExcludes != null && target != null && target.getViewName() != null) { - if (mTargetNameExcludes.contains(target.getViewName())) { + if (mTargetNameExcludes != null && target != null && target.getTransitionName() != null) { + if (mTargetNameExcludes.contains(target.getTransitionName())) { return false; } } @@ -727,7 +731,7 @@ public abstract class Transition implements Cloneable { if (mTargetIds.contains(targetId) || mTargets.contains(target)) { return true; } - if (mTargetNames != null && mTargetNames.contains(target.getViewName())) { + if (mTargetNames != null && mTargetNames.contains(target.getTransitionName())) { return true; } if (mTargetTypes != null) { @@ -878,18 +882,18 @@ public abstract class Transition implements Cloneable { } /** - * Adds the viewName of a target view that this Transition is interested in + * Adds the transitionName of a target view that this Transition is interested in * animating. By default, there are no targetNames, and a Transition will * listen for changes on every view in the hierarchy below the sceneRoot * of the Scene being transitioned into. Setting targetNames constrains - * the Transition to only listen for, and act on, views with these viewNames. - * Views with different viewNames, or no viewName whatsoever, will be ignored. + * the Transition to only listen for, and act on, views with these transitionNames. + * Views with different transitionNames, or no transitionName whatsoever, will be ignored. * - * <p>Note that viewNames should be unique within the view hierarchy.</p> + * <p>Note that transitionNames should be unique within the view hierarchy.</p> * - * @see android.view.View#getViewName() - * @param targetName The viewName of a target view, must be non-null. - * @return The Transition to which the target viewName is added. + * @see android.view.View#getTransitionName() + * @param targetName The transitionName of a target view, must be non-null. + * @return The Transition to which the target transitionName is added. * Returning the same object makes it easier to chain calls during * construction, such as * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code> @@ -954,10 +958,10 @@ public abstract class Transition implements Cloneable { } /** - * Removes the given targetName from the list of viewNames that this Transition + * Removes the given targetName from the list of transitionNames that this Transition * is interested in animating. * - * @param targetName The viewName of a target view, must not be null. + * @param targetName The transitionName of a target view, must not be null. * @return The Transition from which the targetName is removed. * Returning the same object makes it easier to chain calls during * construction, such as @@ -999,28 +1003,28 @@ public abstract class Transition implements Cloneable { } /** - * Whether to add the given viewName to the list of target viewNames to exclude from this - * transition. The <code>exclude</code> parameter specifies whether the target + * Whether to add the given transitionName to the list of target transitionNames to exclude + * from this transition. The <code>exclude</code> parameter specifies whether the target * should be added to or removed from the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded by their - * id, their instance reference, their viewName, or by the Class of that view + * id, their instance reference, their transitionName, or by the Class of that view * (eg, {@link Spinner}).</p> * * @see #excludeTarget(View, boolean) * @see #excludeTarget(int, boolean) * @see #excludeTarget(Class, boolean) * - * @param targetViewName The name of a target to ignore when running this transition. + * @param targetName The name of a target to ignore when running this transition. * @param exclude Whether to add the target to or remove the target from the * current list of excluded targets. * @return This transition object. */ - public Transition excludeTarget(String targetViewName, boolean exclude) { - mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetViewName, exclude); + public Transition excludeTarget(String targetName, boolean exclude) { + mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude); return this; } @@ -1244,7 +1248,7 @@ public abstract class Transition implements Cloneable { /** * Returns the list of target IDs that this transition limits itself to * tracking and animating. If the list is null or empty for - * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and + * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and * {@link #getTargetTypes()} then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. @@ -1258,7 +1262,7 @@ public abstract class Transition implements Cloneable { /** * Returns the list of target views that this transition limits itself to * tracking and animating. If the list is null or empty for - * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and + * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and * {@link #getTargetTypes()} then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. @@ -1270,23 +1274,31 @@ public abstract class Transition implements Cloneable { } /** - * Returns the list of target viewNames that this transition limits itself to + * Returns the list of target transitionNames that this transition limits itself to * tracking and animating. If the list is null or empty for - * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and + * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and * {@link #getTargetTypes()} then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. * - * @return the list of target viewNames + * @return the list of target transitionNames + */ + public List<String> getTargetNames() { + return mTargetNames; + } + + /** + * To be removed before L release. + * @hide */ public List<String> getTargetViewNames() { return mTargetNames; } /** - * Returns the list of target viewNames that this transition limits itself to + * Returns the list of target transitionNames that this transition limits itself to * tracking and animating. If the list is null or empty for - * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and + * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and * {@link #getTargetTypes()} then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. @@ -1347,6 +1359,21 @@ public abstract class Transition implements Cloneable { } else { captureHierarchy(sceneRoot, start); } + if (!start && mNameOverrides != null) { + int numOverrides = mNameOverrides.size(); + ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides); + for (int i = 0; i < numOverrides; i++) { + String fromName = mNameOverrides.keyAt(i); + overriddenViews.add(mStartValues.nameValues.remove(fromName)); + } + for (int i = 0; i < numOverrides; i++) { + View view = overriddenViews.get(i); + if (view != null) { + String toName = mNameOverrides.valueAt(i); + mStartValues.nameValues.put(toName, view); + } + } + } } static void addViewValues(TransitionValuesMaps transitionValuesMaps, @@ -1361,10 +1388,10 @@ public abstract class Transition implements Cloneable { transitionValuesMaps.idValues.put(id, view); } } - String name = view.getViewName(); + String name = view.getTransitionName(); if (name != null) { if (transitionValuesMaps.nameValues.containsKey(name)) { - // Duplicate viewNames: cannot match by viewName. + // Duplicate transitionNames: cannot match by transitionName. transitionValuesMaps.nameValues.put(name, null); } else { transitionValuesMaps.nameValues.put(name, view); @@ -1400,10 +1427,12 @@ public abstract class Transition implements Cloneable { mStartValues.viewValues.clear(); mStartValues.idValues.clear(); mStartValues.itemIdValues.clear(); + mStartValues.nameValues.clear(); } else { mEndValues.viewValues.clear(); mEndValues.idValues.clear(); mEndValues.itemIdValues.clear(); + mEndValues.nameValues.clear(); } } @@ -1566,30 +1595,10 @@ public abstract class Transition implements Cloneable { AnimationInfo oldInfo = runningAnimators.get(anim); if (oldInfo != null && oldInfo.view != null && oldInfo.view.getContext() == sceneRoot.getContext()) { - boolean cancel = false; TransitionValues oldValues = oldInfo.values; View oldView = oldInfo.view; TransitionValues newValues = mEndValues.viewValues.get(oldView); - if (oldValues != null) { - // if oldValues null, then transition didn't care to stash values, - // and won't get canceled - if (newValues != null) { - for (String key : oldValues.values.keySet()) { - Object oldValue = oldValues.values.get(key); - Object newValue = newValues.values.get(key); - if (oldValue != null && newValue != null && - !oldValue.equals(newValue)) { - cancel = true; - if (DBG) { - Log.d(LOG_TAG, "Transition.playTransition: " + - "oldValue != newValue for " + key + - ": old, new = " + oldValue + ", " + newValue); - } - break; - } - } - } - } + boolean cancel = oldInfo.transition.areValuesChanged(oldValues, newValues); if (cancel) { if (anim.isRunning() || anim.isStarted()) { if (DBG) { @@ -1611,6 +1620,29 @@ public abstract class Transition implements Cloneable { runAnimators(); } + boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) { + boolean valuesChanged = false; + // if oldValues null, then transition didn't care to stash values, + // and won't get canceled + if (oldValues != null && newValues != null) { + for (String key : oldValues.values.keySet()) { + Object oldValue = oldValues.values.get(key); + Object newValue = newValues.values.get(key); + if (oldValue != null && newValue != null && + !oldValue.equals(newValue)) { + valuesChanged = true; + if (DBG) { + Log.d(LOG_TAG, "Transition.playTransition: " + + "oldValue != newValue for " + key + + ": old, new = " + oldValue + ", " + newValue); + } + break; + } + } + } + return valuesChanged; + } + /** * This is a utility method used by subclasses to handle standard parts of * setting up and running an Animator: it sets the {@link #getDuration() @@ -1866,6 +1898,20 @@ public abstract class Transition implements Cloneable { return mCanRemoveViews; } + /** + * Sets the shared element names -- a mapping from a name at the start state to + * a different name at the end state. + * @hide + */ + public void setNameOverrides(ArrayMap<String, String> overrides) { + mNameOverrides = overrides; + } + + /** @hide */ + public ArrayMap<String, String> getNameOverrides() { + return mNameOverrides; + } + @Override public String toString() { return toString(""); @@ -2035,12 +2081,15 @@ public abstract class Transition implements Cloneable { String name; TransitionValues values; WindowId windowId; + Transition transition; - AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) { + AnimationInfo(View view, String name, Transition transition, + WindowId windowId, TransitionValues values) { this.view = view; this.name = name; this.values = values; this.windowId = windowId; + this.transition = transition; } } diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 5b7c737..551f78c 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -30,7 +30,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.ArrayList; import java.util.StringTokenizer; /** @@ -43,6 +42,8 @@ import java.util.StringTokenizer; */ public class TransitionInflater { private static final String MATCH_INSTANCE = "instance"; + private static final String MATCH_NAME = "name"; + /** To be removed before L release */ private static final String MATCH_VIEW_NAME = "viewName"; private static final String MATCH_ID = "id"; private static final String MATCH_ITEM_ID = "itemId"; @@ -90,8 +91,6 @@ public class TransitionInflater { /** * Loads a {@link TransitionManager} object from a resource * - * - * * @param resource The resource id of the transition manager to load * @return The loaded TransitionManager object * @throws android.content.res.Resources.NotFoundException when the @@ -235,20 +234,20 @@ public class TransitionInflater { com.android.internal.R.styleable.TransitionTarget); int id = a.getResourceId( com.android.internal.R.styleable.TransitionTarget_targetId, -1); - String viewName; + String transitionName; if (id >= 0) { transition.addTarget(id); } else if ((id = a.getResourceId( com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) { transition.excludeTarget(id, true); - } else if ((viewName = a.getString( - com.android.internal.R.styleable.TransitionTarget_targetViewName)) + } else if ((transitionName = a.getString( + com.android.internal.R.styleable.TransitionTarget_targetName)) != null) { - transition.addTarget(viewName); - } else if ((viewName = a.getString( - com.android.internal.R.styleable.TransitionTarget_excludeViewName)) + transition.addTarget(transitionName); + } else if ((transitionName = a.getString( + com.android.internal.R.styleable.TransitionTarget_excludeName)) != null) { - transition.excludeTarget(viewName, true); + transition.excludeTarget(transitionName, true); } else { String className = a.getString( com.android.internal.R.styleable.TransitionTarget_excludeClass); @@ -282,8 +281,10 @@ public class TransitionInflater { matches[index] = Transition.MATCH_ID; } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) { matches[index] = Transition.MATCH_INSTANCE; + } else if (MATCH_NAME.equalsIgnoreCase(token)) { + matches[index] = Transition.MATCH_NAME; } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) { - matches[index] = Transition.MATCH_VIEW_NAME; + matches[index] = Transition.MATCH_NAME; } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) { matches[index] = Transition.MATCH_ITEM_ID; } else if (token.isEmpty()) { diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 698b563..495814a 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -168,30 +168,106 @@ public class TransitionSet extends Transition { @Override public TransitionSet addTarget(View target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(target); + } return (TransitionSet) super.addTarget(target); } @Override public TransitionSet addTarget(int targetId) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetId); + } return (TransitionSet) super.addTarget(targetId); } @Override + public TransitionSet addTarget(String targetName) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetName); + } + return (TransitionSet) super.addTarget(targetName); + } + + @Override + public TransitionSet addTarget(Class targetType) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).addTarget(targetType); + } + return (TransitionSet) super.addTarget(targetType); + } + + @Override public TransitionSet addListener(TransitionListener listener) { return (TransitionSet) super.addListener(listener); } @Override public TransitionSet removeTarget(int targetId) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(targetId); + } return (TransitionSet) super.removeTarget(targetId); } @Override public TransitionSet removeTarget(View target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } + return (TransitionSet) super.removeTarget(target); + } + + @Override + public TransitionSet removeTarget(Class target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } + return (TransitionSet) super.removeTarget(target); + } + + @Override + public TransitionSet removeTarget(String target) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).removeTarget(target); + } return (TransitionSet) super.removeTarget(target); } @Override + public Transition excludeTarget(View target, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(target, exclude); + } + return super.excludeTarget(target, exclude); + } + + @Override + public Transition excludeTarget(String targetName, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(targetName, exclude); + } + return super.excludeTarget(targetName, exclude); + } + + @Override + public Transition excludeTarget(int targetId, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(targetId, exclude); + } + return super.excludeTarget(targetId, exclude); + } + + @Override + public Transition excludeTarget(Class type, boolean exclude) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).excludeTarget(type, exclude); + } + return super.excludeTarget(type, exclude); + } + + @Override public TransitionSet removeListener(TransitionListener listener) { return (TransitionSet) super.removeListener(listener); } diff --git a/core/java/android/transition/TranslationAnimationCreator.java b/core/java/android/transition/TranslationAnimationCreator.java new file mode 100644 index 0000000..de71fd7 --- /dev/null +++ b/core/java/android/transition/TranslationAnimationCreator.java @@ -0,0 +1,139 @@ +/* + * 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.transition; + +import com.android.internal.R; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.graphics.Path; +import android.view.View; + +/** + * This class is used by Slide and Explode to create an animator that goes from the start + * position to the end position. It takes into account the canceled position so that it + * will not blink out or shift suddenly when the transition is interrupted. + */ +class TranslationAnimationCreator { + + /** + * Creates an animator that can be used for x and/or y translations. When interrupted, + * it sets a tag to keep track of the position so that it may be continued from position. + * + * @param view The view being moved. This may be in the overlay for onDisappear. + * @param values The values containing the view in the view hierarchy. + * @param viewPosX The x screen coordinate of view + * @param viewPosY The y screen coordinate of view + * @param startX The start translation x of view + * @param startY The start translation y of view + * @param endX The end translation x of view + * @param endY The end translation y of view + * @param interpolator The interpolator to use with this animator. + * @return An animator that moves from (startX, startY) to (endX, endY) unless there was + * a previous interruption, in which case it moves from the current position to (endX, endY). + */ + static Animator createAnimation(View view, TransitionValues values, int viewPosX, int viewPosY, + float startX, float startY, float endX, float endY, TimeInterpolator interpolator) { + float terminalX = view.getTranslationX(); + float terminalY = view.getTranslationY(); + int[] startPosition = (int[]) values.view.getTag(R.id.transitionPosition); + if (startPosition != null) { + startX = startPosition[0] - viewPosX + terminalX; + startY = startPosition[1] - viewPosY + terminalY; + } + // Initial position is at translation startX, startY, so position is offset by that amount + int startPosX = viewPosX + Math.round(startX - terminalX); + int startPosY = viewPosY + Math.round(startY - terminalY); + + view.setTranslationX(startX); + view.setTranslationY(startY); + if (startX == endX && startY == endY) { + return null; + } + Path path = new Path(); + path.moveTo(startX, startY); + path.lineTo(endX, endY); + ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, + path); + + TransitionPositionListener listener = new TransitionPositionListener(view, values.view, + startPosX, startPosY, terminalX, terminalY); + anim.addListener(listener); + anim.addPauseListener(listener); + anim.setInterpolator(interpolator); + return anim; + } + + private static class TransitionPositionListener extends AnimatorListenerAdapter { + + private final View mViewInHierarchy; + private final View mMovingView; + private final int mStartX; + private final int mStartY; + private int[] mTransitionPosition; + private float mPausedX; + private float mPausedY; + private final float mTerminalX; + private final float mTerminalY; + + private TransitionPositionListener(View movingView, View viewInHierarchy, + int startX, int startY, float terminalX, float terminalY) { + mMovingView = movingView; + mViewInHierarchy = viewInHierarchy; + mStartX = startX - Math.round(mMovingView.getTranslationX()); + mStartY = startY - Math.round(mMovingView.getTranslationY()); + mTerminalX = terminalX; + mTerminalY = terminalY; + mTransitionPosition = (int[]) mViewInHierarchy.getTag(R.id.transitionPosition); + if (mTransitionPosition != null) { + mViewInHierarchy.setTagInternal(R.id.transitionPosition, null); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (mTransitionPosition == null) { + mTransitionPosition = new int[2]; + } + mTransitionPosition[0] = Math.round(mStartX + mMovingView.getTranslationX()); + mTransitionPosition[1] = Math.round(mStartY + mMovingView.getTranslationY()); + mViewInHierarchy.setTagInternal(R.id.transitionPosition, mTransitionPosition); + } + + @Override + public void onAnimationEnd(Animator animator) { + mMovingView.setTranslationX(mTerminalX); + mMovingView.setTranslationY(mTerminalY); + } + + @Override + public void onAnimationPause(Animator animator) { + mPausedX = mMovingView.getTranslationX(); + mPausedY = mMovingView.getTranslationY(); + mMovingView.setTranslationX(mTerminalX); + mMovingView.setTranslationY(mTerminalY); + } + + @Override + public void onAnimationResume(Animator animator) { + mMovingView.setTranslationX(mPausedX); + mMovingView.setTranslationY(mPausedY); + } + } + +} diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 0f7638b..947e1a7 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -18,6 +18,9 @@ package android.transition; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.view.View; import android.view.ViewGroup; @@ -272,15 +275,23 @@ public abstract class Visibility extends Transition { if (startView.getParent() == null) { // no parent - safe to use overlayView = startView; - } else if (startView.getParent() instanceof View && - startView.getParent().getParent() == null) { + } else if (startView.getParent() instanceof View) { View startParent = (View) startView.getParent(); - int id = startParent.getId(); - if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) { - // no parent, but its parent is unparented but the parent - // hierarchy has been replaced by a new hierarchy with the same id - // and it is safe to un-parent startView - overlayView = startView; + if (!isValidTarget(startParent)) { + if (startView.isAttachedToWindow()) { + overlayView = copyViewImage(startView); + } else { + overlayView = startView; + } + } else if (startParent.getParent() == null) { + int id = startParent.getId(); + if (id != View.NO_ID && sceneRoot.findViewById(id) != null + && mCanRemoveViews) { + // no parent, but its parent is unparented but the parent + // hierarchy has been replaced by a new hierarchy with the same id + // and it is safe to un-parent startView + overlayView = startView; + } } } } @@ -320,16 +331,6 @@ public abstract class Visibility extends Transition { public void onAnimationEnd(Animator animation) { finalSceneRoot.getOverlay().remove(finalOverlayView); } - - @Override - public void onAnimationPause(Animator animation) { - finalSceneRoot.getOverlay().remove(finalOverlayView); - } - - @Override - public void onAnimationResume(Animator animation) { - finalSceneRoot.getOverlay().add(finalOverlayView); - } }); } return animator; @@ -378,6 +379,36 @@ public abstract class Visibility extends Transition { return null; } + private View copyViewImage(View view) { + int width = view.getWidth(); + int height = view.getHeight(); + if (width <= 0 || height <= 0) { + return null; + } + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + final BitmapDrawable drawable = new BitmapDrawable(bitmap); + + View overlayView = new View(view.getContext()); + overlayView.setBackground(drawable); + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + overlayView.measure(widthSpec, heightSpec); + overlayView.layout(0, 0, width, height); + return overlayView; + } + + @Override + boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) { + VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues); + if (oldValues == null && newValues == null) { + return false; + } + return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE || + changeInfo.endVisibility == View.VISIBLE); + } + /** * The default implementation of this method returns a null Animator. Subclasses should * override this method to make targets disappear with the desired transition. The diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java new file mode 100644 index 0000000..f4a0448 --- /dev/null +++ b/core/java/android/util/PathParser.java @@ -0,0 +1,609 @@ +/* + * 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.util; + +import android.graphics.Path; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * @hide + */ +public class PathParser { + static final String LOGTAG = PathParser.class.getSimpleName(); + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return the generated Path object. + */ + public static Path createPathFromPathData(String pathData) { + Path path = new Path(); + PathDataNode[] nodes = createNodesFromPathData(pathData); + if (nodes != null) { + PathDataNode.nodesToPath(nodes, path); + return path; + } + return null; + } + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return an array of the PathDataNode. + */ + public static PathDataNode[] createNodesFromPathData(String pathData) { + if (pathData == null) { + return null; + } + int start = 0; + int end = 1; + + ArrayList<PathDataNode> list = new ArrayList<PathDataNode>(); + while (end < pathData.length()) { + end = nextStart(pathData, end); + String s = pathData.substring(start, end); + float[] val = getFloats(s); + addNode(list, s.charAt(0), val); + + start = end; + end++; + } + if ((end - start) == 1 && start < pathData.length()) { + addNode(list, pathData.charAt(start), new float[0]); + } + return list.toArray(new PathDataNode[list.size()]); + } + + /** + * @param source The array of PathDataNode to be duplicated. + * @return a deep copy of the <code>source</code>. + */ + public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { + PathDataNode[] copy = new PathParser.PathDataNode[source.length]; + for (int i = 0; i < source.length; i ++) { + copy[i] = new PathDataNode(source[i]); + } + return copy; + } + + /** + * @param nodesFrom The source path represented in an array of PathDataNode + * @param nodesTo The target path represented in an array of PathDataNode + * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> + */ + public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { + if (nodesFrom == null || nodesTo == null) { + return false; + } + + if (nodesFrom.length != nodesTo.length) { + return false; + } + + for (int i = 0; i < nodesFrom.length; i ++) { + if (nodesFrom[i].mType != nodesTo[i].mType + || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { + return false; + } + } + return true; + } + + /** + * Update the target's data to match the source. + * Before calling this, make sure canMorph(target, source) is true. + * + * @param target The target path represented in an array of PathDataNode + * @param source The source path represented in an array of PathDataNode + */ + public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { + for (int i = 0; i < source.length; i ++) { + target[i].mType = source[i].mType; + for (int j = 0; j < source[i].mParams.length; j ++) { + target[i].mParams[j] = source[i].mParams[j]; + } + } + } + + private static int nextStart(String s, int end) { + char c; + + while (end < s.length()) { + c = s.charAt(end); + if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) { + return end; + } + end++; + } + return end; + } + + private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) { + list.add(new PathDataNode(cmd, val)); + } + + + /** + * Parse the floats in the string. + * This is an optimized version of parseFloat(s.split(",|\\s")); + * + * @param s the string containing a command and list of floats + * @return array of floats + */ + private static float[] getFloats(String s) { + if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { + return new float[0]; + } + try { + float[] tmp = new float[s.length()]; + int count = 0; + int pos = 1, end; + while ((end = extract(s, pos)) >= 0) { + if (pos < end) { + tmp[count++] = Float.parseFloat(s.substring(pos, end)); + } + pos = end + 1; + } + // handle the final float if there is one + if (pos < s.length()) { + tmp[count++] = Float.parseFloat(s.substring(pos, s.length())); + } + return Arrays.copyOf(tmp, count); + } catch (NumberFormatException e){ + Log.e(LOGTAG,"error in parsing \""+s+"\""); + throw e; + } + } + + /** + * Calculate the position of the next comma or space + * @param s the string to search + * @param start the position to start searching + * @return the position of the next comma or space or -1 if none found + */ + private static int extract(String s, int start) { + int space = s.indexOf(' ', start); + int comma = s.indexOf(',', start); + if (space == -1) { + return comma; + } + if (comma == -1) { + return space; + } + return (comma > space) ? space : comma; + } + + /** + * Each PathDataNode represents one command in the "d" attribute of the svg + * file. + * An array of PathDataNode can represent the whole "d" attribute. + */ + public static class PathDataNode { + private char mType; + private float[] mParams; + + private PathDataNode(char type, float[] params) { + mType = type; + mParams = params; + } + + private PathDataNode(PathDataNode n) { + mType = n.mType; + mParams = Arrays.copyOf(n.mParams, n.mParams.length); + } + + /** + * Convert an array of PathDataNode to Path. + * + * @param node The source array of PathDataNode. + * @param path The target Path object. + */ + public static void nodesToPath(PathDataNode[] node, Path path) { + float[] current = new float[4]; + char previousCommand = 'm'; + for (int i = 0; i < node.length; i++) { + addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); + previousCommand = node[i].mType; + } + } + + /** + * The current PathDataNode will be interpolated between the + * <code>nodeFrom</code> and <code>nodeTo</code> according to the + * <code>fraction</code>. + * + * @param nodeFrom The start value as a PathDataNode. + * @param nodeTo The end value as a PathDataNode + * @param fraction The fraction to interpolate. + */ + public void interpolatePathDataNode(PathDataNode nodeFrom, + PathDataNode nodeTo, float fraction) { + for (int i = 0; i < nodeFrom.mParams.length; i++) { + mParams[i] = nodeFrom.mParams[i] * (1 - fraction) + + nodeTo.mParams[i] * fraction; + } + } + + private static void addCommand(Path path, float[] current, + char previousCmd, char cmd, float[] val) { + + int incr = 2; + float currentX = current[0]; + float currentY = current[1]; + float ctrlPointX = current[2]; + float ctrlPointY = current[3]; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + path.close(); + return; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + for (int k = 0; k < val.length; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + path.rMoveTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'M': // moveto - Start a new sub-path + path.moveTo(val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'l': // lineto - Draw a line from the current point (relative) + path.rLineTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'L': // lineto - Draw a line from the current point + path.lineTo(val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'z': // closepath - Close the current subpath + case 'Z': // closepath - Close the current subpath + path.close(); + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + path.rLineTo(val[k + 0], 0); + currentX += val[k + 0]; + break; + case 'H': // horizontal lineto - Draws a horizontal line + path.lineTo(val[k + 0], currentY); + currentX = val[k + 0]; + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + path.rLineTo(0, val[k + 0]); + currentY += val[k + 0]; + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + path.lineTo(currentX, val[k + 0]); + currentY = val[k + 0]; + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + + ctrlPointX = currentX + val[k + 2]; + ctrlPointY = currentY + val[k + 3]; + currentX += val[k + 4]; + currentY += val[k + 5]; + + break; + case 'C': // curveto - Draws a cubic Bézier curve + path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + currentX = val[k + 4]; + currentY = val[k + 5]; + ctrlPointX = val[k + 2]; + ctrlPointY = val[k + 3]; + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], + val[k + 2], val[k + 3]); + + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 'q': // Draws a quadratic Bézier (relative) + path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'Q': // Draws a quadratic Bézier + path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(path, + currentX, + currentY, + val[k + 5] + currentX, + val[k + 6] + currentY, + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX += val[k + 5]; + currentY += val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(path, + currentX, + currentY, + val[k + 5], + val[k + 6], + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX = val[k + 5]; + currentY = val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + } + previousCmd = cmd; + } + current[0] = currentX; + current[1] = currentY; + current[2] = ctrlPointX; + current[3] = ctrlPointY; + } + + private static void drawArc(Path p, + float x0, + float y0, + float x1, + float y1, + float a, + float b, + float theta, + boolean isMoreThanHalf, + boolean isPositiveArc) { + + /* Convert rotation angle from degrees to radians */ + double thetaD = Math.toRadians(theta); + /* Pre-compute rotation matrix entries */ + double cosTheta = Math.cos(thetaD); + double sinTheta = Math.sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + Log.w(LOGTAG, " Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + Log.w(LOGTAG, "Points are too far apart " + dsq); + float adjust = (float) (Math.sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, + b * adjust, theta, isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = Math.sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = Math.atan2((y0p - cy), (x0p - cx)); + + double eta1 = Math.atan2((y1p - cy), (x1p - cx)); + + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * Math.PI; + } else { + sweep += 2 * Math.PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); + } + + /** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ + private static void arcToBezier(Path p, + double cx, + double cy, + double a, + double b, + double e1x, + double e1y, + double theta, + double start, + double sweep) { + // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI)); + + double eta1 = start; + double cosTheta = Math.cos(theta); + double sinTheta = Math.sin(theta); + double cosEta1 = Math.cos(eta1); + double sinEta1 = Math.sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = Math.sin(eta2); + double cosEta2 = Math.cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = Math.tan((eta2 - eta1) / 2); + double alpha = + Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p.cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } + } + } +} diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 8f4b710..f7d2821 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -62,10 +62,7 @@ public class TimeUtils { */ public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) { TimeZone best = null; - - Resources r = Resources.getSystem(); - XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country); - Date d = new Date(when); + final Date d = new Date(when); TimeZone current = TimeZone.getDefault(); String currentName = current.getID(); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index d7a913d..76a6f52 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -599,6 +599,40 @@ public final class Display { } /** + * Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating + * the phase offset of the VSYNC events provided by Choreographer relative to the + * display refresh. For example, if Choreographer reports that the refresh occurred + * at time N, it actually occurred at (N - appVsyncOffset). + * <p> + * Apps generally do not need to be aware of this. It's only useful for fine-grained + * A/V synchronization. + */ + public long getAppVsyncOffsetNanos() { + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.appVsyncOffsetNanos; + } + } + + /** + * This is how far in advance a buffer must be queued for presentation at + * a given time. If you want a buffer to appear on the screen at + * time N, you must submit the buffer before (N - presentationDeadline). + * <p> + * The desired presentation time for GLES rendering may be set with + * {@link android.opengl.EGLExt#eglPresentationTimeANDROID}. For video decoding, use + * {@link android.media.MediaCodec#releaseOutputBuffer(int, long)}. Times are + * expressed in nanoseconds, using the system monotonic clock + * ({@link System#nanoTime}). + */ + public long getPresentationDeadlineNanos() { + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.presentationDeadlineNanos; + } + } + + /** * Gets display metrics that describe the size and density of this display. * <p> * The size is adjusted based on the current rotation of the display. diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index b0fe0fa..98696c7 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -180,6 +180,20 @@ public final class DisplayInfo implements Parcelable { public float physicalYDpi; /** + * This is a positive value indicating the phase offset of the VSYNC events provided by + * Choreographer relative to the display refresh. For example, if Choreographer reports + * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos). + */ + public long appVsyncOffsetNanos; + + /** + * This is how far in advance a buffer must be queued for presentation at + * a given time. If you want a buffer to appear on the screen at + * time N, you must submit the buffer before (N - bufferDeadlineNanos). + */ + public long presentationDeadlineNanos; + + /** * The state of the display, such as {@link android.view.Display#STATE_ON}. */ public int state; @@ -253,6 +267,8 @@ public final class DisplayInfo implements Parcelable { && logicalDensityDpi == other.logicalDensityDpi && physicalXDpi == other.physicalXDpi && physicalYDpi == other.physicalYDpi + && appVsyncOffsetNanos == other.appVsyncOffsetNanos + && presentationDeadlineNanos == other.presentationDeadlineNanos && state == other.state && ownerUid == other.ownerUid && Objects.equal(ownerPackageName, other.ownerPackageName); @@ -286,6 +302,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = other.logicalDensityDpi; physicalXDpi = other.physicalXDpi; physicalYDpi = other.physicalYDpi; + appVsyncOffsetNanos = other.appVsyncOffsetNanos; + presentationDeadlineNanos = other.presentationDeadlineNanos; state = other.state; ownerUid = other.ownerUid; ownerPackageName = other.ownerPackageName; @@ -314,6 +332,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); physicalYDpi = source.readFloat(); + appVsyncOffsetNanos = source.readLong(); + presentationDeadlineNanos = source.readLong(); state = source.readInt(); ownerUid = source.readInt(); ownerPackageName = source.readString(); @@ -343,6 +363,8 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); dest.writeFloat(physicalYDpi); + dest.writeLong(appVsyncOffsetNanos); + dest.writeLong(presentationDeadlineNanos); dest.writeInt(state); dest.writeInt(ownerUid); dest.writeString(ownerPackageName); @@ -450,6 +472,10 @@ public final class DisplayInfo implements Parcelable { sb.append(physicalYDpi); sb.append(") dpi, layerStack "); sb.append(layerStack); + sb.append(", appVsyncOff "); + sb.append(appVsyncOffsetNanos); + sb.append(", presDeadline "); + sb.append(presentationDeadlineNanos); sb.append(", type "); sb.append(Display.typeToString(type)); if (address != null) { diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 424d860..dcd9ba9 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -51,10 +51,10 @@ class GLES20Canvas extends HardwareCanvas { private int mWidth; private int mHeight; - + private float[] mPoint; private float[] mLine; - + private Rect mClipBounds; private RectF mPathBounds; @@ -75,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas { // Constructors /////////////////////////////////////////////////////////////////////////// - /** - * Creates a canvas to render directly on screen. - */ - GLES20Canvas(boolean translucent) { - this(false, translucent); - } - - protected GLES20Canvas(boolean record, boolean translucent) { - mOpaque = !translucent; - - if (record) { - mRenderer = nCreateDisplayListRenderer(); - } else { - mRenderer = nCreateRenderer(); - } - + // TODO: Merge with GLES20RecordingCanvas + protected GLES20Canvas() { + mOpaque = false; + mRenderer = nCreateDisplayListRenderer(); setupFinalizer(); } @@ -102,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas { } } - private static native long nCreateRenderer(); private static native long nCreateDisplayListRenderer(); private static native void nResetDisplayListRenderer(long renderer); private static native void nDestroyRenderer(long renderer); @@ -131,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetProperty(String name, String value); /////////////////////////////////////////////////////////////////////////// - // Hardware layers - /////////////////////////////////////////////////////////////////////////// - - @Override - void pushLayerUpdate(HardwareLayer layer) { - nPushLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void cancelLayerUpdate(HardwareLayer layer) { - nCancelLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void flushLayerUpdates() { - nFlushLayerUpdates(mRenderer); - } - - @Override - void clearLayerUpdates() { - nClearLayerUpdates(mRenderer); - } - - 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); - private static native void nCancelLayerUpdate(long renderer, long layer); - - /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// @@ -210,7 +167,7 @@ class GLES20Canvas extends HardwareCanvas { nSetViewport(mRenderer, width, height); } - + private static native void nSetViewport(long renderer, int width, int height); @Override @@ -234,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nFinish(long renderer); - /** - * Returns the size of the stencil buffer required by the underlying - * implementation. - * - * @return The minimum number of bits the stencil buffer must. Always >= 0. - * - * @hide - */ - public static int getStencilSize() { - return nGetStencilSize(); - } - - private static native int nGetStencilSize(); - /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -265,68 +208,25 @@ class GLES20Canvas extends HardwareCanvas { /** * Must match Caches::FlushMode values - * - * @see #flushCaches(int) + * + * @see #flushCaches(int) */ static final int FLUSH_CACHES_LAYERS = 0; - + /** * Must match Caches::FlushMode values - * - * @see #flushCaches(int) + * + * @see #flushCaches(int) */ static final int FLUSH_CACHES_MODERATE = 1; /** * Must match Caches::FlushMode values - * - * @see #flushCaches(int) + * + * @see #flushCaches(int) */ static final int FLUSH_CACHES_FULL = 2; - /** - * Flush caches to reclaim as much memory as possible. The amount of memory - * to reclaim is indicate by the level parameter. - * - * The level can be one of {@link #FLUSH_CACHES_MODERATE} or - * {@link #FLUSH_CACHES_FULL}. - * - * @param level Hint about the amount of memory to reclaim - */ - static void flushCaches(int level) { - nFlushCaches(level); - } - - private static native void nFlushCaches(int level); - - /** - * Release all resources associated with the underlying caches. This should - * only be called after a full flushCaches(). - * - * @hide - */ - static void terminateCaches() { - nTerminateCaches(); - } - - private static native void nTerminateCaches(); - - static boolean initCaches() { - return nInitCaches(); - } - - private static native boolean nInitCaches(); - - /////////////////////////////////////////////////////////////////////////// - // Atlas - /////////////////////////////////////////////////////////////////////////// - - static void initAtlas(GraphicBuffer buffer, long[] map) { - nInitAtlas(buffer, map, map.length); - } - - private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count); - /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -334,18 +234,17 @@ class GLES20Canvas extends HardwareCanvas { protected static native long nFinishRecording(long renderer); @Override - public int drawDisplayList(RenderNode displayList, Rect dirty, int flags) { - return nDrawDisplayList(mRenderer, displayList.getNativeDisplayList(), - dirty, flags); + public int drawRenderNode(RenderNode renderNode, Rect dirty, int flags) { + return nDrawRenderNode(mRenderer, renderNode.getNativeDisplayList(), dirty, flags); } - private static native int nDrawDisplayList(long renderer, long displayList, + private static native int nDrawRenderNode(long renderer, long renderNode, Rect dirty, int flags); /////////////////////////////////////////////////////////////////////////// // Hardware layer /////////////////////////////////////////////////////////////////////////// - + void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) { layer.setLayerPaint(paint); nDrawLayer(mRenderer, layer.getLayer(), x, y); @@ -398,7 +297,7 @@ class GLES20Canvas extends HardwareCanvas { public boolean clipRect(float left, float top, float right, float bottom) { return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); } - + private static native boolean nClipRect(long renderer, float left, float top, float right, float bottom, int op); @@ -411,14 +310,14 @@ class GLES20Canvas extends HardwareCanvas { public boolean clipRect(int left, int top, int right, int bottom) { return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); } - + private static native boolean nClipRect(long renderer, int left, int top, int right, int bottom, int op); @Override public boolean clipRect(Rect rect) { return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, - Region.Op.INTERSECT.nativeInt); + Region.Op.INTERSECT.nativeInt); } @Override @@ -460,7 +359,7 @@ class GLES20Canvas extends HardwareCanvas { public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) { return nQuickReject(mRenderer, left, top, right, bottom); } - + private static native boolean nQuickReject(long renderer, float left, float top, float right, float bottom); @@ -485,7 +384,7 @@ class GLES20Canvas extends HardwareCanvas { public void translate(float dx, float dy) { if (dx != 0.0f || dy != 0.0f) nTranslate(mRenderer, dx, dy); } - + private static native void nTranslate(long renderer, float dx, float dy); @Override @@ -499,21 +398,24 @@ class GLES20Canvas extends HardwareCanvas { public void rotate(float degrees) { nRotate(mRenderer, degrees); } - + private static native void nRotate(long renderer, float degrees); @Override public void scale(float sx, float sy) { + // TODO: remove + if (sx > 1000000 || sy > 1000000) throw new IllegalArgumentException("invalid scales passed " + sx + ", " + sy); + nScale(mRenderer, sx, sy); } - + private static native void nScale(long renderer, float sx, float sy); @Override public void setMatrix(Matrix matrix) { nSetMatrix(mRenderer, matrix == null ? 0 : matrix.native_instance); } - + private static native void nSetMatrix(long renderer, long matrix); @SuppressWarnings("deprecation") @@ -521,16 +423,16 @@ class GLES20Canvas extends HardwareCanvas { public void getMatrix(Matrix matrix) { nGetMatrix(mRenderer, matrix.native_instance); } - + private static native void nGetMatrix(long renderer, long matrix); @Override public void concat(Matrix matrix) { if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance); } - + private static native void nConcatMatrix(long renderer, long matrix); - + /////////////////////////////////////////////////////////////////////////// // State management /////////////////////////////////////////////////////////////////////////// @@ -539,14 +441,14 @@ class GLES20Canvas extends HardwareCanvas { public int save() { return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG); } - + @Override public int save(int saveFlags) { return nSave(mRenderer, saveFlags); } private static native int nSave(long renderer, int flags); - + @Override public int saveLayer(RectF bounds, Paint paint, int saveFlags) { if (bounds != null) { @@ -594,12 +496,12 @@ class GLES20Canvas extends HardwareCanvas { private static native int nSaveLayerAlpha(long renderer, float left, float top, float right, float bottom, int alpha, int saveFlags); - + @Override public void restore() { nRestore(mRenderer); } - + private static native void nRestore(long renderer); @Override @@ -608,12 +510,12 @@ class GLES20Canvas extends HardwareCanvas { } private static native void nRestoreToCount(long renderer, int saveCount); - + @Override public int getSaveCount() { return nGetSaveCount(mRenderer); } - + private static native int nGetSaveCount(long renderer); /////////////////////////////////////////////////////////////////////////// @@ -644,9 +546,9 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, + public void drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, boolean useCenter, Paint paint) { + nDrawArc(mRenderer, left, top, right, bottom, startAngle, sweepAngle, useCenter, paint.mNativePaint); } @@ -839,7 +741,7 @@ class GLES20Canvas extends HardwareCanvas { public void drawColor(int color, PorterDuff.Mode mode) { nDrawColor(mRenderer, color, mode.nativeInt); } - + private static native void nDrawColor(long renderer, int color, int mode); @Override @@ -871,8 +773,8 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawOval(RectF oval, Paint paint) { - nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint); + public void drawOval(float left, float top, float right, float bottom, Paint paint) { + nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint); } private static native void nDrawOval(long renderer, float left, float top, @@ -899,28 +801,14 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPath(long renderer, long path, long paint); private static native void nDrawRects(long renderer, long region, long paint); - void drawRects(float[] rects, int count, Paint paint) { - nDrawRects(mRenderer, rects, count, paint.mNativePaint); - } - - private static native void nDrawRects(long renderer, float[] rects, int count, long paint); - @Override public void drawPicture(Picture picture) { - if (picture.createdFromStream) { - return; - } - picture.endRecording(); // TODO: Implement rendering } @Override public void drawPicture(Picture picture, Rect dst) { - if (picture.createdFromStream) { - return; - } - save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -932,10 +820,6 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPicture(Picture picture, RectF dst) { - if (picture.createdFromStream) { - return; - } - save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -968,31 +852,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPoints(long renderer, float[] points, int offset, int count, long paint); - @SuppressWarnings("deprecation") - @Override - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - if (index < 0 || index + count > text.length || count * 2 > pos.length) { - throw new IndexOutOfBoundsException(); - } - - nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint); - } - - private static native void nDrawPosText(long renderer, char[] text, int index, int count, - float[] pos, long paint); - - @SuppressWarnings("deprecation") - @Override - public void drawPosText(String text, float[] pos, Paint paint) { - if (text.length() * 2 > pos.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint); - } - - private static native void nDrawPosText(long renderer, String text, int start, int end, - float[] pos, long paint); + // Note: drawPosText just uses implementation in Canvas @Override public void drawRect(float left, float top, float right, float bottom, Paint paint) { @@ -1036,7 +896,7 @@ class GLES20Canvas extends HardwareCanvas { nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } - + private static native void nDrawText(long renderer, char[] text, int index, int count, float x, float y, int bidiFlags, long paint, long typeface); @@ -1084,68 +944,66 @@ class GLES20Canvas extends HardwareCanvas { } nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint); + long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, + long typeface); @Override public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { if (text.length() == 0) return; nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextOnPath(long renderer, String text, int start, int end, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint); + long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, + long typeface); @Override public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, int dir, Paint paint) { + float x, float y, boolean isRtl, Paint paint) { if ((index | count | text.length - index - count) < 0) { throw new IndexOutOfBoundsException(); } - if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { - throw new IllegalArgumentException("Unknown direction: " + dir); - } - nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir, + nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextRun(long renderer, char[] text, int index, int count, - int contextIndex, int contextCount, float x, float y, int dir, long nativePaint, long nativeTypeface); + int contextIndex, int contextCount, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); @Override public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, - float x, float y, int dir, Paint paint) { + float x, float y, boolean isRtl, Paint paint) { if ((start | end | end - start | text.length() - end) < 0) { throw new IndexOutOfBoundsException(); } - int flags = dir == 0 ? 0 : 1; if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, - contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, flags, paint); + contextStart, contextEnd, x, y, isRtl, paint); } else { int contextLen = contextEnd - contextStart; int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, - x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); TemporaryBuffer.recycle(buf); } } private static native void nDrawTextRun(long renderer, String text, int start, int end, - int contextStart, int contextEnd, float x, float y, int flags, long nativePaint, long nativeTypeface); + int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); @Override public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index a94ec3a..b2961e5 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { RenderNode mNode; private GLES20RecordingCanvas() { - super(true, true); + super(); } static GLES20RecordingCanvas obtain(@NonNull RenderNode node) { diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java deleted file mode 100644 index f1163e2..0000000 --- a/core/java/android/view/GLRenderer.java +++ /dev/null @@ -1,1521 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package 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.FileDescriptor; -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, - }; - - 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 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> mLayerUpdates = 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) { - mLayerUpdates.add(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); - } - - boolean hasContext() { - return sEgl != null && mEglContext != null - && mEglContext.equals(sEgl.eglGetCurrentContext()); - } - - @Override - void onLayerDestroyed(HardwareLayer layer) { - if (mGlCanvas != null) { - mGlCanvas.cancelLayerUpdate(layer); - } - mLayerUpdates.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 invokeFunctor(long functor, boolean waitForCompletion) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - boolean hasContext = !needsContext; - - if (needsContext) { - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - if (managedContext != null) { - usePbufferSurface(managedContext.getContext()); - hasContext = true; - } - } - - try { - nInvokeFunctor(functor, hasContext); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - } - - private static native void nInvokeFunctor(long functor, boolean hasContext); - - @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 - void setOpaque(boolean opaque) { - // Not supported - } - - @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 GraphDataProvider(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, FileDescriptor fd) { - 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(); - } - } - } - - /** - * 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); - } - } - - @Override - void pauseSurface(Surface surface) { - // No-op - } - - 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, float lightX, float lightY, float lightZ, float lightRadius) { - if (validate()) { - mCanvas.setViewport(width, height); - mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius); - mWidth = width; - mHeight = height; - } - } - - @Override - int getWidth() { - return mWidth; - } - - @Override - int getHeight() { - return mHeight; - } - - @Override - void setName(String name) { - mName = name; - } - - @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); - - RenderNode 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 = RenderNode.STATUS_DONE; - - long start = getSystemTime(); - try { - status = prepareFrame(dirty); - - saveCount = canvas.save(); - callbacks.onHardwarePreDraw(canvas); - - if (displayList != null) { - status |= drawDisplayList(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 < mLayerUpdates.size(); i++) { - HardwareLayer layer = mLayerUpdates.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--; - } else if (layer.hasDisplayList()) { - mCanvas.pushLayerUpdate(layer); - } - } - mLayerUpdates.clear(); - } - - @Override - void fence() { - // Everything is immediate, so this is a no-op - } - - private RenderNode buildDisplayList(View view, HardwareCanvas canvas) { - 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"); - RenderNode renderNode = view.getDisplayList(); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - - endBuildDisplayListProfiling(buildDisplayListStartTime); - - return renderNode; - } - - 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(HardwareCanvas canvas, RenderNode displayList, - int status) { - - long drawDisplayListStartTime = 0; - if (mProfileEnabled) { - drawDisplayListStartTime = System.nanoTime(); - } - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); - nPrepareTree(displayList.getNativeDisplayList()); - try { - status |= canvas.drawDisplayList(displayList, mRedrawClip, - RenderNode.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; - } - - return status; - } - - private void swapBuffers(int status) { - if ((status & RenderNode.STATUS_DREW) == RenderNode.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); - } - } - } - - /** - * 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); - - private static native void nPrepareTree(long displayListPtr); - - class GraphDataProvider { - /** - * Draws the graph as bars. Frame elements are stacked on top of - * each other. - */ - public static final int GRAPH_TYPE_BARS = 0; - /** - * Draws the graph as lines. The number of series drawn corresponds - * to the number of elements. - */ - public static final int GRAPH_TYPE_LINES = 1; - - private final int mGraphType; - - private int mVerticalUnit; - private int mHorizontalUnit; - private int mHorizontalMargin; - private int mThresholdStroke; - - public GraphDataProvider(int graphType) { - mGraphType = graphType; - } - - 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); - } - - int getGraphType() { - return mGraphType; - } - - int getVerticalUnitSize() { - return mVerticalUnit; - } - - int getHorizontalUnitSize() { - return mHorizontalUnit; - } - - int getHorizontaUnitMargin() { - return mHorizontalMargin; - } - - float[] getData() { - return mProfileData; - } - - float getThreshold() { - return 16; - } - - int getFrameCount() { - return mProfileData.length / PROFILE_FRAME_DATA_COUNT; - } - - int getElementCount() { - return PROFILE_FRAME_DATA_COUNT; - } - - int getCurrentFrame() { - return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; - } - - void setupGraphPaint(Paint paint, int elementIndex) { - paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - - void setupThresholdPaint(Paint paint) { - paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); - paint.setStrokeWidth(mThresholdStroke); - } - - 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/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 9568760..263ebda 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -61,16 +61,16 @@ public abstract class HardwareCanvas extends Canvas { * Draws the specified display list onto this canvas. The display list can only * be drawn if {@link android.view.RenderNode#isValid()} returns true. * - * @param displayList The display list to replay. + * @param renderNode The RenderNode to replay. */ - public void drawDisplayList(RenderNode displayList) { - drawDisplayList(displayList, null, RenderNode.FLAG_CLIP_CHILDREN); + public void drawRenderNode(RenderNode renderNode) { + drawRenderNode(renderNode, null, RenderNode.FLAG_CLIP_CHILDREN); } /** * Draws the specified display list onto this canvas. * - * @param displayList The display list to replay. + * @param renderNode The RenderNode to replay. * @param dirty Ignored, can be null. * @param flags Optional flags about drawing, see {@link RenderNode} for * the possible flags. @@ -80,7 +80,7 @@ public abstract class HardwareCanvas extends Canvas { * * @hide */ - public abstract int drawDisplayList(RenderNode displayList, Rect dirty, int flags); + public abstract int drawRenderNode(RenderNode renderNode, Rect dirty, int flags); /** * Draws the specified layer onto this canvas. @@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas { return RenderNode.STATUS_DONE; } - /** - * Indicates that the specified layer must be updated as soon as possible. - * - * @param layer The layer to update - * - * @see #clearLayerUpdates() - * - * @hide - */ - 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) - * @see #clearLayerUpdates() - * - * @hide - */ - abstract void cancelLayerUpdate(HardwareLayer layer); - - /** - * Immediately executes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void flushLayerUpdates(); - - /** - * Removes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void clearLayerUpdates(); - public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint); } diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 652bcd2..266a6fe 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -19,7 +19,6 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.SurfaceTexture; import com.android.internal.util.VirtualRefBasePtr; @@ -34,34 +33,18 @@ import com.android.internal.util.VirtualRefBasePtr; * @hide */ final class HardwareLayer { - private static final int LAYER_TYPE_TEXTURE = 1; - private static final int LAYER_TYPE_DISPLAY_LIST = 2; - private HardwareRenderer mRenderer; private VirtualRefBasePtr mFinalizer; - private RenderNode mDisplayList; - private final int mLayerType; - private HardwareLayer(HardwareRenderer renderer, long deferredUpdater, int type) { + private HardwareLayer(HardwareRenderer renderer, long deferredUpdater) { if (renderer == null || deferredUpdater == 0) { throw new IllegalArgumentException("Either hardware renderer: " + renderer + " or deferredUpdater: " + deferredUpdater + " is invalid"); } mRenderer = renderer; - mLayerType = type; mFinalizer = new VirtualRefBasePtr(deferredUpdater); } - private void assertType(int type) { - if (mLayerType != type) { - throw new IllegalAccessError("Method not appropriate for this layer type! " + mLayerType); - } - } - - boolean hasDisplayList() { - return mDisplayList != null; - } - /** * Update the paint used when drawing this layer. * @@ -90,11 +73,6 @@ final class HardwareLayer { // Already destroyed return; } - - if (mDisplayList != null) { - mDisplayList.destroyDisplayListData(); - mDisplayList = null; - } mRenderer.onLayerDestroyed(this); mRenderer = null; mFinalizer.release(); @@ -105,21 +83,6 @@ final class HardwareLayer { return mFinalizer.get(); } - public RenderNode startRecording() { - assertType(LAYER_TYPE_DISPLAY_LIST); - - if (mDisplayList == null) { - mDisplayList = RenderNode.create("HardwareLayer"); - } - return mDisplayList; - } - - public void endRecording(Rect dirtyRect) { - nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(), - dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); - mRenderer.pushLayerUpdate(this); - } - /** * Copies this layer into the specified bitmap. * @@ -160,7 +123,6 @@ final class HardwareLayer { * Indicates that this layer has lost its texture. */ public void detachSurfaceTexture(final SurfaceTexture surface) { - assertType(LAYER_TYPE_TEXTURE); mRenderer.safelyRun(new Runnable() { @Override public void run() { @@ -172,36 +134,16 @@ final class HardwareLayer { }); } - /** - * 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 - */ - @Deprecated - public void flushChanges() { - if (HardwareRenderer.sUseRenderThread) { - // Not supported, don't try. - return; - } - - boolean success = nFlushChanges(mFinalizer.get()); - if (!success) { - destroy(); - } - } - public long getLayer() { return nGetLayer(mFinalizer.get()); } public void setSurfaceTexture(SurfaceTexture surface) { - assertType(LAYER_TYPE_TEXTURE); nSetSurfaceTexture(mFinalizer.get(), surface, false); mRenderer.pushLayerUpdate(this); } public void updateSurfaceTexture() { - assertType(LAYER_TYPE_TEXTURE); nUpdateSurfaceTexture(mFinalizer.get()); mRenderer.pushLayerUpdate(this); } @@ -210,39 +152,15 @@ final class HardwareLayer { * This should only be used by HardwareRenderer! Do not call directly */ SurfaceTexture createSurfaceTexture() { - assertType(LAYER_TYPE_TEXTURE); SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get())); nSetSurfaceTexture(mFinalizer.get(), st, true); return st; } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - 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); + return new HardwareLayer(renderer, layer); } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - static HardwareLayer createDisplayListLayer(HardwareRenderer renderer, - int width, int height) { - return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST); - } - - 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 boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque); @@ -254,8 +172,6 @@ final class HardwareLayer { 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); } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d71de9f..cfb4af2 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -16,6 +16,7 @@ package android.view; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.SurfaceTexture; @@ -171,9 +172,6 @@ public abstract class HardwareRenderer { */ public static boolean sSystemRendererDisabled = false; - /** @hide */ - public static boolean sUseRenderThread = true; - private boolean mEnabled; private boolean mRequested = true; @@ -309,7 +307,7 @@ public abstract class HardwareRenderer { * @hide */ public static void setupDiskCache(File cacheDir) { - GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); } /** @@ -366,8 +364,7 @@ public abstract class HardwareRenderer { * @param callbacks Callbacks invoked when drawing happens. * @param dirty The dirty rectangle to update, can be null. */ - abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, - Rect dirty); + abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks); /** * Creates a new hardware layer. A hardware layer built by calling this @@ -378,16 +375,6 @@ public abstract class HardwareRenderer { abstract HardwareLayer createTextureLayer(); /** - * Creates a new hardware layer. - * - * @param width The minimum width of the layer - * @param height The minimum height of the layer - * - * @return A hardware layer - */ - abstract HardwareLayer createDisplayListLayer(int width, int height); - - /** * Creates a new {@link SurfaceTexture} that can be used to render into the * specified hardware layer. * @@ -400,17 +387,6 @@ public abstract class HardwareRenderer { abstract boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap); /** - * Schedules the functor for execution in either kModeProcess or - * kModeProcessNoContext, depending on whether or not there is an EGLContext. - * - * @param functor The native functor to invoke - * @param waitForCompletion If true, this will not return until the functor - * has invoked. If false, the functor may be invoked - * asynchronously. - */ - abstract void invokeFunctor(long functor, boolean waitForCompletion); - - /** * Initializes the hardware renderer for the specified surface and setup the * renderer for drawing, if needed. This is invoked when the ViewAncestor has * potentially lost the hardware renderer. The hardware renderer should be @@ -466,14 +442,10 @@ public abstract class HardwareRenderer { * * @return A hardware renderer backed by OpenGL. */ - static HardwareRenderer create(boolean translucent) { + static HardwareRenderer create(Context context, boolean translucent) { HardwareRenderer renderer = null; if (GLES20Canvas.isAvailable()) { - if (sUseRenderThread) { - renderer = new ThreadedRenderer(translucent); - } else { - renderer = new GLRenderer(translucent); - } + renderer = new ThreadedRenderer(context, translucent); } return renderer; } @@ -500,7 +472,7 @@ public abstract class HardwareRenderer { * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { - GLRenderer.startTrimMemory(level); + ThreadedRenderer.startTrimMemory(level); } /** @@ -508,7 +480,7 @@ public abstract class HardwareRenderer { * cleanup special resources used by the memory trimming process. */ static void endTrimMemory() { - GLRenderer.endTrimMemory(); + ThreadedRenderer.endTrimMemory(); } /** diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index c32a2c9..fa5bd88 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -46,7 +46,7 @@ interface IWindowSession { int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets); void remove(IWindow window); - + /** * Change the parameters of a window. You supply the * new parameters, it returns the new frame of the window on screen (the @@ -109,7 +109,7 @@ interface IWindowSession { * to optimize compositing of this part of the window. */ void setTransparentRegion(IWindow window, in Region region); - + /** * Tell the window manager about the content and visible insets of the * given window, which can be used to adjust the <var>outContentInsets</var> @@ -122,20 +122,20 @@ interface IWindowSession { */ void setInsets(IWindow window, int touchableInsets, in Rect contentInsets, in Rect visibleInsets, in Region touchableRegion); - + /** * Return the current display size in which the window is being laid out, * accounting for screen decorations around it. */ void getDisplayFrame(IWindow window, out Rect outDisplayFrame); - + void finishDrawing(IWindow window); - + void setInTouchMode(boolean showFocus); boolean getInTouchMode(); - + boolean performHapticFeedback(IWindow window, int effectId, boolean always); - + /** * Allocate the drag's thumbnail surface. Also assigns a token that identifies * the drag to the OS and passes that as the return value. A return value of @@ -150,11 +150,11 @@ interface IWindowSession { boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data); - /** - * Report the result of a drop action targeted to the given window. - * consumed is 'true' when the drop was accepted by a valid recipient, - * 'false' otherwise. - */ + /** + * Report the result of a drop action targeted to the given window. + * consumed is 'true' when the drop was accepted by a valid recipient, + * 'false' otherwise. + */ void reportDropResult(IWindow window, boolean consumed); /** @@ -174,12 +174,12 @@ interface IWindowSession { * how big the increment is from one screen to another. */ void setWallpaperPosition(IBinder windowToken, float x, float y, float xstep, float ystep); - + void wallpaperOffsetsComplete(IBinder window); - + Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, in Bundle extras, boolean sync); - + void wallpaperCommandComplete(IBinder window, in Bundle result); void setUniverseTransform(IBinder window, float alpha, float offx, float offy, @@ -188,7 +188,7 @@ interface IWindowSession { /** * Notifies that a rectangle on the screen has been requested. */ - void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate); + void onRectangleOnScreenRequested(IBinder token, in Rect rectangle); IWindowId getWindowId(IBinder window); } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 8a996d2..8b2ec7a 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1716,6 +1716,7 @@ public class KeyEvent extends InputEvent implements Parcelable { case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_SLEEP: case KeyEvent.KEYCODE_WAKEUP: + case KeyEvent.KEYCODE_PAIRING: return true; } return false; diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index b9ed801..577415e 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -311,7 +311,7 @@ public abstract class LayoutInflater { if (mFactory == null) { mFactory = mFactory2 = factory; } else { - mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2); + mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } } @@ -319,7 +319,11 @@ public abstract class LayoutInflater { * @hide for use by framework */ public void setPrivateFactory(Factory2 factory) { - mPrivateFactory = factory; + if (mPrivateFactory == null) { + mPrivateFactory = factory; + } else { + mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory); + } } /** diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index e63829e..d86b699 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.NonNull; import android.graphics.Matrix; import android.graphics.Outline; +import android.graphics.Paint; import java.util.ArrayList; import java.util.List; @@ -322,11 +323,13 @@ public class RenderNode { * handled in the drawLayer operation directly (and more efficiently). * * @param caching true if the display list represents a hardware layer, false otherwise. - * - * @hide */ - public void setCaching(boolean caching) { - nSetCaching(mNativeRenderNode, caching); + public boolean setLayerType(int layerType) { + return nSetLayerType(mNativeRenderNode, layerType); + } + + public boolean setLayerPaint(Paint paint) { + return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.mNativePaint : 0); } /** @@ -335,8 +338,8 @@ public class RenderNode { * * @param clipToBounds true if the display list should clip to its bounds */ - public void setClipToBounds(boolean clipToBounds) { - nSetClipToBounds(mNativeRenderNode, clipToBounds); + public boolean setClipToBounds(boolean clipToBounds) { + return nSetClipToBounds(mNativeRenderNode, clipToBounds); } /** @@ -346,8 +349,8 @@ public class RenderNode { * @param shouldProject true if the display list should be projected onto a * containing volume. */ - public void setProjectBackwards(boolean shouldProject) { - nSetProjectBackwards(mNativeRenderNode, shouldProject); + public boolean setProjectBackwards(boolean shouldProject) { + return nSetProjectBackwards(mNativeRenderNode, shouldProject); } /** @@ -355,8 +358,8 @@ public class RenderNode { * DisplayList should draw any descendent DisplayLists with * ProjectBackwards=true directly on top of it. Default value is false. */ - public void setProjectionReceiver(boolean shouldRecieve) { - nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); + public boolean setProjectionReceiver(boolean shouldRecieve) { + return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); } /** @@ -365,15 +368,16 @@ public class RenderNode { * * Deep copies the data into native to simplify reference ownership. */ - public void setOutline(Outline outline) { + public boolean setOutline(Outline outline) { if (outline == null || outline.isEmpty()) { - nSetOutlineEmpty(mNativeRenderNode); + return nSetOutlineEmpty(mNativeRenderNode); } else if (outline.mRect != null) { - nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top, + return nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top, outline.mRect.right, outline.mRect.bottom, outline.mRadius); } else if (outline.mPath != null) { - nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath); + return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath); } + throw new IllegalArgumentException("Unrecognized outline?"); } /** @@ -381,8 +385,8 @@ public class RenderNode { * * @param clipToOutline true if clipping to the outline. */ - public void setClipToOutline(boolean clipToOutline) { - nSetClipToOutline(mNativeRenderNode, clipToOutline); + public boolean setClipToOutline(boolean clipToOutline) { + return nSetClipToOutline(mNativeRenderNode, clipToOutline); } public boolean getClipToOutline() { @@ -392,9 +396,9 @@ public class RenderNode { /** * Controls the RenderNode's circular reveal clip. */ - public void setRevealClip(boolean shouldClip, boolean inverseClip, + public boolean setRevealClip(boolean shouldClip, boolean inverseClip, float x, float y, float radius) { - nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius); + return nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius); } /** @@ -403,8 +407,8 @@ public class RenderNode { * * @param matrix A transform matrix to apply to this display list */ - public void setStaticMatrix(Matrix matrix) { - nSetStaticMatrix(mNativeRenderNode, matrix.native_instance); + public boolean setStaticMatrix(Matrix matrix) { + return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance); } /** @@ -417,8 +421,8 @@ public class RenderNode { * * @hide */ - public void setAnimationMatrix(Matrix matrix) { - nSetAnimationMatrix(mNativeRenderNode, + public boolean setAnimationMatrix(Matrix matrix) { + return nSetAnimationMatrix(mNativeRenderNode, (matrix != null) ? matrix.native_instance : 0); } @@ -430,8 +434,8 @@ public class RenderNode { * @see View#setAlpha(float) * @see #getAlpha() */ - public void setAlpha(float alpha) { - nSetAlpha(mNativeRenderNode, alpha); + public boolean setAlpha(float alpha) { + return nSetAlpha(mNativeRenderNode, alpha); } /** @@ -456,8 +460,8 @@ public class RenderNode { * @see android.view.View#hasOverlappingRendering() * @see #hasOverlappingRendering() */ - public void setHasOverlappingRendering(boolean hasOverlappingRendering) { - nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering); + public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) { + return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering); } /** @@ -472,8 +476,8 @@ public class RenderNode { return nHasOverlappingRendering(mNativeRenderNode); } - public void setElevation(float lift) { - nSetElevation(mNativeRenderNode, lift); + public boolean setElevation(float lift) { + return nSetElevation(mNativeRenderNode, lift); } public float getElevation() { @@ -488,8 +492,8 @@ public class RenderNode { * @see View#setTranslationX(float) * @see #getTranslationX() */ - public void setTranslationX(float translationX) { - nSetTranslationX(mNativeRenderNode, translationX); + public boolean setTranslationX(float translationX) { + return nSetTranslationX(mNativeRenderNode, translationX); } /** @@ -509,8 +513,8 @@ public class RenderNode { * @see View#setTranslationY(float) * @see #getTranslationY() */ - public void setTranslationY(float translationY) { - nSetTranslationY(mNativeRenderNode, translationY); + public boolean setTranslationY(float translationY) { + return nSetTranslationY(mNativeRenderNode, translationY); } /** @@ -528,8 +532,8 @@ public class RenderNode { * @see View#setTranslationZ(float) * @see #getTranslationZ() */ - public void setTranslationZ(float translationZ) { - nSetTranslationZ(mNativeRenderNode, translationZ); + public boolean setTranslationZ(float translationZ) { + return nSetTranslationZ(mNativeRenderNode, translationZ); } /** @@ -549,8 +553,8 @@ public class RenderNode { * @see View#setRotation(float) * @see #getRotation() */ - public void setRotation(float rotation) { - nSetRotation(mNativeRenderNode, rotation); + public boolean setRotation(float rotation) { + return nSetRotation(mNativeRenderNode, rotation); } /** @@ -570,8 +574,8 @@ public class RenderNode { * @see View#setRotationX(float) * @see #getRotationX() */ - public void setRotationX(float rotationX) { - nSetRotationX(mNativeRenderNode, rotationX); + public boolean setRotationX(float rotationX) { + return nSetRotationX(mNativeRenderNode, rotationX); } /** @@ -591,8 +595,8 @@ public class RenderNode { * @see View#setRotationY(float) * @see #getRotationY() */ - public void setRotationY(float rotationY) { - nSetRotationY(mNativeRenderNode, rotationY); + public boolean setRotationY(float rotationY) { + return nSetRotationY(mNativeRenderNode, rotationY); } /** @@ -612,8 +616,8 @@ public class RenderNode { * @see View#setScaleX(float) * @see #getScaleX() */ - public void setScaleX(float scaleX) { - nSetScaleX(mNativeRenderNode, scaleX); + public boolean setScaleX(float scaleX) { + return nSetScaleX(mNativeRenderNode, scaleX); } /** @@ -633,8 +637,8 @@ public class RenderNode { * @see View#setScaleY(float) * @see #getScaleY() */ - public void setScaleY(float scaleY) { - nSetScaleY(mNativeRenderNode, scaleY); + public boolean setScaleY(float scaleY) { + return nSetScaleY(mNativeRenderNode, scaleY); } /** @@ -654,8 +658,8 @@ public class RenderNode { * @see View#setPivotX(float) * @see #getPivotX() */ - public void setPivotX(float pivotX) { - nSetPivotX(mNativeRenderNode, pivotX); + public boolean setPivotX(float pivotX) { + return nSetPivotX(mNativeRenderNode, pivotX); } /** @@ -675,8 +679,8 @@ public class RenderNode { * @see View#setPivotY(float) * @see #getPivotY() */ - public void setPivotY(float pivotY) { - nSetPivotY(mNativeRenderNode, pivotY); + public boolean setPivotY(float pivotY) { + return nSetPivotY(mNativeRenderNode, pivotY); } /** @@ -702,8 +706,8 @@ public class RenderNode { * @see View#setCameraDistance(float) * @see #getCameraDistance() */ - public void setCameraDistance(float distance) { - nSetCameraDistance(mNativeRenderNode, distance); + public boolean setCameraDistance(float distance) { + return nSetCameraDistance(mNativeRenderNode, distance); } /** @@ -723,8 +727,8 @@ public class RenderNode { * @see View#setLeft(int) * @see #getLeft() */ - public void setLeft(int left) { - nSetLeft(mNativeRenderNode, left); + public boolean setLeft(int left) { + return nSetLeft(mNativeRenderNode, left); } /** @@ -744,8 +748,8 @@ public class RenderNode { * @see View#setTop(int) * @see #getTop() */ - public void setTop(int top) { - nSetTop(mNativeRenderNode, top); + public boolean setTop(int top) { + return nSetTop(mNativeRenderNode, top); } /** @@ -765,8 +769,8 @@ public class RenderNode { * @see View#setRight(int) * @see #getRight() */ - public void setRight(int right) { - nSetRight(mNativeRenderNode, right); + public boolean setRight(int right) { + return nSetRight(mNativeRenderNode, right); } /** @@ -786,8 +790,8 @@ public class RenderNode { * @see View#setBottom(int) * @see #getBottom() */ - public void setBottom(int bottom) { - nSetBottom(mNativeRenderNode, bottom); + public boolean setBottom(int bottom) { + return nSetBottom(mNativeRenderNode, bottom); } /** @@ -812,8 +816,8 @@ public class RenderNode { * @see View#setRight(int) * @see View#setBottom(int) */ - public void setLeftTopRightBottom(int left, int top, int right, int bottom) { - nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); + public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); } /** @@ -824,8 +828,8 @@ public class RenderNode { * * @see View#offsetLeftAndRight(int) */ - public void offsetLeftAndRight(float offset) { - nOffsetLeftAndRight(mNativeRenderNode, offset); + public boolean offsetLeftAndRight(float offset) { + return nOffsetLeftAndRight(mNativeRenderNode, offset); } /** @@ -836,8 +840,8 @@ public class RenderNode { * * @see View#offsetTopAndBottom(int) */ - public void offsetTopAndBottom(float offset) { - nOffsetTopAndBottom(mNativeRenderNode, offset); + public boolean offsetTopAndBottom(float offset) { + return nOffsetTopAndBottom(mNativeRenderNode, offset); } /** @@ -890,42 +894,43 @@ public class RenderNode { // Properties - private static native void nOffsetTopAndBottom(long renderNode, float offset); - private static native void nOffsetLeftAndRight(long renderNode, float offset); - private static native void nSetLeftTopRightBottom(long renderNode, int left, int top, + private static native boolean nOffsetTopAndBottom(long renderNode, float offset); + private static native boolean nOffsetLeftAndRight(long renderNode, float offset); + private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top, int right, int bottom); - private static native void nSetBottom(long renderNode, int bottom); - private static native void nSetRight(long renderNode, int right); - private static native void nSetTop(long renderNode, int top); - private static native void nSetLeft(long renderNode, int left); - private static native void nSetCameraDistance(long renderNode, float distance); - private static native void nSetPivotY(long renderNode, float pivotY); - private static native void nSetPivotX(long renderNode, float pivotX); - private static native void nSetCaching(long renderNode, boolean caching); - private static native void nSetClipToBounds(long renderNode, boolean clipToBounds); - private static native void nSetProjectBackwards(long renderNode, boolean shouldProject); - private static native void nSetProjectionReceiver(long renderNode, boolean shouldRecieve); - private static native void nSetOutlineRoundRect(long renderNode, int left, int top, + private static native boolean nSetBottom(long renderNode, int bottom); + private static native boolean nSetRight(long renderNode, int right); + private static native boolean nSetTop(long renderNode, int top); + private static native boolean nSetLeft(long renderNode, int left); + private static native boolean nSetCameraDistance(long renderNode, float distance); + private static native boolean nSetPivotY(long renderNode, float pivotY); + private static native boolean nSetPivotX(long renderNode, float pivotX); + private static native boolean nSetLayerType(long renderNode, int layerType); + private static native boolean nSetLayerPaint(long renderNode, long paint); + private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds); + private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); + private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); + private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, int right, int bottom, float radius); - private static native void nSetOutlineConvexPath(long renderNode, long nativePath); - private static native void nSetOutlineEmpty(long renderNode); - private static native void nSetClipToOutline(long renderNode, boolean clipToOutline); - private static native void nSetRevealClip(long renderNode, + private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath); + private static native boolean nSetOutlineEmpty(long renderNode); + private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); + private static native boolean nSetRevealClip(long renderNode, boolean shouldClip, boolean inverseClip, float x, float y, float radius); - private static native void nSetAlpha(long renderNode, float alpha); - private static native void nSetHasOverlappingRendering(long renderNode, + private static native boolean nSetAlpha(long renderNode, float alpha); + private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); - private static native void nSetElevation(long renderNode, float lift); - private static native void nSetTranslationX(long renderNode, float translationX); - private static native void nSetTranslationY(long renderNode, float translationY); - private static native void nSetTranslationZ(long renderNode, float translationZ); - private static native void nSetRotation(long renderNode, float rotation); - private static native void nSetRotationX(long renderNode, float rotationX); - private static native void nSetRotationY(long renderNode, float rotationY); - private static native void nSetScaleX(long renderNode, float scaleX); - private static native void nSetScaleY(long renderNode, float scaleY); - private static native void nSetStaticMatrix(long renderNode, long nativeMatrix); - private static native void nSetAnimationMatrix(long renderNode, long animationMatrix); + private static native boolean nSetElevation(long renderNode, float lift); + private static native boolean nSetTranslationX(long renderNode, float translationX); + private static native boolean nSetTranslationY(long renderNode, float translationY); + private static native boolean nSetTranslationZ(long renderNode, float translationZ); + private static native boolean nSetRotation(long renderNode, float rotation); + private static native boolean nSetRotationX(long renderNode, float rotationX); + private static native boolean nSetRotationY(long renderNode, float rotationY); + private static native boolean nSetScaleX(long renderNode, float scaleX); + private static native boolean nSetScaleY(long renderNode, float scaleY); + private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix); + private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix); private static native boolean nHasOverlappingRendering(long renderNode); private static native boolean nGetClipToOutline(long renderNode); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c15ce44..94d8f70 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -38,11 +38,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, - boolean useIdentityTransform); + Rect sourceCrop, 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, - boolean useIdentityTransform); + Rect sourceCrop, int width, int height, int minLayer, int maxLayer, + boolean allLayers, boolean useIdentityTransform); private static native void nativeOpenTransaction(); private static native void nativeCloseTransaction(); @@ -78,8 +78,8 @@ public class SurfaceControl { IBinder displayToken); private static native int nativeGetActiveConfig(IBinder displayToken); private static native boolean nativeSetActiveConfig(IBinder displayToken, int id); - private static native void nativeBlankDisplay(IBinder displayToken); - private static native void nativeUnblankDisplay(IBinder displayToken); + private static native void nativeSetDisplayPowerMode( + IBinder displayToken, int mode); private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -209,6 +209,25 @@ public class SurfaceControl { */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; + /* Display power modes * / + + /** + * Display power mode off: used while blanking the screen. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_OFF = 0; + + /** + * Display power mode doze: used while putting the screen into low power mode. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_DOZE = 1; + + /** + * Display power mode normal: used while unblanking the screen. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_NORMAL = 2; /** @@ -439,6 +458,8 @@ public class SurfaceControl { public float xDpi; public float yDpi; public boolean secure; + public long appVsyncOffsetNanos; + public long presentationDeadlineNanos; public PhysicalDisplayInfo() { } @@ -460,7 +481,9 @@ public class SurfaceControl { && density == other.density && xDpi == other.xDpi && yDpi == other.yDpi - && secure == other.secure; + && secure == other.secure + && appVsyncOffsetNanos == other.appVsyncOffsetNanos + && presentationDeadlineNanos == other.presentationDeadlineNanos; } @Override @@ -476,6 +499,8 @@ public class SurfaceControl { xDpi = other.xDpi; yDpi = other.yDpi; secure = other.secure; + appVsyncOffsetNanos = other.appVsyncOffsetNanos; + presentationDeadlineNanos = other.presentationDeadlineNanos; } // For debugging purposes @@ -483,22 +508,16 @@ public class SurfaceControl { public String toString() { return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, " + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure - + "}"; - } - } - - public static void unblankDisplay(IBinder displayToken) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); + + ", appVsyncOffset " + appVsyncOffsetNanos + + ", bufferDeadline " + presentationDeadlineNanos + "}"; } - nativeUnblankDisplay(displayToken); } - public static void blankDisplay(IBinder displayToken) { + public static void setDisplayPowerMode(IBinder displayToken, int mode) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - nativeBlankDisplay(displayToken); + nativeSetDisplayPowerMode(displayToken, mode); } public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) { @@ -597,8 +616,8 @@ public class SurfaceControl { public static void screenshot(IBinder display, Surface consumer, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform) { - screenshot(display, consumer, width, height, minLayer, maxLayer, false, - useIdentityTransform); + screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer, + false, useIdentityTransform); } /** @@ -613,7 +632,7 @@ public class SurfaceControl { */ public static void screenshot(IBinder display, Surface consumer, int width, int height) { - screenshot(display, consumer, width, height, 0, 0, true, false); + screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false); } /** @@ -623,7 +642,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, false); + screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false); } /** @@ -634,6 +653,8 @@ public class SurfaceControl { * the versions that use a {@link Surface} instead, such as * {@link SurfaceControl#screenshot(IBinder, Surface)}. * + * @param sourceCrop The portion of the screen to capture into the Bitmap; + * caller may pass in 'new Rect()' if no cropping is desired. * @param width The desired width of the returned bitmap; the raw * screen will be scaled down to this size. * @param height The desired height of the returned bitmap; the raw @@ -649,13 +670,13 @@ public class SurfaceControl { * 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, - boolean useIdentityTransform) { + public static Bitmap screenshot(Rect sourceCrop, 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, - useIdentityTransform); + return nativeScreenshot(displayToken, sourceCrop, width, height, + minLayer, maxLayer, false, useIdentityTransform); } /** @@ -674,10 +695,10 @@ 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, false); + return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false); } - private static void screenshot(IBinder display, Surface consumer, + private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform) { if (display == null) { @@ -686,7 +707,7 @@ public class SurfaceControl { if (consumer == null) { throw new IllegalArgumentException("consumer must not be null"); } - nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers, - useIdentityTransform); + nativeScreenshot(display, consumer, sourceCrop, width, height, + minLayer, maxLayer, allLayers, useIdentityTransform); } } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 2a9f7d5..1f7eaa2 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -275,6 +275,11 @@ public class TextureView extends View { } } + @Override + public void setLayerPaint(Paint paint) { + setLayerType(/* ignored */ 0, paint); + } + /** * Always returns {@link #LAYER_TYPE_HARDWARE}. */ @@ -332,11 +337,6 @@ public class TextureView extends View { } } - @Override - boolean destroyLayer(boolean valid) { - return false; - } - /** * @hide */ diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 72b9d3e..bfab654 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -16,21 +16,29 @@ package android.view; +import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.graphics.drawable.Drawable; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.Trace; import android.util.Log; +import android.util.LongSparseArray; import android.util.TimeUtils; import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; /** * Hardware renderer that proxies the rendering to a render thread. Most calls @@ -54,13 +62,11 @@ import java.io.PrintWriter; public class ThreadedRenderer extends HardwareRenderer { private static final String LOGTAG = "ThreadedRenderer"; - private static final Rect NULL_RECT = new Rect(); - // Keep in sync with DrawFrameTask.h SYNC_* flags // Nothing interesting to report - private static final int SYNC_OK = 0x0; + private static final int SYNC_OK = 0; // Needs a ViewRoot invalidate - private static final int SYNC_INVALIDATE_REQUIRED = 0x1; + private static final int SYNC_INVALIDATE_REQUIRED = 1 << 0; private static final String[] VISUALIZERS = { PROFILE_PROPERTY_VISUALIZE_BARS, @@ -73,15 +79,14 @@ public class ThreadedRenderer extends HardwareRenderer { private Choreographer mChoreographer; private boolean mProfilingEnabled; - ThreadedRenderer(boolean translucent) { - // Temporarily disabled - //AtlasInitializer.sInstance.init(); - + ThreadedRenderer(Context context, boolean translucent) { long rootNodePtr = nCreateRootRenderNode(); mRootNode = RenderNode.adopt(rootNodePtr); mRootNode.setClipToBounds(false); mNativeProxy = nCreateProxy(translucent, rootNodePtr); + AtlasInitializer.sInstance.init(context, mNativeProxy); + // Setup timing mChoreographer = Choreographer.getInstance(); nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos()); @@ -217,7 +222,7 @@ public class ThreadedRenderer extends HardwareRenderer { try { canvas.save(); callbacks.onHardwarePreDraw(canvas); - canvas.drawDisplayList(view.getDisplayList()); + canvas.drawRenderNode(view.getDisplayList()); callbacks.onHardwarePostDraw(canvas); canvas.restore(); } finally { @@ -229,7 +234,7 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { + void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { attachInfo.mIgnoreDirtyState = true; long frameTimeNanos = mChoreographer.getFrameTimeNanos(); attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS; @@ -247,26 +252,15 @@ public class ThreadedRenderer extends HardwareRenderer { attachInfo.mIgnoreDirtyState = false; - if (dirty == null) { - dirty = NULL_RECT; - } int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, - recordDuration, view.getResources().getDisplayMetrics().density, - dirty.left, dirty.top, dirty.right, dirty.bottom); + recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { attachInfo.mViewRootImpl.invalidate(); } } - @Override - void invokeFunctor(long functor, boolean waitForCompletion) { - nInvokeFunctor(mNativeProxy, functor, waitForCompletion); - } - - @Override - HardwareLayer createDisplayListLayer(int width, int height) { - long layer = nCreateDisplayListLayer(mNativeProxy, width, height); - return HardwareLayer.adoptDisplayListLayer(this, layer); + static void invokeFunctor(long functor, boolean waitForCompletion) { + nInvokeFunctor(functor, waitForCompletion); } @Override @@ -332,6 +326,14 @@ public class ThreadedRenderer extends HardwareRenderer { } } + static void startTrimMemory(int level) { + // TODO + } + + static void endTrimMemory() { + // TODO + } + private static class AtlasInitializer { static AtlasInitializer sInstance = new AtlasInitializer(); @@ -339,7 +341,7 @@ public class ThreadedRenderer extends HardwareRenderer { private AtlasInitializer() {} - synchronized void init() { + synchronized void init(Context context, long renderProxy) { if (mInitialized) return; IBinder binder = ServiceManager.getService("assetatlas"); if (binder == null) return; @@ -351,7 +353,9 @@ public class ThreadedRenderer extends HardwareRenderer { if (buffer != null) { long[] map = atlas.getMap(); if (map != null) { - nSetAtlas(buffer, map); + // TODO Remove after fixing b/15425820 + validateMap(context, map); + nSetAtlas(renderProxy, buffer, map); mInitialized = true; } // If IAssetAtlas is not the same class as the IBinder @@ -366,9 +370,35 @@ public class ThreadedRenderer extends HardwareRenderer { Log.w(LOG_TAG, "Could not acquire atlas", e); } } + + private static void validateMap(Context context, long[] map) { + Log.d("Atlas", "Validating map..."); + HashSet<Long> preloadedPointers = new HashSet<Long>(); + + // We only care about drawables that hold bitmaps + final Resources resources = context.getResources(); + final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); + + final int count = drawables.size(); + for (int i = 0; i < count; i++) { + final Bitmap bitmap = drawables.valueAt(i).getBitmap(); + if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) { + preloadedPointers.add(bitmap.mNativeBitmap); + } + } + + for (int i = 0; i < map.length; i += 4) { + if (!preloadedPointers.contains(map[i])) { + Log.w("Atlas", String.format("Pointer 0x%X, not in getPreloadedDrawables?", map[i])); + map[i] = 0; + } + } + } } - private static native void nSetAtlas(GraphicBuffer buffer, long[] map); + static native void setupShadersDiskCache(String cacheFile); + + private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map); private static native long nCreateRootRenderNode(); private static native long nCreateProxy(boolean translucent, long rootRenderNode); @@ -384,12 +414,11 @@ public class ThreadedRenderer extends HardwareRenderer { float lightX, float lightY, float lightZ, float lightRadius); private static native void nSetOpaque(long nativeProxy, boolean opaque); private static native int nSyncAndDrawFrame(long nativeProxy, - long frameTimeNanos, long recordDuration, float density, - int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); + long frameTimeNanos, long recordDuration, float density); private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); private static native void nDestroyCanvasAndSurface(long nativeProxy); - private static native void nInvokeFunctor(long nativeProxy, long functor, boolean waitForCompletion); + private static native void nInvokeFunctor(long functor, boolean waitForCompletion); private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height); private static native long nCreateTextureLayer(long nativeProxy); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7c70ee4..829e089 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ClipData; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -90,6 +91,7 @@ import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; import com.android.internal.view.menu.MenuBuilder; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -431,7 +433,7 @@ import java.util.concurrent.atomic.AtomicInteger; * child. The child must use this size, and guarantee that all of its * descendants will fit within this size. * <li>AT_MOST: This is used by the parent to impose a maximum size on the - * child. The child must gurantee that it and all of its descendants will fit + * child. The child must guarantee that it and all of its descendants will fit * within this size. * </ul> * </p> @@ -670,7 +672,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack * @attr ref android.R.styleable#View_stateListAnimator - * @attr ref android.R.styleable#View_viewName + * @attr ref android.R.styleable#View_transitionName * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag * @attr ref android.R.styleable#View_textAlignment @@ -2775,6 +2777,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @hide * + * Whether Recents is visible or not. + */ + public static final int RECENT_APPS_VISIBLE = 0x00004000; + + /** + * @hide + * * Makes system ui transparent. */ public static final int SYSTEM_UI_TRANSPARENT = 0x00008000; @@ -2782,7 +2791,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @hide */ - public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00007FFF; + public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FFF; /** * These are the system UI flags that can be cleared by events outside @@ -3179,20 +3188,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_") private Drawable mBackground; + private ColorStateList mBackgroundTint = null; + private PorterDuff.Mode mBackgroundTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasBackgroundTint = false; /** - * Display list used for backgrounds. + * RenderNode 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 + * of the background drawable. It is cleared on temporary detach, and reset * on cleanup. */ - private RenderNode mBackgroundDisplayList; + private RenderNode mBackgroundRenderNode; private int mBackgroundResource; private boolean mBackgroundSizeChanged; - private String mViewName; + private String mTransitionName; static class ListenerInfo { /** @@ -3465,8 +3477,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }) int mLayerType = LAYER_TYPE_NONE; Paint mLayerPaint; - Rect mLocalDirtyRect; - private HardwareLayer mHardwareLayer; /** * Set to true when drawing cache is enabled and cannot be created. @@ -3884,7 +3894,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (fadingEdge != FADING_EDGE_NONE) { viewFlagValues |= fadingEdge; viewFlagMasks |= FADING_EDGE_MASK; - initializeFadingEdge(a); + initializeFadingEdgeInternal(a); } break; case R.styleable.View_scrollbarStyle: @@ -4006,8 +4016,8 @@ 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_viewName: - setViewName(a.getString(attr)); + case R.styleable.View_transitionName: + setTransitionName(a.getString(attr)); break; case R.styleable.View_nestedScrollingEnabled: setNestedScrollingEnabled(a.getBoolean(attr, false)); @@ -4016,6 +4026,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, a.getResourceId(attr, 0))); break; + case R.styleable.View_backgroundTint: + // This will get applied later during setBackground(). + mBackgroundTint = a.getColorStateList(R.styleable.View_backgroundTint); + mHasBackgroundTint = true; + break; + case R.styleable.View_backgroundTintMode: + // This will get applied later during setBackground(). + mBackgroundTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.View_backgroundTintMode, -1), mBackgroundTintMode); + break; } } @@ -4088,7 +4108,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (initializeScrollbars) { - initializeScrollbars(a); + initializeScrollbarsInternal(a); } a.recycle(); @@ -4216,6 +4236,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param a the styled attributes set to initialize the fading edges from */ protected void initializeFadingEdge(TypedArray a) { + // This method probably shouldn't have been included in the SDK to begin with. + // It relies on 'a' having been initialized using an attribute filter array that is + // not publicly available to the SDK. The old method has been renamed + // to initializeFadingEdgeInternal and hidden for framework use only; + // this one initializes using defaults to make it safe to call for apps. + + TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View); + + initializeFadingEdgeInternal(arr); + + arr.recycle(); + } + + /** + * <p> + * Initializes the fading edges from a given set of styled attributes. This + * method should be called by subclasses that need fading edges and when an + * instance of these subclasses is created programmatically rather than + * being inflated from XML. This method is automatically called when the XML + * is inflated. + * </p> + * + * @param a the styled attributes set to initialize the fading edges from + * @hide This is the real method; the public one is shimmed to be safe to call from apps. + */ + protected void initializeFadingEdgeInternal(TypedArray a) { initScrollCache(); mScrollCache.fadingEdgeLength = a.getDimensionPixelSize( @@ -4330,6 +4376,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param a the styled attributes set to initialize the scrollbars from */ protected void initializeScrollbars(TypedArray a) { + // It's not safe to use this method from apps. The parameter 'a' must have been obtained + // using the View filter array which is not available to the SDK. As such, internal + // framework usage now uses initializeScrollbarsInternal and we grab a default + // TypedArray with the right filter instead here. + TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View); + + initializeScrollbarsInternal(arr); + + // We ignored the method parameter. Recycle the one we actually did use. + arr.recycle(); + } + + /** + * <p> + * Initializes the scrollbars from a given set of styled attributes. This + * method should be called by subclasses that need scrollbars and when an + * instance of these subclasses is created programmatically rather than + * being inflated from XML. This method is automatically called when the XML + * is inflated. + * </p> + * + * @param a the styled attributes set to initialize the scrollbars from + * @hide + */ + protected void initializeScrollbarsInternal(TypedArray a) { initScrollCache(); final ScrollabilityCache scrollabilityCache = mScrollCache; @@ -4789,14 +4860,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param v previous or the next focus holder, or null if none */ private void manageFocusHotspot(boolean focused, View v) { - if (mBackground == null) { - return; - } - final Rect r = new Rect(); - if (!focused && v != null) { + if (!focused && v != null && mAttachInfo != null) { v.getBoundsOnScreen(r); - final int[] location = new int[2]; + final int[] location = mAttachInfo.mTmpLocation; getLocationOnScreen(location); r.offset(-location[0], -location[1]); } else { @@ -4805,7 +4872,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final float x = r.exactCenterX(); final float y = r.exactCenterY(); - mBackground.setHotspot(x, y); + drawableHotspotChanged(x, y); } /** @@ -5377,8 +5444,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Gets the location of this view in screen coordintates. * * @param outRect The output location + * @hide */ - void getBoundsOnScreen(Rect outRect) { + public void getBoundsOnScreen(Rect outRect) { if (mAttachInfo == null) { return; } @@ -6100,6 +6168,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected boolean fitSystemWindows(Rect insets) { if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { + if (insets == null) { + // Null insets by definition have already been consumed. + // This call cannot apply insets since there are none to apply, + // so return false. + return false; + } // 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. @@ -6761,7 +6835,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private void setPressed(boolean pressed, float x, float y) { if (pressed) { - setHotspot(x, y); + drawableHotspotChanged(x, y); } setPressed(pressed); @@ -7648,7 +7722,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 */ @@ -9100,8 +9174,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away - setHotspot(x, y); - setPressed(true); + setPressed(true, x, y); checkForLongClick(0); } break; @@ -9113,7 +9186,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; case MotionEvent.ACTION_MOVE: - setHotspot(x, y); + drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { @@ -9135,12 +9208,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } - private void setHotspot(float x, float y) { - if (mBackground != null) { - mBackground.setHotspot(x, y); - } - } - /** * @hide */ @@ -9236,6 +9303,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Request unbuffered dispatch of the given stream of MotionEvents to this View. + * + * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input + * system not batch {@link MotionEvent}s but instead deliver them as soon as they're + * available. This method should only be called for touch events. + * + * <p class="note">This api is not intended for most applications. Buffered dispatch + * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent + * streams will not improve your input latency. Side effects include: increased latency, + * jittery scrolls and inability to take advantage of system resampling. Talk to your input + * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for + * you.</p> + */ + public final void requestUnbufferedDispatch(MotionEvent event) { + final int action = event.getAction(); + if (mAttachInfo == null + || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE + || !event.isTouchEvent()) { + return; + } + mAttachInfo.mUnbufferedDispatchRequested = true; + } + + /** * Set flags controlling behavior of this view. * * @param flags Constant indicating the value which should be set @@ -9646,7 +9737,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * The transform matrix of this view, which is calculated based on the current - * roation, scale, and pivot properties. + * rotation, scale, and pivot properties. * * @see #getRotation() * @see #getScaleX() @@ -12906,7 +12997,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stopNestedScroll(); destroyDrawingCache(); - destroyLayer(false); cleanupDraw(); mCurrentAnimation = null; @@ -12919,33 +13009,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * This method ensures the hardware renderer is in a valid state - * before executing the specified action. - * - * This method will attempt to set a valid state even if the window - * the renderer is attached to was destroyed. - * - * This method is not guaranteed to work. If the hardware renderer - * does not exist or cannot be put in a valid state, this method - * will not executed the specified action. - * - * The specified action is executed synchronously. - * - * @param action The action to execute after the renderer is in a valid state - * - * @return True if the specified Runnable was executed, false otherwise - * - * @hide - */ - public boolean executeHardwareAction(Runnable action) { - //noinspection SimplifiableIfStatement - if (mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { - return mAttachInfo.mHardwareRenderer.safelyRun(action); - } - return false; - } - void invalidateInheritedLayoutMode(int layoutModeOfRoot) { } @@ -13384,28 +13447,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE"); } - if (layerType == mLayerType) { + boolean typeChanged = mRenderNode.setLayerType(layerType); + + if (!typeChanged) { setLayerPaint(paint); return; } // Destroy any previous software drawing cache if needed - switch (mLayerType) { - case LAYER_TYPE_HARDWARE: - destroyLayer(false); - // fall through - non-accelerated views may use software layer mechanism instead - case LAYER_TYPE_SOFTWARE: - destroyDrawingCache(); - break; - default: - break; + if (mLayerType == LAYER_TYPE_SOFTWARE) { + destroyDrawingCache(); } mLayerType = layerType; - final boolean layerDisabled = mLayerType == LAYER_TYPE_NONE; + final boolean layerDisabled = (mLayerType == LAYER_TYPE_NONE); mLayerPaint = layerDisabled ? null : (paint == null ? new Paint() : paint); - mLocalDirtyRect = layerDisabled ? null : new Rect(); + mRenderNode.setLayerPaint(mLayerPaint); + // draw() behaves differently if we are on a layer, so we need to + // invalidate() here invalidateParentCaches(); invalidate(true); } @@ -13440,11 +13500,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (layerType != LAYER_TYPE_NONE) { mLayerPaint = paint == null ? new Paint() : paint; if (layerType == LAYER_TYPE_HARDWARE) { - HardwareLayer layer = getHardwareLayer(); - if (layer != null) { - layer.setLayerPaint(mLayerPaint); + if (mRenderNode.setLayerPaint(mLayerPaint)) { + invalidateViewProperty(false, false); } - invalidateViewProperty(false, false); } else { invalidate(); } @@ -13500,18 +13558,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, throw new IllegalStateException("This view must be attached to a window first"); } + if (getWidth() == 0 || getHeight() == 0) { + return; + } + switch (mLayerType) { case LAYER_TYPE_HARDWARE: - 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(); - } + // The only part of a hardware layer we can build in response to + // this call is to ensure the display list is up to date. + // The actual rendering of the display list into the layer must + // be done at playback time + updateDisplayListIfDirty(); break; case LAYER_TYPE_SOFTWARE: buildDrawingCache(true); @@ -13520,76 +13577,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * <p>Returns a hardware layer that can be used to draw this view again - * without executing its draw method.</p> + * If this View draws with a HardwareLayer, returns it. + * Otherwise returns null * - * @return A HardwareLayer ready to render, or null if an error occurred. + * TODO: Only TextureView uses this, can we eliminate it? */ HardwareLayer getHardwareLayer() { - if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null || - !mAttachInfo.mHardwareRenderer.isEnabled()) { - return null; - } - - final int width = mRight - mLeft; - final int height = mBottom - mTop; - - if (width == 0 || height == 0) { - return null; - } - - if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) { - if (mHardwareLayer == null) { - mHardwareLayer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( - 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 - // (for instance isOpaque() returns true, but the background is - // not opaque.) - computeOpaqueFlags(); - - 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); - RenderNode displayList = mHardwareLayer.startRecording(); - updateDisplayListIfDirty(displayList, true); - mHardwareLayer.endRecording(mLocalDirtyRect); - mLocalDirtyRect.setEmpty(); - } - - return mHardwareLayer; - } - - /** - * Destroys this View's hardware layer if possible. - * - * @return True if the layer was destroyed, false otherwise. - * - * @see #setLayerType(int, android.graphics.Paint) - * @see #LAYER_TYPE_HARDWARE - */ - boolean destroyLayer(boolean valid) { - if (mHardwareLayer != null) { - mHardwareLayer.destroy(); - mHardwareLayer = null; - - invalidate(true); - invalidateParentCaches(); - return true; - } - return false; + return null; } /** @@ -13605,7 +13599,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void destroyHardwareResources() { resetDisplayList(); - destroyLayer(true); } /** @@ -13700,20 +13693,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return !(mAttachInfo == null || mAttachInfo.mHardwareRenderer == 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). - * - * @param renderNode The previous version of this displayList, could be null. - * @param isLayer Whether the requester of the display list is a layer. If so, - * the view will avoid creating a layer inside the resulting display list. - * @return A new or reused DisplayList object. - */ - private void updateDisplayListIfDirty(@NonNull RenderNode renderNode, boolean isLayer) { - if (renderNode == null) { - throw new IllegalArgumentException("RenderNode must not be null"); - } + private void updateDisplayListIfDirty() { + final RenderNode renderNode = mRenderNode; if (!canHaveDisplayList()) { // can't populate RenderNode, don't try return; @@ -13721,11 +13702,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || !renderNode.isValid() - || (!isLayer && mRecreateDisplayList)) { + || (mRecreateDisplayList)) { // Don't need to recreate the display list, just need to tell our // children to restore/recreate theirs if (renderNode.isValid() - && !isLayer && !mRecreateDisplayList) { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; @@ -13734,13 +13714,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; // no work needed } - if (!isLayer) { - // If we got here, we're recreating it. Mark it as such to ensure that - // we copy in child display lists into ours in drawChild() - mRecreateDisplayList = true; - } + // If we got here, we're recreating it. Mark it as such to ensure that + // we copy in child display lists into ours in drawChild() + mRecreateDisplayList = true; - boolean caching = false; int width = mRight - mLeft; int height = mBottom - mTop; int layerType = getLayerType(); @@ -13748,34 +13725,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final HardwareCanvas canvas = renderNode.start(width, height); try { - if (!isLayer && layerType != LAYER_TYPE_NONE) { - if (layerType == LAYER_TYPE_HARDWARE) { - final HardwareLayer layer = getHardwareLayer(); - if (layer != null && layer.isValid()) { - canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint); - } else { - canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint, - Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | - Canvas.CLIP_TO_LAYER_SAVE_FLAG); - } - caching = true; - } else { - buildDrawingCache(true); - Bitmap cache = getDrawingCache(true); - if (cache != null) { - canvas.drawBitmap(cache, 0, 0, mLayerPaint); - caching = true; - } + final HardwareLayer layer = getHardwareLayer(); + if (layer != null && layer.isValid()) { + canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint); + } else if (layerType == LAYER_TYPE_SOFTWARE) { + buildDrawingCache(true); + Bitmap cache = getDrawingCache(true); + if (cache != null) { + canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } else { - computeScroll(); canvas.translate(-mScrollX, -mScrollY); - if (!isLayer) { - mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; - mPrivateFlags &= ~PFLAG_DIRTY_MASK; - } + mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { @@ -13789,14 +13753,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } finally { renderNode.end(canvas); - renderNode.setCaching(caching); - if (isLayer) { - renderNode.setLeftTopRightBottom(0, 0, width, height); - } else { - setDisplayListProperties(renderNode); - } + setDisplayListProperties(renderNode); } - } else if (!isLayer) { + } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } @@ -13811,7 +13770,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public RenderNode getDisplayList() { - updateDisplayListIfDirty(mRenderNode, false); + updateDisplayListIfDirty(); return mRenderNode; } @@ -13820,8 +13779,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mRenderNode.destroyDisplayListData(); } - if (mBackgroundDisplayList != null && mBackgroundDisplayList.isValid()) { - mBackgroundDisplayList.destroyDisplayListData(); + if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) { + mBackgroundRenderNode.destroyDisplayListData(); } } @@ -14305,6 +14264,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True if the view is attached to a window and the window is * hardware accelerated; false in any other case. */ + @ViewDebug.ExportedProperty(category = "drawing") public boolean isHardwareAccelerated() { return mAttachInfo != null && mAttachInfo.mHardwareAccelerated; } @@ -14535,7 +14495,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_INVALIDATED; } - RenderNode displayList = null; + RenderNode renderNode = null; Bitmap cache = null; boolean hasDisplayList = false; if (caching) { @@ -14570,12 +14530,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } useDisplayListProperties &= hasDisplayList; if (useDisplayListProperties) { - displayList = getDisplayList(); - if (!displayList.isValid()) { + renderNode = getDisplayList(); + if (!renderNode.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not // try to use it again. - displayList = null; + renderNode = null; hasDisplayList = false; useDisplayListProperties = false; } @@ -14629,7 +14589,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (transformToApply != null) { if (concatMatrix) { if (useDisplayListProperties) { - displayList.setAnimationMatrix(transformToApply.getMatrix()); + renderNode.setAnimationMatrix(transformToApply.getMatrix()); } else { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. @@ -14672,7 +14632,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; } if (useDisplayListProperties) { - displayList.setAlpha(alpha * getAlpha() * getTransitionAlpha()); + renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha()); } else if (layerType == LAYER_TYPE_NONE) { final int scrollX = hasDisplayList ? 0 : sx; final int scrollY = hasDisplayList ? 0 : sy; @@ -14704,12 +14664,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (!useDisplayListProperties && hasDisplayList) { - displayList = getDisplayList(); - if (!displayList.isValid()) { + renderNode = getDisplayList(); + if (!renderNode.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not // try to use it again. - displayList = null; + renderNode = null; hasDisplayList = false; } } @@ -14742,7 +14702,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags); + ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags); } } } else if (cache != null) { @@ -15013,12 +14973,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Attempt to use a display list if requested. if (canvas.isHardwareAccelerated() && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { - mBackgroundDisplayList = getDrawableDisplayList(background, mBackgroundDisplayList); + mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); - final RenderNode displayList = mBackgroundDisplayList; + final RenderNode displayList = mBackgroundRenderNode; if (displayList != null && displayList.isValid()) { setBackgroundDisplayListProperties(displayList); - ((HardwareCanvas) canvas).drawDisplayList(displayList); + ((HardwareCanvas) canvas).drawRenderNode(displayList); return; } } @@ -15049,30 +15009,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * specified Drawable. * * @param drawable Drawable for which to create a display list - * @param displayList Existing display list, or {@code null} + * @param renderNode Existing RenderNode, or {@code null} * @return A valid display list for the specified drawable */ - private RenderNode getDrawableDisplayList(Drawable drawable, RenderNode displayList) { - if (displayList == null) { - displayList = RenderNode.create(drawable.getClass().getName()); + private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) { + if (renderNode == null) { + renderNode = RenderNode.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); + final HardwareCanvas canvas = renderNode.start(width, height); try { drawable.draw(canvas); } finally { - displayList.end(canvas); + renderNode.end(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; + renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom); + renderNode.setProjectBackwards(drawable.isProjected()); + renderNode.setProjectionReceiver(true); + renderNode.setClipToBounds(false); + return renderNode; } /** @@ -15582,6 +15542,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * This function is called whenever the drawable hotspot changes. + * <p> + * Be sure to call through to the superclass when overriding this function. + * + * @param x hotspot x coordinate + * @param y hotspot y coordinate + */ + public void drawableHotspotChanged(float x, float y) { + if (mBackground != null) { + mBackground.setHotspot(x, y); + } + } + + /** * Call this to force a view to update its drawable state. This will cause * drawableStateChanged to be called on this view. Views that are interested * in the new state should call getDrawableState. @@ -15764,7 +15738,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } - Drawable d= null; + Drawable d = null; if (resid != 0) { d = mContext.getDrawable(resid); } @@ -15840,8 +15814,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or // if it has a different minimum size, we should layout again - if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || - mBackground.getMinimumWidth() != background.getMinimumWidth()) { + if (mBackground == null + || mBackground.getMinimumHeight() != background.getMinimumHeight() + || mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true; } @@ -15852,6 +15827,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, background.setVisible(getVisibility() == VISIBLE, false); mBackground = background; + applyBackgroundTint(); + if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND; @@ -15907,6 +15884,88 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Applies a tint to the background drawable. + * <p> + * Subsequent calls to {@link #setBackground(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#View_backgroundTint + * @attr ref android.R.styleable#View_backgroundTintMode + * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) + */ + private void setBackgroundTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mBackgroundTint = tint; + mBackgroundTintMode = tintMode; + mHasBackgroundTint = true; + + applyBackgroundTint(); + } + + /** + * Applies a tint to the background drawable. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setBackground(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#View_backgroundTint + * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + public void setBackgroundTint(@Nullable ColorStateList tint) { + setBackgroundTint(tint, mBackgroundTintMode); + } + + /** + * @return the tint applied to the background drawable + * @attr ref android.R.styleable#View_backgroundTint + * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getBackgroundTint() { + return mBackgroundTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setBackgroundTint(ColorStateList)}} to the background drawable. + * The default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#View_backgroundTintMode + * @see #setBackgroundTint(ColorStateList) + */ + public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + setBackgroundTint(mBackgroundTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the background drawable + * @attr ref android.R.styleable#View_backgroundTintMode + * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getBackgroundTintMode() { + return mBackgroundTintMode; + } + + private void applyBackgroundTint() { + if (mBackground != null && mHasBackgroundTint) { + mBackground = mBackground.mutate(); + mBackground.setTint(mBackgroundTint, mBackgroundTintMode); + } + } + + /** * Sets the padding. The view may add on the space required to display * the scrollbars, depending on the style and visibility of the scrollbars. * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop}, @@ -18881,15 +18940,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Adds all Views that have {@link #getViewName()} non-null to namedElements. - * @param namedElements Will contain all Views in the hierarchy having a view name. + * Adds all Views that have {@link #getTransitionName()} non-null to namedElements. + * @param namedElements Will contain all Views in the hierarchy having a transitionName. * @hide */ public void findNamedViews(Map<String, View> namedElements) { if (getVisibility() == VISIBLE) { - String viewName = getViewName(); - if (viewName != null) { - namedElements.put(viewName, this); + String transitionName = getTransitionName(); + if (transitionName != null) { + namedElements.put(transitionName, this); } } } @@ -19291,10 +19350,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the name of the View to be used to identify Views in Transitions. * Names should be unique in the View hierarchy. * - * @param viewName The name of the View to uniquely identify it for Transitions. + * @param transitionName The name of the View to uniquely identify it for Transitions. + */ + public final void setTransitionName(String transitionName) { + mTransitionName = transitionName; + } + + /** + * To be removed before L release. + * @hide */ - public final void setViewName(String viewName) { - mViewName = viewName; + public final void setViewName(String transitionName) { + setTransitionName(transitionName); } /** @@ -19306,11 +19373,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The name used of the View to be used to identify Views in Transitions or null * if no name has been given. */ - public String getViewName() { - return mViewName; + public String getTransitionName() { + return mTransitionName; } /** + * To be removed before L release. + * @hide + */ + public String getViewName() { return getTransitionName(); } + + /** * 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 @@ -19761,6 +19834,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mInTouchMode; /** + * Indicates whether the view has requested unbuffered input dispatching for the current + * event stream. + */ + boolean mUnbufferedDispatchRequested; + + /** * Indicates that ViewAncestor should trigger a global layout change * the next time it performs a traversal */ @@ -19825,6 +19904,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ final int[] mInvalidateChildLocation = new int[2]; + /** + * Global to the view hierarchy used as a temporary for dealng with + * computing absolute on-screen location. + */ + final int[] mTmpLocation = new int[2]; /** * Global to the view hierarchy used as a temporary for dealing with diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java index 3854f34..29e865f 100644 --- a/core/java/android/view/ViewAnimationUtils.java +++ b/core/java/android/view/ViewAnimationUtils.java @@ -23,7 +23,7 @@ import android.animation.ValueAnimator; * Defines common utilities for working with View's animations. * */ -public class ViewAnimationUtils { +public final class ViewAnimationUtils { private ViewAnimationUtils() {} /** * Returns a ValueAnimator which can animate a clipping circle. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index eef09ae..45ac073 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2326,13 +2326,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * 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 #getViewName()} is not null. + * is not null or if {@link #getTransitionName()} 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 || getViewName() != null; + return getBackground() != null || getTransitionName() != null; } } @@ -4389,7 +4389,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; - child.mLocalDirtyRect.union(dirty); } final int[] location = attachInfo.mInvalidateChildLocation; @@ -4500,7 +4499,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; - mLocalDirtyRect.union(dirty); } return mParent; @@ -4519,7 +4517,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; - mLocalDirtyRect.union(dirty); } return mParent; @@ -4530,6 +4527,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Native-calculated damage path + * Returns false if this path was unable to complete successfully. This means + * it hit a ViewParent it doesn't recognize and needs to fall back to calculating + * damage area + * @hide + */ + public boolean damageChildDeferred(View child) { + ViewParent parent = getParent(); + while (parent != null) { + if (parent instanceof ViewGroup) { + parent = parent.getParent(); + } else if (parent instanceof ViewRootImpl) { + ((ViewRootImpl) parent).invalidate(); + return true; + } else { + parent = null; + } + } + return false; + } + + /** * Quick invalidation method called by View.invalidateViewProperty. This doesn't set the * DRAWN flags and doesn't handle the Animation logic that the default invalidation methods * do; all we want to do here is schedule a traversal with the appropriate dirty rect. @@ -4537,14 +4556,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ public void damageChild(View child, final Rect dirty) { + if (damageChildDeferred(child)) { + return; + } + ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { - if (child.mLayerType != LAYER_TYPE_NONE) { - child.mLocalDirtyRect.union(dirty); - } - int left = child.mLeft; int top = child.mTop; if (!child.getMatrix().isIdentity()) { @@ -4592,9 +4611,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 || dirty.intersect(0, 0, mRight - mLeft, mBottom - mTop)) { - if (mLayerType != LAYER_TYPE_NONE) { - mLocalDirtyRect.union(dirty); - } if (!getMatrix().isIdentity()) { transformRect(dirty); } @@ -6913,13 +6929,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (getClass() != another.getClass()) { return 1; } - // First is above second. - if (mLocation.bottom - another.mLocation.top <= 0) { - return -1; - } - // First is below second. - if (mLocation.top - another.mLocation.bottom >= 0) { - return 1; + final int topDiference = mLocation.top - another.mLocation.top; + if (topDiference != 0) { + return topDiference; } // LTR if (mLayoutDirection == LAYOUT_DIRECTION_LTR) { @@ -6935,11 +6947,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return -rightDifference; } } - // Break tie by top. - final int topDiference = mLocation.top - another.mLocation.top; - if (topDiference != 0) { - return topDiference; - } // Break tie by height. final int heightDiference = mLocation.height() - another.mLocation.height(); if (heightDiference != 0) { diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index af1de78..3f72b4c 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -52,7 +52,7 @@ public class ViewPropertyAnimator { * The View whose properties are being animated by this class. This is set at * construction time. */ - private final View mView; + final View mView; /** * The duration of the underlying Animator object. By default, we don't set the duration @@ -253,10 +253,9 @@ public class ViewPropertyAnimator { ViewPropertyAnimator(View view) { mView = view; view.ensureTransformationInfo(); - // TODO: Disabled because of b/15287046 - //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { - // mRTBackend = new ViewPropertyAnimatorRT(view); - //} + if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { + mRTBackend = new ViewPropertyAnimatorRT(view); + } } /** @@ -434,6 +433,9 @@ public class ViewPropertyAnimator { } mPendingAnimations.clear(); mView.removeCallbacks(mAnimationStarter); + if (mRTBackend != null) { + mRTBackend.cancelAll(); + } } /** diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java index 709efdb..8b4277a 100644 --- a/core/java/android/view/ViewPropertyAnimatorRT.java +++ b/core/java/android/view/ViewPropertyAnimatorRT.java @@ -50,6 +50,15 @@ class ViewPropertyAnimatorRT { return true; } + public void cancelAll() { + for (int i = 0; i < mAnimators.length; i++) { + if (mAnimators[i] != null) { + mAnimators[i].cancel(); + mAnimators[i] = null; + } + } + } + private void doStartAnimation(ViewPropertyAnimator parent) { int size = parent.mPendingAnimations.size(); @@ -63,12 +72,22 @@ class ViewPropertyAnimatorRT { NameValuesHolder holder = parent.mPendingAnimations.get(i); int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant); - RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue); + final float finalValue = holder.mFromValue + holder.mDeltaValue; + RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue); animator.setStartDelay(startDelay); animator.setDuration(duration); animator.setInterpolator(interpolator); animator.setTarget(mView); animator.start(); + + // Alpha is a special snowflake that has the canonical value stored + // in mTransformationInfo instead of in RenderNode, so we need to update + // it with the final value here. + if (property == RenderNodeAnimator.ALPHA) { + // Don't need null check because ViewPropertyAnimator's + // ctor calls ensureTransformationInfo() + parent.mView.mTransformationInfo.mAlpha = finalValue; + } } parent.mPendingAnimations.clear(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index aa06d15..84e30c6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -41,6 +41,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.media.AudioManager; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -119,6 +120,9 @@ public final class ViewRootImpl implements ViewParent, private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering"; private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media"; + // property used by emulator to determine display shape + private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + /** * Maximum time we allow the user to roll the trackball enough to generate * a key event, before resetting the counters. @@ -181,7 +185,6 @@ public final class ViewRootImpl implements ViewParent, int mWidth; int mHeight; Rect mDirty; - final Rect mCurrentDirty = new Rect(); boolean mIsAnimating; CompatibilityInfo.Translator mTranslator; @@ -230,6 +233,7 @@ public final class ViewRootImpl implements ViewParent, QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; boolean mProcessInputEventsScheduled; + boolean mUnbufferedInputDispatch; String mPendingInputEventQueueLengthCounterName = "pq"; InputStage mFirstInputStage; @@ -325,6 +329,8 @@ public final class ViewRootImpl implements ViewParent, /** Set to true once doDie() has been called. */ private boolean mRemoved; + private boolean mIsEmulator; + /** * Consistency verifier for debugging purposes. */ @@ -631,27 +637,7 @@ public final class ViewRootImpl implements ViewParent, } void destroyHardwareLayers() { - if (mThread != Thread.currentThread()) { - if (mAttachInfo.mHardwareRenderer != null && - mAttachInfo.mHardwareRenderer.isEnabled()) { - HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); - } - } else { - destroyHardwareLayer(mView); - } - } - - 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)); - } - } + // TODO Implement } void flushHardwareLayerUpdates() { @@ -675,12 +661,17 @@ public final class ViewRootImpl implements ViewParent, } } - public boolean invokeFunctor(long functor, boolean waitForCompletion) { - if (mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) { - return false; - } - mAttachInfo.mHardwareRenderer.invokeFunctor(functor, waitForCompletion); - return true; + /** + * Schedules the functor for execution in either kModeProcess or + * kModeProcessNoContext, depending on whether or not there is an EGLContext. + * + * @param functor The native functor to invoke + * @param waitForCompletion If true, this will not return until the functor + * has invoked. If false, the functor may be invoked + * asynchronously. + */ + public void invokeFunctor(long functor, boolean waitForCompletion) { + ThreadedRenderer.invokeFunctor(functor, waitForCompletion); } private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { @@ -715,23 +706,12 @@ public final class ViewRootImpl implements ViewParent, if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) { - if (!HardwareRenderer.sUseRenderThread) { - // TODO: Delete - // Don't enable hardware acceleration when we're not on the main thread - if (!HardwareRenderer.sSystemRendererDisabled && - Looper.getMainLooper() != Looper.myLooper()) { - Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " - + "acceleration outside of the main thread, aborting"); - return; - } - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } final boolean translucent = attrs.format != PixelFormat.OPAQUE; - mAttachInfo.mHardwareRenderer = HardwareRenderer.create(translucent); + mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); mAttachInfo.mHardwareAccelerated = @@ -871,7 +851,9 @@ public final class ViewRootImpl implements ViewParent, void invalidate() { mDirty.set(0, 0, mWidth, mHeight); - scheduleTraversals(); + if (!mWillDrawSoon) { + scheduleTraversals(); + } } void invalidateWorld(View view) { @@ -1016,7 +998,9 @@ public final class ViewRootImpl implements ViewParent, mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); - scheduleConsumeBatchedInput(); + if (!mUnbufferedInputDispatch) { + scheduleConsumeBatchedInput(); + } notifyRendererOfFramePending(); } } @@ -1197,8 +1181,9 @@ public final class ViewRootImpl implements ViewParent, if ((mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0 && mDisplay.getDisplayId() == 0) { // we're fullscreen and not hosted in an ActivityView - isRound = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_windowIsRound); + isRound = (mIsEmulator && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false)) + || mContext.getResources().getBoolean( + com.android.internal.R.bool.config_windowIsRound); } host.dispatchApplyWindowInsets(new WindowInsets( mFitSystemWindowsInsets, isRound)); @@ -1527,54 +1512,55 @@ public final class ViewRootImpl implements ViewParent, disposeResizeBuffer(); - if (mResizeBuffer == null) { - mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( - mWidth, mHeight); - } - mResizeBuffer.prepare(mWidth, mHeight, false); - RenderNode layerRenderNode = mResizeBuffer.startRecording(); - HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight); - try { - 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); - } - - RenderNode renderNode = mView.mRenderNode; - if (renderNode != null && renderNode.isValid()) { - layerCanvas.drawDisplayList(renderNode, null, - RenderNode.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); - layerRenderNode.end(layerCanvas); - layerRenderNode.setCaching(true); - layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); - mTempRect.set(0, 0, mWidth, mHeight); - } finally { - mResizeBuffer.endRecording(mTempRect); - } - mAttachInfo.mHardwareRenderer.flushLayerUpdates(); +// TODO: Again.... +// if (mResizeBuffer == null) { +// mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( +// mWidth, mHeight); +// } +// mResizeBuffer.prepare(mWidth, mHeight, false); +// RenderNode layerRenderNode = mResizeBuffer.startRecording(); +// HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight); +// try { +// 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); +// } +// +// RenderNode renderNode = mView.mRenderNode; +// if (renderNode != null && renderNode.isValid()) { +// layerCanvas.drawDisplayList(renderNode, null, +// RenderNode.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); +// layerRenderNode.end(layerCanvas); +// layerRenderNode.setCaching(true); +// layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); +// mTempRect.set(0, 0, mWidth, mHeight); +// } finally { +// mResizeBuffer.endRecording(mTempRect); +// } +// mAttachInfo.mHardwareRenderer.flushLayerUpdates(); } mAttachInfo.mContentInsets.set(mPendingContentInsets); if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " @@ -2444,12 +2430,10 @@ public final class ViewRootImpl implements ViewParent, mHardwareYOffset = yoff; mResizeAlpha = resizeAlpha; - mCurrentDirty.set(dirty); dirty.setEmpty(); mBlockResizeBuffer = false; - attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, - animating ? null : mCurrentDirty); + attachInfo.mHardwareRenderer.draw(mView, attachInfo, this); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface @@ -2616,7 +2600,7 @@ public final class ViewRootImpl implements ViewParent, } final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); - final Rect bounds = mView.mAttachInfo.mTmpInvalRect; + final Rect bounds = mAttachInfo.mTmpInvalRect; if (provider == null) { host.getBoundsOnScreen(bounds); } else if (mAccessibilityFocusedVirtualView != null) { @@ -3898,6 +3882,18 @@ public final class ViewRootImpl implements ViewParent, } } + @Override + protected void onDeliverToNext(QueuedInputEvent q) { + if (mUnbufferedInputDispatch + && q.mEvent instanceof MotionEvent + && ((MotionEvent)q.mEvent).isTouchEvent() + && isTerminalInputEvent(q.mEvent)) { + mUnbufferedInputDispatch = false; + scheduleConsumeBatchedInput(); + } + super.onDeliverToNext(q); + } + private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; @@ -4010,10 +4006,15 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; - if (mView.dispatchPointerEvent(event)) { - return FINISH_HANDLED; + mAttachInfo.mUnbufferedDispatchRequested = false; + boolean handled = mView.dispatchPointerEvent(event); + if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { + mUnbufferedInputDispatch = true; + if (mConsumeBatchedInputScheduled) { + scheduleConsumeBatchedInputImmediately(); + } } - return FORWARD; + return handled ? FINISH_HANDLED : FORWARD; } private int processTrackballEvent(QueuedInputEvent q) { @@ -5278,6 +5279,8 @@ public final class ViewRootImpl implements ViewParent, writer.print(" mRemoved="); writer.println(mRemoved); writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled="); writer.println(mConsumeBatchedInputScheduled); + writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled="); + writer.println(mConsumeBatchedInputImmediatelyScheduled); writer.print(innerPrefix); writer.print("mPendingInputEventCount="); writer.println(mPendingInputEventCount); writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled="); @@ -5435,6 +5438,9 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200); } } + + // detect emulator + mIsEmulator = Build.HARDWARE.contains("goldfish"); } }); } @@ -5688,6 +5694,7 @@ public final class ViewRootImpl implements ViewParent, 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); @@ -5727,15 +5734,25 @@ public final class ViewRootImpl implements ViewParent, } } + void scheduleConsumeBatchedInputImmediately() { + if (!mConsumeBatchedInputImmediatelyScheduled) { + unscheduleConsumeBatchedInput(); + mConsumeBatchedInputImmediatelyScheduled = true; + mHandler.post(mConsumeBatchedInputImmediatelyRunnable); + } + } + void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if (mInputEventReceiver != null) { - if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) { + if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) + && frameTimeNanos != -1) { // If we consumed a batch here, we want to go ahead and schedule the // consumption of batched input events on the next frame. Otherwise, we would // wait until we have more input events pending and might get starved by other - // things occurring in the process. + // things occurring in the process. If the frame time is -1, however, then + // we're in a non-batching mode, so there's no need to schedule this. scheduleConsumeBatchedInput(); } } @@ -5763,7 +5780,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void onBatchedInputEventPending() { - scheduleConsumeBatchedInput(); + if (mUnbufferedInputDispatch) { + super.onBatchedInputEventPending(); + } else { + scheduleConsumeBatchedInput(); + } } @Override @@ -5784,6 +5805,16 @@ public final class ViewRootImpl implements ViewParent, new ConsumeBatchedInputRunnable(); boolean mConsumeBatchedInputScheduled; + final class ConsumeBatchedInputImmediatelyRunnable implements Runnable { + @Override + public void run() { + doConsumeBatchedInput(-1); + } + } + final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable = + new ConsumeBatchedInputImmediatelyRunnable(); + boolean mConsumeBatchedInputImmediatelyScheduled; + final class InvalidateOnAnimationRunnable implements Runnable { private boolean mPosted; private final ArrayList<View> mViews = new ArrayList<View>(); @@ -6180,7 +6211,7 @@ public final class ViewRootImpl implements ViewParent, mTempRect.offset(0, -mCurScrollY); mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); try { - mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate); + mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect); } catch (RemoteException re) { /* ignore */ } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index ecc4586..0120875 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1516,6 +1516,29 @@ public abstract class Window { public boolean getAllowExitTransitionOverlap() { return true; } /** + * Returns the duration, in milliseconds, of the window background fade + * when transitioning into or away from an Activity when called with an Activity Transition. + * <p>When executing the enter transition, the background starts transparent + * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is + * 300 milliseconds.</p> + * @return The duration of the window background fade to opaque during enter transition. + * @see #getEnterTransition() + */ + public long getTransitionBackgroundFadeDuration() { return 0; } + + /** + * Sets the duration, in milliseconds, of the window background fade + * when transitioning into or away from an Activity when called with an Activity Transition. + * <p>When executing the enter transition, the background starts transparent + * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is + * 300 milliseconds.</p> + * @param fadeDurationMillis The duration of the window background fade to or from opaque + * during enter transition. + * @see #setEnterTransition(android.transition.Transition) + */ + public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { } + + /** * @return the color of the status bar. */ public abstract int getStatusBarColor(); diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java index 7f89044..b721074 100644 --- a/core/java/android/view/WindowInfo.java +++ b/core/java/android/view/WindowInfo.java @@ -111,6 +111,7 @@ public class WindowInfo implements Parcelable { builder.append("type=").append(type); builder.append(", layer=").append(layer); builder.append(", token=").append(token); + builder.append(", bounds=").append(boundsInScreen); builder.append(", parent=").append(parentToken); builder.append(", focused=").append(focused); builder.append(", children=").append(childTokens); diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9b75595..1d2f1bf 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -102,7 +102,12 @@ public final class WindowInsets { if (mTempRect == null) { mTempRect = new Rect(); } - mTempRect.set(mSystemWindowInsets); + if (mSystemWindowInsets != null) { + mTempRect.set(mSystemWindowInsets); + } else { + // If there were no system window insets, this is just empty. + mTempRect.setEmpty(); + } return mTempRect; } diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index a92bf59..bf5c84e 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -167,5 +167,5 @@ public abstract class WindowManagerInternal { * Invalidate all visible windows. Then report back on the callback once all windows have * redrawn. */ - public abstract void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout); + public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout); } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 2b4677c..024600d 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -605,21 +605,21 @@ public interface WindowManagerPolicy { public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation); /** - * Return whether the given window should forcibly hide everything - * behind it. Typically returns true for the keyguard. + * Return whether the given window is forcibly hiding all windows except windows with + * FLAG_SHOW_WHEN_LOCKED set. Typically returns true for the keyguard. */ - public boolean doesForceHide(WindowManager.LayoutParams attrs); + public boolean isForceHiding(WindowManager.LayoutParams attrs); /** - * Return whether the given window can become one that passes doesForceHide() test. + * Return whether the given window can become one that passes isForceHiding() test. * Typically returns true for the StatusBar. */ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs); /** * Determine if a window that is behind one that is force hiding - * (as determined by {@link #doesForceHide}) should actually be hidden. + * (as determined by {@link #isForceHiding}) should actually be hidden. * For example, typically returns false for the status bar. Be careful * to return false for any window that you may hide yourself, since this * will conflict with what you set. @@ -830,13 +830,11 @@ public interface WindowManagerPolicy { * setting the window's frame, either here or in finishLayout(). * * @param win The window being positioned. - * @param attrs The LayoutParams of the window. * @param attached For sub-windows, the window it is attached to; this * window will already have had layoutWindow() called on it * so you can use its Rect. Otherwise null. */ - public void layoutWindowLw(WindowState win, - WindowManager.LayoutParams attrs, WindowState attached); + public void layoutWindowLw(WindowState win, WindowState attached); /** @@ -1156,6 +1154,12 @@ public interface WindowManagerPolicy { public void setLastInputMethodWindowLw(WindowState ime, WindowState target); /** + * Show the recents task list app. + * @hide + */ + public void showRecentApps(); + + /** * @return The current height of the input method window. */ public int getInputMethodWindowVisibleHeightLw(); diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java index c08f348..1c75f16 100644 --- a/core/java/android/view/animation/AccelerateInterpolator.java +++ b/core/java/android/view/animation/AccelerateInterpolator.java @@ -17,15 +17,18 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.util.AttributeSet; +import com.android.internal.R; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; /** - * An interpolator where the rate of change starts out slowly and + * An interpolator where the rate of change starts out slowly and * and then accelerates. * */ @@ -38,10 +41,10 @@ public class AccelerateInterpolator implements Interpolator, NativeInterpolatorF mFactor = 1.0f; mDoubleFactor = 2.0; } - + /** * Constructor - * + * * @param factor Degree to which the animation should be eased. Seting * factor to 1.0f produces a y=x^2 parabola. Increasing factor above * 1.0f exaggerates the ease-in effect (i.e., it starts even @@ -51,17 +54,26 @@ public class AccelerateInterpolator implements Interpolator, NativeInterpolatorF mFactor = factor; mDoubleFactor = 2 * mFactor; } - + public AccelerateInterpolator(Context context, AttributeSet attrs) { - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator); - - mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f); + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator); + } + + mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f); mDoubleFactor = 2 * mFactor; a.recycle(); } - + public float getInterpolation(float input) { if (mFactor == 1.0f) { return input * input; diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 1d1fa1e..af4e04f 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -20,6 +20,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.XmlResourceParser; import android.content.res.Resources.NotFoundException; import android.util.AttributeSet; @@ -143,7 +145,7 @@ public class AnimationUtils { */ public static LayoutAnimationController loadLayoutAnimation(Context context, int id) throws NotFoundException { - + XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); @@ -201,7 +203,7 @@ public class AnimationUtils { /** * Make an animation for objects becoming visible. Uses a slide and fade * effect. - * + * * @param c Context for loading resources * @param fromLeft is the object to be animated coming from the left * @return The new animation @@ -218,11 +220,11 @@ public class AnimationUtils { a.setStartTime(currentAnimationTimeMillis()); return a; } - + /** * Make an animation for objects becoming invisible. Uses a slide and fade * effect. - * + * * @param c Context for loading resources * @param toRight is the object to be animated exiting to the right * @return The new animation @@ -234,17 +236,17 @@ public class AnimationUtils { } else { a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left); } - + a.setInterpolator(new AccelerateInterpolator()); a.setStartTime(currentAnimationTimeMillis()); return a; } - + /** * Make an animation for objects becoming visible. Uses a slide up and fade * effect. - * + * * @param c Context for loading resources * @return The new animation */ @@ -255,10 +257,10 @@ public class AnimationUtils { a.setStartTime(currentAnimationTimeMillis()); return a; } - + /** * Loads an {@link Interpolator} object from a resource - * + * * @param context Application context used to access resources * @param id The resource id of the animation to load * @return The animation object reference by the specified id @@ -268,7 +270,7 @@ public class AnimationUtils { XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); - return createInterpolatorFromXml(context, parser); + return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); @@ -284,54 +286,84 @@ public class AnimationUtils { } } - - private static Interpolator createInterpolatorFromXml(Context c, XmlPullParser parser) + + /** + * Loads an {@link Interpolator} object from a resource + * + * @param res The resources + * @param id The resource id of the animation to load + * @return The interpolator object reference by the specified id + * @throws NotFoundException + * @hide + */ + public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException { + XmlResourceParser parser = null; + try { + parser = res.getAnimation(id); + return createInterpolatorFromXml(res, theme, parser); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) + parser.close(); + } + + } + + private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) throws XmlPullParserException, IOException { - + Interpolator interpolator = null; - + // Make sure we are on a start tag. int type; int depth = parser.getDepth(); - while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } AttributeSet attrs = Xml.asAttributeSet(parser); - - String name = parser.getName(); - - + + String name = parser.getName(); + if (name.equals("linearInterpolator")) { - interpolator = new LinearInterpolator(c, attrs); + interpolator = new LinearInterpolator(); } else if (name.equals("accelerateInterpolator")) { - interpolator = new AccelerateInterpolator(c, attrs); + interpolator = new AccelerateInterpolator(res, theme, attrs); } else if (name.equals("decelerateInterpolator")) { - interpolator = new DecelerateInterpolator(c, attrs); - } else if (name.equals("accelerateDecelerateInterpolator")) { - interpolator = new AccelerateDecelerateInterpolator(c, attrs); - } else if (name.equals("cycleInterpolator")) { - interpolator = new CycleInterpolator(c, attrs); + interpolator = new DecelerateInterpolator(res, theme, attrs); + } else if (name.equals("accelerateDecelerateInterpolator")) { + interpolator = new AccelerateDecelerateInterpolator(); + } else if (name.equals("cycleInterpolator")) { + interpolator = new CycleInterpolator(res, theme, attrs); } else if (name.equals("anticipateInterpolator")) { - interpolator = new AnticipateInterpolator(c, attrs); + interpolator = new AnticipateInterpolator(res, theme, attrs); } else if (name.equals("overshootInterpolator")) { - interpolator = new OvershootInterpolator(c, attrs); + interpolator = new OvershootInterpolator(res, theme, attrs); } else if (name.equals("anticipateOvershootInterpolator")) { - interpolator = new AnticipateOvershootInterpolator(c, attrs); + interpolator = new AnticipateOvershootInterpolator(res, theme, attrs); } else if (name.equals("bounceInterpolator")) { - interpolator = new BounceInterpolator(c, attrs); + interpolator = new BounceInterpolator(); } else if (name.equals("pathInterpolator")) { - interpolator = new PathInterpolator(c, attrs); + interpolator = new PathInterpolator(res, theme, attrs); } else { throw new RuntimeException("Unknown interpolator name: " + parser.getName()); } } - + return interpolator; } diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java index 83a8007..fe756bd 100644 --- a/core/java/android/view/animation/AnticipateInterpolator.java +++ b/core/java/android/view/animation/AnticipateInterpolator.java @@ -17,9 +17,12 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; +import com.android.internal.R; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; @@ -45,11 +48,20 @@ public class AnticipateInterpolator implements Interpolator, NativeInterpolatorF } public AnticipateInterpolator(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AnticipateInterpolator); + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public AnticipateInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.AnticipateInterpolator, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator); + } mTension = - a.getFloat(com.android.internal.R.styleable.AnticipateInterpolator_tension, 2.0f); + a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f); a.recycle(); } diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java index 1a8adfd..78e5acf 100644 --- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java +++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java @@ -17,6 +17,8 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.util.AttributeSet; @@ -62,7 +64,17 @@ public class AnticipateOvershootInterpolator implements Interpolator, NativeInte } public AnticipateOvershootInterpolator(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator); + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public AnticipateOvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator, 0, 0); + } else { + a = res.obtainAttributes(attrs, AnticipateOvershootInterpolator); + } mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) * a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f); diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java index d1ebf05..3114aa3 100644 --- a/core/java/android/view/animation/CycleInterpolator.java +++ b/core/java/android/view/animation/CycleInterpolator.java @@ -17,9 +17,12 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; +import com.android.internal.R; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; @@ -34,20 +37,29 @@ public class CycleInterpolator implements Interpolator, NativeInterpolatorFactor public CycleInterpolator(float cycles) { mCycles = cycles; } - + public CycleInterpolator(Context context, AttributeSet attrs) { - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CycleInterpolator); - - mCycles = a.getFloat(com.android.internal.R.styleable.CycleInterpolator_cycles, 1.0f); - + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public CycleInterpolator(Resources resources, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.CycleInterpolator, 0, 0); + } else { + a = resources.obtainAttributes(attrs, R.styleable.CycleInterpolator); + } + + mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f); + a.recycle(); } - + public float getInterpolation(float input) { return (float)(Math.sin(2 * mCycles * Math.PI * input)); } - + private float mCycles; /** @hide */ diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java index 0789a0e..674207c 100644 --- a/core/java/android/view/animation/DecelerateInterpolator.java +++ b/core/java/android/view/animation/DecelerateInterpolator.java @@ -17,15 +17,18 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; +import com.android.internal.R; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; /** - * An interpolator where the rate of change starts out quickly and + * An interpolator where the rate of change starts out quickly and * and then decelerates. * */ @@ -36,7 +39,7 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF /** * Constructor - * + * * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces * an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the * ease-out effect (i.e., it starts even faster and ends evens slower) @@ -44,16 +47,25 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF public DecelerateInterpolator(float factor) { mFactor = factor; } - + public DecelerateInterpolator(Context context, AttributeSet attrs) { - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.DecelerateInterpolator); - - mFactor = a.getFloat(com.android.internal.R.styleable.DecelerateInterpolator_factor, 1.0f); - + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.DecelerateInterpolator, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.DecelerateInterpolator); + } + + mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f); + a.recycle(); } - + public float getInterpolation(float input) { float result; if (mFactor == 1.0f) { @@ -63,7 +75,7 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF } return result; } - + private float mFactor = 1.0f; /** @hide */ diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java index a2466f1..d6c2808 100644 --- a/core/java/android/view/animation/OvershootInterpolator.java +++ b/core/java/android/view/animation/OvershootInterpolator.java @@ -17,9 +17,12 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.util.AttributeSet; +import com.android.internal.R; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; @@ -46,11 +49,20 @@ public class OvershootInterpolator implements Interpolator, NativeInterpolatorFa } public OvershootInterpolator(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.OvershootInterpolator); + this(context.getResources(), context.getTheme(), attrs); + } + + /** @hide */ + public OvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.OvershootInterpolator, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator); + } mTension = - a.getFloat(com.android.internal.R.styleable.OvershootInterpolator_tension, 2.0f); + a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f); a.recycle(); } diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java index a369509..945ecf0 100644 --- a/core/java/android/view/animation/PathInterpolator.java +++ b/core/java/android/view/animation/PathInterpolator.java @@ -16,11 +16,16 @@ package android.view.animation; import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Path; import android.util.AttributeSet; +import android.util.PathParser; import android.view.InflateException; +import com.android.internal.R; + /** * 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> @@ -81,35 +86,60 @@ public class PathInterpolator implements Interpolator { } 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."); - } + this(context.getResources(), context.getTheme(), attrs); + } - if (!hasX2) { - initQuad(x1, y1); + /** @hide */ + public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0); } 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 = res.obtainAttributes(attrs, R.styleable.PathInterpolator); } + parseInterpolatorFromTypeArray(a); a.recycle(); } + private void parseInterpolatorFromTypeArray(TypedArray a) { + // If there is pathData defined in the xml file, then the controls points + // will be all coming from pathData. + if (a.hasValue(R.styleable.PathInterpolator_pathData)) { + String pathData = a.getString(R.styleable.PathInterpolator_pathData); + Path path = PathParser.createPathFromPathData(pathData); + if (path == null) { + throw new InflateException("The path is null, which is created" + + " from " + pathData); + } + initPath(path); + } else { + if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) { + throw new InflateException("pathInterpolator requires the controlX1 attribute"); + } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) { + throw new InflateException("pathInterpolator requires the controlY1 attribute"); + } + float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0); + float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0); + + boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2); + boolean hasY2 = a.hasValue(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(R.styleable.PathInterpolator_controlX2, 0); + float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0); + initCubic(x1, y1, x2, y2); + } + } + } + private void initQuad(float controlX, float controlY) { Path path = new Path(); path.moveTo(0, 0); @@ -199,5 +229,4 @@ public class PathInterpolator implements Interpolator { 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 cccfa78..e1f40b7 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -195,6 +195,7 @@ public class BaseInputConnection implements InputConnection { public boolean commitText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "commitText " + text); replaceText(text, newCursorPosition, false); + mIMM.notifyUserAction(); sendCurrentText(); return true; } @@ -435,6 +436,7 @@ public class BaseInputConnection implements InputConnection { public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "setComposingText " + text); replaceText(text, newCursorPosition, true); + mIMM.notifyUserAction(); return true; } @@ -518,6 +520,7 @@ public class BaseInputConnection implements InputConnection { viewRootImpl.dispatchKeyFromIme(event); } } + mIMM.notifyUserAction(); return false; } @@ -601,10 +604,6 @@ 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); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f874eb7..ace8808 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -320,6 +320,25 @@ public final class InputMethodManager { int mCursorCandEnd; /** + * Represents an invalid action notification sequence number. {@link InputMethodManagerService} + * always issues a positive integer for action notification sequence numbers. Thus -1 is + * guaranteed to be different from any valid sequence number. + */ + private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1; + /** + * The next sequence number that is to be sent to {@link InputMethodManagerService} via + * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed. + */ + private int mNextUserActionNotificationSequenceNumber = + NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; + + /** + * The last sequence number that is already sent to {@link InputMethodManagerService}. + */ + private int mLastSentUserActionNotificationSequenceNumber = + NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; + + /** * The instance that has previously been sent to the input method. */ private CursorAnchorInfo mCursorAnchorInfo = null; @@ -363,7 +382,8 @@ public final class InputMethodManager { static final int MSG_SEND_INPUT_EVENT = 5; static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; - static final int SET_CURSOR_ANCHOR_MONITOR_MODE = 8; + static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8; + static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9; class H extends Handler { H(Looper looper) { @@ -494,7 +514,7 @@ public final class InputMethodManager { finishedInputEvent(msg.arg1, false, false); return; } - case SET_CURSOR_ANCHOR_MONITOR_MODE: { + case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: { synchronized (mH) { mCursorAnchorMonitorMode = msg.arg1; // Clear the cache. @@ -503,6 +523,11 @@ public final class InputMethodManager { } return; } + case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: { + synchronized (mH) { + mNextUserActionNotificationSequenceNumber = msg.arg1; + } + } } } } @@ -570,7 +595,13 @@ public final class InputMethodManager { @Override public void setCursorAnchorMonitorMode(int monitorMode) { - mH.sendMessage(mH.obtainMessage(SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0)); + mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0)); + } + + @Override + public void setUserActionNotificationSequenceNumber(int sequenceNumber) { + mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER, + sequenceNumber, 0)); } }; @@ -1214,6 +1245,8 @@ public final class InputMethodManager { mBindSequence = res.sequence; mCurMethod = res.method; mCurId = res.id; + mNextUserActionNotificationSequenceNumber = + res.userActionNotificationSequenceNumber; } else { if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); @@ -1913,13 +1946,33 @@ public final class InputMethodManager { } /** - * Notify the current IME commits text + * Notify that a user took some action with this input method. * @hide */ - public void notifyTextCommitted() { + public void notifyUserAction() { synchronized (mH) { + if (mLastSentUserActionNotificationSequenceNumber == + mNextUserActionNotificationSequenceNumber) { + if (DEBUG) { + Log.w(TAG, "Ignoring notifyUserAction as it has already been sent." + + " mLastSentUserActionNotificationSequenceNumber: " + + mLastSentUserActionNotificationSequenceNumber + + " mNextUserActionNotificationSequenceNumber: " + + mNextUserActionNotificationSequenceNumber); + } + return; + } try { - mService.notifyTextCommitted(); + if (DEBUG) { + Log.w(TAG, "notifyUserAction: " + + " mLastSentUserActionNotificationSequenceNumber: " + + mLastSentUserActionNotificationSequenceNumber + + " mNextUserActionNotificationSequenceNumber: " + + mNextUserActionNotificationSequenceNumber); + } + mService.notifyUserAction(mNextUserActionNotificationSequenceNumber); + mLastSentUserActionNotificationSequenceNumber = + mNextUserActionNotificationSequenceNumber; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } @@ -2103,6 +2156,10 @@ public final class InputMethodManager { + " mCursorSelEnd=" + mCursorSelEnd + " mCursorCandStart=" + mCursorCandStart + " mCursorCandEnd=" + mCursorCandEnd); + p.println(" mNextUserActionNotificationSequenceNumber=" + + mNextUserActionNotificationSequenceNumber + + " mLastSentUserActionNotificationSequenceNumber=" + + mLastSentUserActionNotificationSequenceNumber); } /** diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 628da3c..84f395a 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -427,8 +427,12 @@ public class SpellCheckerSession { @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - mHandler.sendMessage( - Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + synchronized (this) { + if (mHandler != null) { + mHandler.sendMessage(Message.obtain(mHandler, + MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + } + } } } diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 2b75d83..321d9d3 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -17,6 +17,7 @@ package android.webkit; import android.net.WebAddress; +import android.webkit.ValueCallback; /** * Manages the cookies used by an application's {@link WebView} instances. @@ -70,10 +71,9 @@ public class CookieManager { /** * Sets a cookie for the given URL. Any existing cookie with the same host, * path and name will be replaced with the new cookie. The cookie being set - * must not have expired and must not be a session cookie, otherwise it - * will be ignored. + * will be ignored if it is expired. * - * @param url the URL for which the cookie is set + * @param url the URL for which the cookie is to be set * @param value the cookie as a string, using the format of the 'Set-Cookie' * HTTP response header */ @@ -82,6 +82,29 @@ public class CookieManager { } /** + * Sets a cookie for the given URL. Any existing cookie with the same host, + * path and name will be replaced with the new cookie. The cookie being set + * will be ignored if it is expired. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether the cookie was set successfully. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether it succeeded, and in this case it is safe to call the method from a + * thread without a Looper. + * + * @param url the URL for which the cookie is to be set + * @param value the cookie as a string, using the format of the 'Set-Cookie' + * HTTP response header + * @param callback a callback to be executed when the cookie has been set + */ + public void setCookie(String url, String value, ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Gets the cookies for the given URL. * * @param url the URL for which the cookies are requested @@ -121,19 +144,57 @@ public class CookieManager { /** * Removes all session cookies, which are cookies without an expiration * date. + * @deprecated use {@link #removeSessionCookies(ValueCallback)} instead. */ public void removeSessionCookie() { throw new MustOverrideException(); } /** + * Removes all session cookies, which are cookies without an expiration + * date. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether any cookies were removed. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether any cookie were removed, and in this case it is safe to call the + * method from a thread without a Looper. + * @param callback a callback which is executed when the session cookies have been removed + */ + public void removeSessionCookies(ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Removes all cookies. + * @deprecated Use {@link #removeAllCookies(ValueCallback)} instead. */ + @Deprecated public void removeAllCookie() { throw new MustOverrideException(); } /** + * Removes all cookies. + * <p> + * This method is asynchronous. + * If a {@link ValueCallback} is provided, + * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current + * thread's {@link android.os.Looper} once the operation is complete. + * The value provided to the callback indicates whether any cookies were removed. + * You can pass {@code null} as the callback if you don't need to know when the operation + * completes or whether any cookies were removed, and in this case it is safe to call the + * method from a thread without a Looper. + * @param callback a callback which is executed when the cookies have been removed + */ + public void removeAllCookies(ValueCallback<Boolean> callback) { + throw new MustOverrideException(); + } + + /** * Gets whether there are stored cookies. * * @return true if there are stored cookies @@ -154,7 +215,9 @@ public class CookieManager { /** * Removes all expired cookies. + * @deprecated The WebView handles removing expired cookies automatically. */ + @Deprecated public void removeExpiredCookie() { throw new MustOverrideException(); } diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java index fa760b7..231bf2d 100644 --- a/core/java/android/webkit/PermissionRequest.java +++ b/core/java/android/webkit/PermissionRequest.java @@ -39,6 +39,12 @@ public interface PermissionRequest { * Resource belongs to audio capture device, like microphone. */ public final static long RESOURCE_AUDIO_CAPTURE = 1 << 2; + /** + * Resource belongs to protected media identifier. + * After the user grants this resource, the origin can use EME APIs to generate the license + * requests. + */ + public final static long RESOURCE_PROTECTED_MEDIA_ID = 1 << 3; /** * @return the origin of web content which attempt to access the restricted diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index d630a9a..470d413 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -92,7 +92,7 @@ public class WebChromeClient { @Deprecated public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {}; - + /** * Notify the host application that the current page would * like to hide its custom view. @@ -392,6 +392,75 @@ public class WebChromeClient { } /** + * Tell the client to show a file chooser. + * + * This is called to handle HTML forms with 'file' input type, in response to the + * user pressing the "Select File" button. + * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and + * return true. + * + * @param webView The WebView instance that is initiating the request. + * @param filePathCallback Invoke this callback to supply the list of paths to files to upload, + * or NULL to cancel. Must only be called if the + * <code>showFileChooser</code> implementations returns true. + * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be + * used with it. + * @return true if filePathCallback will be invoked, false to use default handling. + * + * @see FileChooserParams + */ + public boolean showFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, + FileChooserParams fileChooserParams) { + return false; + } + + /** + * Parameters used in the {@link #showFileChooser} method. + * This is intended to be used as a read-only data struct by the application. + */ + public static class FileChooserParams { + // Flags for mode + /** Bitflag for <code>mode</code> indicating multiple files maybe selected */ + public static final int MODE_OPEN_MULTIPLE = 1 << 0; + /** Bitflag for <code>mode</code> indicating a folder maybe selected. + * The implementation should enumerate all files selected by this operation */ + public static final int MODE_OPEN_FOLDER = 1 << 1; + /** Bitflag for <code>mode</code> indicating a non-existant filename maybe returned */ + public static final int MODE_SAVE = 1 << 2; + + /** + * Bit-field of the <code>MODE_</code> flags. + * + * 0 indicates plain single file open. + */ + public int mode; + + /** + * Comma-seperated list of acceptable MIME types. + */ + public String acceptTypes; + + /** + * true indicates a preference for a live media captured value (e.g. Camera, Microphone). + * + * Use <code>acceptTypes</code> to determine suitable capture devices. + */ + public boolean capture; + + /** + * The title to use for this file selector, or null. + * + * Maybe null, in which case a default title should be used. + */ + public String title; + + /** + * Name of a default selection if appropriate, or null. + */ + public String defaultFilename; + }; + + /** * Tell the client to open a file chooser. * @param uploadFile A ValueCallback to set the URI of the file to upload. * onReceiveValue must be called to wake up the thread.a @@ -399,8 +468,11 @@ public class WebChromeClient { * associated with this file picker. * @param capture The value of the 'capture' attribute of the input tag * associated with this file picker. - * @hide + * + * @deprecated Use {@link #showFileChooser} instead. + * @hide This method was not published in any SDK version. */ + @Deprecated public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { uploadFile.onReceiveValue(null); } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 7c32c5b..d14c19b 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1460,4 +1460,36 @@ public abstract class WebSettings { * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}. */ public abstract int getMixedContentMode(); + + /** + * Sets whether to use a video overlay for embedded encrypted video. + * In API levels prior to {@link android.os.Build.VERSION_CODES#L}, encrypted video can + * only be rendered directly on a secure video surface, so it had been a hard problem to play + * encrypted video in HTML. When this flag is on, WebView can play encrypted video (MSE/EME) + * by using a video overlay (aka hole-punching) for videos embedded using HTML <video> + * tag.<br> + * Caution: This setting is intended for use only in a narrow set of circumstances and apps + * should only enable it if they require playback of encrypted video content. It will impose + * the following limitations on the WebView: + * <ul> + * <li> Only one video overlay can be played at a time. + * <li> Changes made to position or dimensions of a video element may be propagated to the + * corresponding video overlay with a noticeable delay. + * <li> The video overlay is not visible to web APIs and as such may not interact with + * script or styling. For example, CSS styles applied to the <video> tag may be ignored. + * </ul> + * This is not an exhaustive set of constraints and it may vary with new versions of the + * WebView. + * @hide + */ + public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag); + + /** + * Gets whether a video overlay will be used for embedded encrypted video. + * + * @return true if WebView uses a video overlay for embedded encrypted video. + * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled + * @hide + */ + public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled(); } diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 945e0e3..6e6a987 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -37,13 +37,6 @@ public interface WebViewFactoryProvider { String findAddress(String addr); /** - * Implements the API methods: - * {@link android.webkit.WebView#enablePlatformNotifications()} - * {@link android.webkit.WebView#disablePlatformNotifications()} - */ - void setPlatformNotificationsEnabled(boolean enable); - - /** * Implements the API method: * {@link android.webkit.WebSettings#getDefaultUserAgent(Context) } */ diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 372228c..f2692da 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -785,7 +785,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te setVerticalScrollBarEnabled(true); TypedArray a = context.obtainStyledAttributes(R.styleable.View); - initializeScrollbars(a); + initializeScrollbarsInternal(a); a.recycle(); } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 43f623b..7e2d809 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -16,10 +16,13 @@ package android.widget; +import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Insets; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region.Op; import android.graphics.drawable.Drawable; @@ -31,10 +34,16 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import com.android.internal.R; + public abstract class AbsSeekBar extends ProgressBar { private final Rect mTempRect = new Rect(); private Drawable mThumb; + private ColorStateList mThumbTint = null; + private PorterDuff.Mode mThumbTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasThumbTint = false; + private int mThumbOffset; private boolean mSplitTrack; @@ -83,6 +92,15 @@ public abstract class AbsSeekBar extends ProgressBar { final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); setThumb(thumb); + if (a.hasValue(R.styleable.SeekBar_thumbTint)) { + mThumbTint = a.getColorStateList(R.styleable.SeekBar_thumbTint); + mThumbTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.SeekBar_thumbTintMode, -1), mThumbTintMode); + mHasThumbTint = true; + + applyThumbTint(); + } + // Guess thumb offset if thumb != null, but allow layout to override. final int thumbOffset = a.getDimensionPixelOffset( com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset()); @@ -108,7 +126,7 @@ public abstract class AbsSeekBar extends ProgressBar { * @param thumb Drawable representing the thumb */ public void setThumb(Drawable thumb) { - boolean needUpdate; + final boolean needUpdate; // This way, calling setThumb again with the same bitmap will result in // it recalcuating mThumbOffset (if for example it the bounds of the // drawable changed) @@ -118,6 +136,7 @@ public abstract class AbsSeekBar extends ProgressBar { } else { needUpdate = false; } + if (thumb != null) { thumb.setCallback(this); if (canResolveLayoutDirection()) { @@ -136,8 +155,12 @@ public abstract class AbsSeekBar extends ProgressBar { requestLayout(); } } + mThumb = thumb; + + applyThumbTint(); invalidate(); + if (needUpdate) { updateThumbAndTrackPos(getWidth(), getHeight()); if (thumb != null && thumb.isStateful()) { @@ -160,6 +183,88 @@ public abstract class AbsSeekBar extends ProgressBar { } /** + * Applies a tint to the thumb drawable. + * <p> + * Subsequent calls to {@link #setThumb(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#SeekBar_thumbTint + * @attr ref android.R.styleable#SeekBar_thumbTintMode + * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) + */ + private void setThumbTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mThumbTint = tint; + mThumbTintMode = tintMode; + mHasThumbTint = true; + + applyThumbTint(); + } + + /** + * Applies a tint to the thumb drawable. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setThumb(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#SeekBar_thumbTint + * @see #setThumbTint(ColorStateList, PorterDuff.Mode) + */ + public void setThumbTint(@Nullable ColorStateList tint) { + setThumbTint(tint, mThumbTintMode); + } + + /** + * @return the tint applied to the thumb drawable + * @attr ref android.R.styleable#SeekBar_thumbTint + * @see #setThumbTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getThumbTint() { + return mThumbTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setThumbTint(ColorStateList)}} to the thumb drawable. The + * default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#SeekBar_thumbTintMode + * @see #setThumbTint(ColorStateList) + */ + public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { + setThumbTint(mThumbTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the thumb drawable + * @attr ref android.R.styleable#SeekBar_thumbTintMode + * @see #setThumbTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getThumbTintMode() { + return mThumbTintMode; + } + + private void applyThumbTint() { + if (mThumb != null && mHasThumbTint) { + mThumb = mThumb.mutate(); + mThumb.setTint(mThumbTint, mThumbTintMode); + } + } + + /** * @see #setThumbOffset(int) */ public int getThumbOffset() { @@ -237,7 +342,10 @@ public abstract class AbsSeekBar extends ProgressBar { @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); - if (mThumb != null) mThumb.jumpToCurrentState(); + + if (mThumb != null) { + mThumb.jumpToCurrentState(); + } } @Override @@ -256,12 +364,11 @@ public abstract class AbsSeekBar extends ProgressBar { } @Override - public void invalidateDrawable(Drawable dr) { - super.invalidateDrawable(dr); + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); - if (dr == mThumb) { - // Handle changes to thumb width and height. - requestLayout(); + if (mThumb != null) { + mThumb.setHotspot(x, y); } } @@ -357,11 +464,11 @@ public abstract class AbsSeekBar extends ProgressBar { final Drawable background = getBackground(); if (background != null) { - final Rect bounds = mThumb.getBounds(); + final Rect bounds = thumb.getBounds(); final int offsetX = mPaddingLeft - mThumbOffset; final int offsetY = mPaddingTop; - background.setHotspotBounds(left + offsetX, bounds.top + offsetY, - right + offsetX, bounds.bottom + offsetY); + background.setHotspotBounds(left + offsetX, top + offsetY, + right + offsetX, bottom + offsetY); } // Canvas will be translated, so 0,0 is where we start drawing @@ -383,8 +490,8 @@ public abstract class AbsSeekBar extends ProgressBar { @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); - drawThumb(canvas); + } @Override diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 3ae9508..4aa2300 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -308,6 +308,15 @@ public class CheckedTextView extends TextView implements Checkable { } @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mCheckMarkDrawable != null) { + mCheckMarkDrawable.setHotspot(x, y); + } + } + + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(CheckedTextView.class.getName()); diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 6aff4f4..9ba0fe1 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -16,9 +16,12 @@ package android.widget; +import android.annotation.Nullable; +import android.graphics.PorterDuff; import com.android.internal.R; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -48,7 +51,12 @@ public abstract class CompoundButton extends Button implements Checkable { private boolean mChecked; private int mButtonResource; private boolean mBroadcasting; + private Drawable mButtonDrawable; + private ColorStateList mButtonTint = null; + private PorterDuff.Mode mButtonTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasButtonTint = false; + private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener; @@ -74,13 +82,22 @@ public abstract class CompoundButton extends Button implements Checkable { 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); + final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); if (d != null) { setButtonDrawable(d); } - boolean checked = a - .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false); + if (a.hasValue(R.styleable.CompoundButton_buttonTint)) { + mButtonTint = a.getColorStateList(R.styleable.CompoundButton_buttonTint); + mButtonTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode); + mHasButtonTint = true; + + applyButtonTint(); + } + + final boolean checked = a.getBoolean( + com.android.internal.R.styleable.CompoundButton_checked, false); setChecked(checked); a.recycle(); @@ -173,9 +190,11 @@ public abstract class CompoundButton extends Button implements Checkable { } /** - * Set the background to a given Drawable, identified by its resource id. + * Set the button graphic to a given Drawable, identified by its resource + * id. * - * @param resid the resource id of the drawable to use as the background + * @param resid the resource id of the drawable to use as the button + * graphic */ public void setButtonDrawable(int resid) { if (resid != 0 && resid == mButtonResource) { @@ -192,23 +211,114 @@ public abstract class CompoundButton extends Button implements Checkable { } /** - * Set the background to a given Drawable + * Set the button graphic to a given Drawable * - * @param d The Drawable to use as the background + * @param d The Drawable to use as the button graphic */ public void setButtonDrawable(Drawable d) { - if (d != null) { + if (mButtonDrawable != d) { if (mButtonDrawable != null) { mButtonDrawable.setCallback(null); unscheduleDrawable(mButtonDrawable); } - d.setCallback(this); - d.setVisible(getVisibility() == VISIBLE, false); + mButtonDrawable = d; - setMinHeight(mButtonDrawable.getIntrinsicHeight()); + + if (d != null) { + d.setCallback(this); + d.setLayoutDirection(getLayoutDirection()); + if (d.isStateful()) { + d.setState(getDrawableState()); + } + d.setVisible(getVisibility() == VISIBLE, false); + setMinHeight(d.getIntrinsicHeight()); + applyButtonTint(); + } } + } + + /** + * Applies a tint to the button drawable. + * <p> + * Subsequent calls to {@link #setButtonDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#CompoundButton_buttonTint + * @attr ref android.R.styleable#CompoundButton_buttonTintMode + * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + private void setButtonTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mButtonTint = tint; + mButtonTintMode = tintMode; + mHasButtonTint = true; - refreshDrawableState(); + applyButtonTint(); + } + + /** + * Applies a tint to the button drawable. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setButtonDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#CompoundButton_buttonTint + * @see #setButtonTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + public void setButtonTint(@Nullable ColorStateList tint) { + setButtonTint(tint, mButtonTintMode); + } + + /** + * @return the tint applied to the button drawable + * @attr ref android.R.styleable#CompoundButton_buttonTint + * @see #setButtonTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getButtonTint() { + return mButtonTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setButtonTint(ColorStateList)}} to the button drawable. The + * default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#CompoundButton_buttonTintMode + * @see #setButtonTint(ColorStateList) + */ + public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) { + setButtonTint(mButtonTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the button drawable + * @attr ref android.R.styleable#CompoundButton_buttonTintMode + * @see #setButtonTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getButtonTintMode() { + return mButtonTintMode; + } + + private void applyButtonTint() { + if (mButtonDrawable != null && mHasButtonTint) { + mButtonDrawable = mButtonDrawable.mutate(); + mButtonDrawable.setTint(mButtonTint, mButtonTintMode); + } } @Override @@ -321,6 +431,15 @@ public abstract class CompoundButton extends Button implements Checkable { } @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mButtonDrawable != null) { + mButtonDrawable.setHotspot(x, y); + } + } + + @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mButtonDrawable; } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 265dbcd..2c1a77c 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -24,6 +24,7 @@ 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; @@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout { 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()); + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); char[] order = ICU.getDateFormatOrder(pattern); final int spinnerCount = order.length; for (int i = 0; i < spinnerCount; i++) { diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 2502954..90fec23 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -286,6 +286,22 @@ public class EdgeEffect { mTargetDisplacement = 0.5f; } + /** + * Set the color of this edge effect in argb. + * + * @param color Color in argb + */ + public void setColor(int color) { + mPaint.setColor(color); + } + + /** + * Return the color of this edge effect in argb. + * @return The color of this edge effect in argb + */ + public int getColor() { + return mPaint.getColor(); + } /** * Draw into the provided canvas. Assumes that the canvas has been rotated diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4467128..13a0849 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1420,7 +1420,7 @@ public class Editor { blockDisplayList.setLeftTopRightBottom(left, top, right, bottom); } - ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null, + ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, null, 0 /* no child clipping, our TextView parent enforces it */); endOfPreviousBlock = blockEndLine; diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index b029328..34f333e 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -18,9 +18,12 @@ package android.widget; import java.util.ArrayList; +import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; @@ -33,6 +36,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; +import com.android.internal.R; + /** * FrameLayout is designed to block out an area on the screen to display @@ -62,6 +67,9 @@ public class FrameLayout extends ViewGroup { @ViewDebug.ExportedProperty(category = "drawing") private Drawable mForeground; + private ColorStateList mForegroundTint = null; + private PorterDuff.Mode mForegroundTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasForegroundTint = false; @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingLeft = 0; @@ -119,6 +127,15 @@ public class FrameLayout extends ViewGroup { setMeasureAllChildren(true); } + if (a.hasValue(R.styleable.FrameLayout_foregroundTint)) { + mForegroundTint = a.getColorStateList(R.styleable.FrameLayout_foregroundTint); + mForegroundTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.FrameLayout_foregroundTintMode, -1), mForegroundTintMode); + mHasForegroundTint = true; + + applyForegroundTint(); + } + mForegroundInPadding = a.getBoolean( com.android.internal.R.styleable.FrameLayout_foregroundInsidePadding, true); @@ -205,6 +222,15 @@ public class FrameLayout extends ViewGroup { } } + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mForeground != null) { + mForeground.setHotspot(x, y); + } + } + /** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, @@ -221,32 +247,34 @@ public class FrameLayout extends ViewGroup { * into account by ensuring that the children are inset to be placed * inside of the padding area. * - * @param drawable The Drawable to be drawn on top of the children. + * @param d The Drawable to be drawn on top of the children. * * @attr ref android.R.styleable#FrameLayout_foreground */ - public void setForeground(Drawable drawable) { - if (mForeground != drawable) { + public void setForeground(Drawable d) { + if (mForeground != d) { if (mForeground != null) { mForeground.setCallback(null); unscheduleDrawable(mForeground); } - mForeground = drawable; + mForeground = d; mForegroundPaddingLeft = 0; mForegroundPaddingTop = 0; mForegroundPaddingRight = 0; mForegroundPaddingBottom = 0; - if (drawable != null) { + if (d != null) { setWillNotDraw(false); - drawable.setCallback(this); - if (drawable.isStateful()) { - drawable.setState(getDrawableState()); + d.setCallback(this); + d.setLayoutDirection(getLayoutDirection()); + if (d.isStateful()) { + d.setState(getDrawableState()); } + applyForegroundTint(); if (mForegroundGravity == Gravity.FILL) { Rect padding = new Rect(); - if (drawable.getPadding(padding)) { + if (d.getPadding(padding)) { mForegroundPaddingLeft = padding.left; mForegroundPaddingTop = padding.top; mForegroundPaddingRight = padding.right; @@ -271,6 +299,89 @@ public class FrameLayout extends ViewGroup { return mForeground; } + /** + * Applies a tint to the foreground drawable. + * <p> + * Subsequent calls to {@link #setForeground(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#FrameLayout_foregroundTint + * @attr ref android.R.styleable#FrameLayout_foregroundTintMode + * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) + */ + private void setForegroundTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mForegroundTint = tint; + mForegroundTintMode = tintMode; + mHasForegroundTint = true; + + applyForegroundTint(); + } + + /** + * Applies a tint to the foreground drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setForeground(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#FrameLayout_foregroundTint + * @see #setForegroundTint(ColorStateList, PorterDuff.Mode) + */ + public void setForegroundTint(@Nullable ColorStateList tint) { + setForegroundTint(tint, mForegroundTintMode); + } + + /** + * @return the tint applied to the foreground drawable + * @attr ref android.R.styleable#FrameLayout_foregroundTint + * @see #setForegroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getForegroundTint() { + return mForegroundTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setForegroundTint(ColorStateList)}} to the foreground drawable. + * The default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#FrameLayout_foregroundTintMode + * @see #setForegroundTint(ColorStateList) + */ + public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) { + setForegroundTint(mForegroundTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the foreground + * drawable + * @attr ref android.R.styleable#FrameLayout_foregroundTintMode + * @see #setForegroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getForegroundTintMode() { + return mForegroundTintMode; + } + + private void applyForegroundTint() { + if (mForeground != null && mHasForegroundTint) { + mForeground = mForeground.mutate(); + mForeground.setTint(mForegroundTint, mForegroundTintMode); + } + } + int getPaddingLeftWithForeground() { return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 8511601..defc26c 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -104,14 +104,16 @@ import static java.lang.Math.min; * * <h4>Excess Space Distribution</h4> * - * GridLayout's distribution of excess space is based on <em>priority</em> - * rather than <em>weight</em>. + * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight. + * In the event that no weights are specified, the previous conventions are respected and + * columns and rows are taken as flexible if their views specify some form of alignment + * within their groups. * <p> - * A child's ability to stretch is inferred from the alignment properties of - * its row and column groups (which are typically set by setting the - * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters). - * If alignment was defined along a given axis then the component - * is taken as <em>flexible</em> in that direction. If no alignment was set, + * The flexibility of a view is therefore influenced by its alignment which is, + * in turn, typically defined by setting the + * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters. + * If either a weight or alignment were defined along a given axis then the component + * is taken as <em>flexible</em> in that direction. If no weight or alignment was set, * the component is instead assumed to be <em>inflexible</em>. * <p> * Multiple components in the same row or column group are @@ -122,12 +124,16 @@ import static java.lang.Math.min; * elements is flexible if <em>one</em> of its elements is flexible. * <p> * To make a column stretch, make sure all of the components inside it define a - * gravity. To prevent a column from stretching, ensure that one of the components - * in the column does not define a gravity. + * weight or a gravity. To prevent a column from stretching, ensure that one of the components + * in the column does not define a weight or a gravity. * <p> * When the principle of flexibility does not provide complete disambiguation, * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em> - * and <em>bottom</em> edges. + * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout + * parameters as a constraint in the a set of variables that define the grid-lines along a + * given axis. During layout, GridLayout solves the constraints so as to return the unique + * solution to those constraints for which all variables are less-than-or-equal-to + * the corresponding value in any other valid solution. * * <h4>Interpretation of GONE</h4> * @@ -140,18 +146,6 @@ import static java.lang.Math.min; * had never been added to it. * These statements apply equally to rows as well as columns, and to groups of rows or columns. * - * <h5>Limitations</h5> - * - * GridLayout does not provide support for the principle of <em>weight</em>, as defined in - * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible - * to configure a GridLayout to distribute excess space between multiple components. - * <p> - * Some common use-cases may nevertheless be accommodated as follows. - * To place equal amounts of space around a component in a cell group; - * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}). - * For complete control over excess space distribution in a row or column; - * use a {@link LinearLayout} subview to hold the components in the associated cell group. - * When using either of these techniques, bear in mind that cell groups may be defined to overlap. * <p> * See {@link GridLayout.LayoutParams} for a full description of the * layout parameters used by GridLayout. @@ -1018,6 +1012,8 @@ public class GridLayout extends ViewGroup { LayoutParams lp = getLayoutParams(c); if (firstPass) { measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); + mHorizontalAxis.recordOriginalMeasurement(i); + mVerticalAxis.recordOriginalMeasurement(i); } else { boolean horizontal = (mOrientation == HORIZONTAL); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; @@ -1245,6 +1241,11 @@ public class GridLayout extends ViewGroup { public int[] locations; public boolean locationsValid = false; + public boolean hasWeights; + public boolean hasWeightsValid = false; + public int[] originalMeasurements; + public int[] deltas; + boolean orderPreserved = DEFAULT_ORDER_PRESERVED; private MutableInt parentMin = new MutableInt(0); @@ -1321,7 +1322,10 @@ public class GridLayout extends ViewGroup { // we must include views that are GONE here, see introductory javadoc LayoutParams lp = getLayoutParams(c); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; - groupBounds.getValue(i).include(GridLayout.this, c, spec, this); + int size = (spec.weight == 0) ? + getMeasurementIncludingMargin(c, horizontal) : + getOriginalMeasurements()[i] + getDeltas()[i]; + groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size); } } @@ -1693,8 +1697,94 @@ public class GridLayout extends ViewGroup { return trailingMargins; } - private void computeLocations(int[] a) { + private void solve(int[] a) { solve(getArcs(), a); + } + + private boolean computeHasWeights() { + for (int i = 0, N = getChildCount(); i < N; i++) { + LayoutParams lp = getLayoutParams(getChildAt(i)); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + if (spec.weight != 0) { + return true; + } + } + return false; + } + + private boolean hasWeights() { + if (!hasWeightsValid) { + hasWeights = computeHasWeights(); + hasWeightsValid = true; + } + return hasWeights; + } + + public int[] getOriginalMeasurements() { + if (originalMeasurements == null) { + originalMeasurements = new int[getChildCount()]; + } + return originalMeasurements; + } + + private void recordOriginalMeasurement(int i) { + if (hasWeights()) { + getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal); + } + } + + public int[] getDeltas() { + if (deltas == null) { + deltas = new int[getChildCount()]; + } + return deltas; + } + + private void shareOutDelta() { + int totalDelta = 0; + float totalWeight = 0; + for (int i = 0, N = getChildCount(); i < N; i++) { + View c = getChildAt(i); + LayoutParams lp = getLayoutParams(c); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + float weight = spec.weight; + if (weight != 0) { + int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i]; + totalDelta += delta; + totalWeight += weight; + } + } + for (int i = 0, N = getChildCount(); i < N; i++) { + LayoutParams lp = getLayoutParams(getChildAt(i)); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + float weight = spec.weight; + if (weight != 0) { + int delta = Math.round((weight * totalDelta / totalWeight)); + deltas[i] = delta; + // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end + totalDelta -= delta; + totalWeight -= weight; + } + } + } + + private void solveAndDistributeSpace(int[] a) { + Arrays.fill(getDeltas(), 0); + solve(a); + shareOutDelta(); + arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + groupBoundsValid = false; + solve(a); + } + + private void computeLocations(int[] a) { + if (!hasWeights()) { + solve(a); + } else { + solveAndDistributeSpace(a); + } if (!orderPreserved) { // Solve returns the smallest solution to the constraint system for which all // values are positive. One value is therefore zero - though if the row/col @@ -1777,6 +1867,10 @@ public class GridLayout extends ViewGroup { locations = null; + originalMeasurements = null; + deltas = null; + hasWeightsValid = false; + invalidateValues(); } @@ -1810,6 +1904,9 @@ public class GridLayout extends ViewGroup { * both aspects of alignment within the cell group. It is also possible to specify a child's * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} * method. + * <p> + * The weight property is also included in Spec and specifies the proportion of any + * excess space that is due to the associated view. * * <h4>WRAP_CONTENT and MATCH_PARENT</h4> * @@ -1851,9 +1948,11 @@ public class GridLayout extends ViewGroup { * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> + * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> + * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> * </ul> * * See {@link GridLayout} for a more complete description of the conventions @@ -1861,8 +1960,10 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_Layout_layout_row * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_column * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { @@ -1889,9 +1990,11 @@ public class GridLayout extends ViewGroup { private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; + private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; private static final int ROW = R.styleable.GridLayout_Layout_layout_row; private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; + private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; @@ -2034,11 +2137,13 @@ public class GridLayout extends ViewGroup { int column = a.getInt(COLUMN, DEFAULT_COLUMN); int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); - this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); + float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); + this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); - this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); + float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); + this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); } finally { a.recycle(); } @@ -2273,10 +2378,9 @@ public class GridLayout extends ViewGroup { return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); } - protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { + protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) { this.flexibility &= spec.getFlexibility(); boolean horizontal = axis.horizontal; - int size = gl.getMeasurementIncludingMargin(c, horizontal); Alignment alignment = gl.getAlignment(spec.alignment, horizontal); // todo test this works correctly when the returned value is UNDEFINED int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); @@ -2401,36 +2505,43 @@ public class GridLayout extends ViewGroup { * <li>{@link #spec(int, int)}</li> * <li>{@link #spec(int, Alignment)}</li> * <li>{@link #spec(int, int, Alignment)}</li> + * <li>{@link #spec(int, float)}</li> + * <li>{@link #spec(int, int, float)}</li> + * <li>{@link #spec(int, Alignment, float)}</li> + * <li>{@link #spec(int, int, Alignment, float)}</li> * </ul> * */ public static class Spec { static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); + static final float DEFAULT_WEIGHT = 0; final boolean startDefined; final Interval span; final Alignment alignment; + final float weight; - private Spec(boolean startDefined, Interval span, Alignment alignment) { + private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { this.startDefined = startDefined; this.span = span; this.alignment = alignment; + this.weight = weight; } - private Spec(boolean startDefined, int start, int size, Alignment alignment) { - this(startDefined, new Interval(start, start + size), alignment); + private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { + this(startDefined, new Interval(start, start + size), alignment, weight); } final Spec copyWriteSpan(Interval span) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final Spec copyWriteAlignment(Alignment alignment) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final int getFlexibility() { - return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; + return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; } /** @@ -2478,6 +2589,7 @@ public class GridLayout extends ViewGroup { * <ul> * <li> {@code spec.span = [start, start + size]} </li> * <li> {@code spec.alignment = alignment} </li> + * <li> {@code spec.weight = weight} </li> * </ul> * <p> * To leave the start index undefined, use the value {@link #UNDEFINED}. @@ -2485,9 +2597,55 @@ public class GridLayout extends ViewGroup { * @param start the start * @param size the size * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, int size, Alignment alignment, float weight) { + return new Spec(start != UNDEFINED, start, size, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, alignment, weight)}. + * + * @param start the start + * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, Alignment alignment, float weight) { + return spec(start, 1, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - + * where {@code default_alignment} is specified in + * {@link android.widget.GridLayout.LayoutParams}. + * + * @param start the start + * @param size the size + * @param weight the weight + */ + public static Spec spec(int start, int size, float weight) { + return spec(start, size, UNDEFINED_ALIGNMENT, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, weight)}. + * + * @param start the start + * @param weight the weight + */ + public static Spec spec(int start, float weight) { + return spec(start, 1, weight); + } + + /** + * Equivalent to: {@code spec(start, size, alignment, 0f)}. + * + * @param start the start + * @param size the size + * @param alignment the alignment */ public static Spec spec(int start, int size, Alignment alignment) { - return new Spec(start != UNDEFINED, start, size, alignment); + return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); } /** diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 04b18c1..93810b3 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1326,12 +1326,28 @@ public class GridView extends AbsListView { if (sel != null) { positionSelector(INVALID_POSITION, sel); mSelectedTop = sel.getTop(); - } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { - View child = getChildAt(mMotionPosition - mFirstPosition); - if (child != null) positionSelector(mMotionPosition, child); } else { - mSelectedTop = 0; - mSelectorRect.setEmpty(); + final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN + && mTouchMode < TOUCH_MODE_SCROLL; + if (inTouchMode) { + // If the user's finger is down, select the motion position. + final View child = getChildAt(mMotionPosition - mFirstPosition); + if (child != null) { + positionSelector(mMotionPosition, child); + } + } else if (mSelectedPosition != INVALID_POSITION) { + // If we had previously positioned the selector somewhere, + // put it back there. It might not match up with the data, + // but it's transitioning out so it's not a big deal. + final View child = getChildAt(mSelectorPosition - mFirstPosition); + if (child != null) { + positionSelector(mSelectorPosition, child); + } + } else { + // Otherwise, clear selection. + mSelectedTop = 0; + mSelectorRect.setEmpty(); + } } // Attempt to restore accessibility focus, if necessary. diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 572302a..5d578ca 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -16,8 +16,10 @@ package android.widget; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -44,6 +46,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; +import com.android.internal.R; + import java.io.IOException; import java.io.InputStream; @@ -75,13 +79,18 @@ public class ImageView extends View { private int mMaxHeight = Integer.MAX_VALUE; // these are applied to the drawable - private ColorFilter mColorFilter; + private ColorFilter mColorFilter = null; + private boolean mHasColorFilter = false; private Xfermode mXfermode; private int mAlpha = 255; private int mViewAlphaScale = 256; private boolean mColorMod = false; private Drawable mDrawable = null; + private ColorStateList mDrawableTint = null; + private PorterDuff.Mode mDrawableTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasDrawableTint = false; + private int[] mState = null; private boolean mMergeState = false; private int mLevel = 0; @@ -154,17 +163,21 @@ public class ImageView extends View { setMaxHeight(a.getDimensionPixelSize( com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE)); - int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); + final int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); if (index >= 0) { setScaleType(sScaleTypeArray[index]); } - int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0); - if (tint != 0) { - setColorFilter(tint); + if (a.hasValue(R.styleable.ImageView_tint)) { + mDrawableTint = a.getColorStateList(R.styleable.ImageView_tint); + mDrawableTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.ImageView_tintMode, -1), mDrawableTintMode); + mHasDrawableTint = true; + + applyDrawableTint(); } - - int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255); + + final int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255); if (alpha != 255) { setAlpha(alpha); } @@ -435,6 +448,88 @@ public class ImageView extends View { } /** + * Applies a tint to the image drawable. + * <p> + * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#ImageView_tint + * @attr ref android.R.styleable#ImageView_tintMode + * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) + */ + private void setTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mDrawableTint = tint; + mDrawableTintMode = tintMode; + mHasDrawableTint = true; + + applyDrawableTint(); + } + + /** + * Applies a tint to the image drawable. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#ImageView_tint + * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) + */ + public void setTint(@Nullable ColorStateList tint) { + setTint(tint, mDrawableTintMode); + } + + /** + * @return the tint applied to the image drawable + * @attr ref android.R.styleable#ImageView_tint + * @see #setTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getTint() { + return mDrawableTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setTint(ColorStateList)}} to the image drawable. The default + * mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#ImageView_tintMode + * @see #setTint(ColorStateList) + */ + public void setTintMode(@Nullable PorterDuff.Mode tintMode) { + setTint(mDrawableTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the image drawable + * @attr ref android.R.styleable#ImageView_tintMode + * @see #setTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getTintMode() { + return mDrawableTintMode; + } + + private void applyDrawableTint() { + if (mDrawable != null && mHasDrawableTint) { + mDrawable = mDrawable.mutate(); + mDrawable.setTint(mDrawableTint, mDrawableTintMode); + } + } + + /** * Sets a Bitmap as the content of this ImageView. * * @param bm The bitmap to set @@ -709,17 +804,20 @@ public class ImageView extends View { mDrawable.setCallback(null); unscheduleDrawable(mDrawable); } + mDrawable = d; + if (d != null) { d.setCallback(this); + d.setLayoutDirection(getLayoutDirection()); if (d.isStateful()) { d.setState(getDrawableState()); } - d.setLevel(mLevel); - d.setLayoutDirection(getLayoutDirection()); d.setVisible(getVisibility() == VISIBLE, true); + d.setLevel(mLevel); mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); + applyDrawableTint(); applyColorMod(); configureBounds(); } else { @@ -1010,7 +1108,16 @@ public class ImageView extends View { } } - @Override + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mDrawable != null) { + mDrawable.setHotspot(x, y); + } + } + + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -1167,6 +1274,7 @@ public class ImageView extends View { public void setColorFilter(ColorFilter cf) { if (mColorFilter != cf) { mColorFilter = cf; + mHasColorFilter = true; mColorMod = true; applyColorMod(); invalidate(); @@ -1221,7 +1329,9 @@ public class ImageView extends View { // re-applied if the Drawable is changed. if (mDrawable != null && mColorMod) { mDrawable = mDrawable.mutate(); - mDrawable.setColorFilter(mColorFilter); + if (mHasColorFilter) { + mDrawable.setColorFilter(mColorFilter); + } mDrawable.setXfermode(mXfermode); mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index eeb8015..1baeca8 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1718,14 +1718,24 @@ public class ListView extends AbsListView { } mSelectedTop = sel.getTop(); } else { - // If the user's finger is down, select the motion position. - // Otherwise, clear selection. - if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) { + final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP + || mTouchMode == TOUCH_MODE_DONE_WAITING; + if (inTouchMode) { + // If the user's finger is down, select the motion position. final View child = getChildAt(mMotionPosition - mFirstPosition); - if (child != null) { + if (child != null) { positionSelector(mMotionPosition, child); } + } else if (mSelectorPosition != INVALID_POSITION) { + // If we had previously positioned the selector somewhere, + // put it back there. It might not match up with the data, + // but it's transitioning out so it's not a big deal. + final View child = getChildAt(mSelectorPosition - mFirstPosition); + if (child != null) { + positionSelector(mSelectorPosition, child); + } } else { + // Otherwise, clear selection. mSelectedTop = 0; mSelectorRect.setEmpty(); } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index b49938c..394b255 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -16,13 +16,17 @@ package android.widget; +import android.annotation.Nullable; +import android.graphics.PorterDuff; import com.android.internal.R; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Animatable; @@ -210,8 +214,26 @@ public class ProgressBar extends View { private Transformation mTransformation; private AlphaAnimation mAnimation; private boolean mHasAnimation; + private Drawable mIndeterminateDrawable; + private ColorStateList mIndeterminateTint = null; + private PorterDuff.Mode mIndeterminateTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasIndeterminateTint = false; + private Drawable mProgressDrawable; + + private ColorStateList mProgressTint = null; + private PorterDuff.Mode mProgressTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasProgressTint = false; + + private ColorStateList mProgressBackgroundTint = null; + private PorterDuff.Mode mProgressBackgroundTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasProgressBackgroundTint = false; + + private ColorStateList mSecondaryProgressTint = null; + private PorterDuff.Mode mSecondaryProgressTintMode = PorterDuff.Mode.SRC_ATOP; + private boolean mHasSecondaryProgressTint = false; + private Drawable mCurrentDrawable; Bitmap mSampleTile; private boolean mNoInvalidate; @@ -257,11 +279,11 @@ public class ProgressBar extends View { mNoInvalidate = true; - Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); - if (drawable != null) { + final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); + if (progressDrawable != null) { // Calling this method can set mMaxHeight, make sure the corresponding // XML attribute for mMaxHeight is read after calling this method - setProgressDrawableTiled(drawable); + setProgressDrawableTiled(progressDrawable); } @@ -288,9 +310,10 @@ public class ProgressBar extends View { setSecondaryProgress( a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); - drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable); - if (drawable != null) { - setIndeterminateDrawableTiled(drawable); + final Drawable indeterminateDrawable = a.getDrawable( + R.styleable.ProgressBar_indeterminateDrawable); + if (indeterminateDrawable != null) { + setIndeterminateDrawableTiled(indeterminateDrawable); } mOnlyIndeterminate = a.getBoolean( @@ -303,6 +326,53 @@ public class ProgressBar extends View { mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); + if (a.hasValue(R.styleable.ProgressBar_progressTint)) { + mProgressTint = a.getColorStateList( + R.styleable.ProgressBar_progressTint); + mProgressTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.ProgressBar_progressBackgroundTintMode, -1), + mProgressTintMode); + mHasProgressTint = true; + + applyProgressLayerTint(R.id.progress, mProgressTint, + mProgressTintMode, true); + } + + if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) { + mProgressBackgroundTint = a.getColorStateList( + R.styleable.ProgressBar_progressBackgroundTint); + mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.ProgressBar_progressTintMode, -1), + mProgressBackgroundTintMode); + mHasProgressBackgroundTint = true; + + applyProgressLayerTint(R.id.background, mProgressBackgroundTint, + mProgressBackgroundTintMode, false); + } + + if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) { + mSecondaryProgressTint = a.getColorStateList( + R.styleable.ProgressBar_secondaryProgressTint); + mSecondaryProgressTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.ProgressBar_secondaryProgressTintMode, -1), + mSecondaryProgressTintMode); + mHasSecondaryProgressTint = true; + + applyProgressLayerTint(R.id.secondaryProgress, mSecondaryProgressTint, + mSecondaryProgressTintMode, false); + } + + if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { + mIndeterminateTint = a.getColorStateList( + R.styleable.ProgressBar_indeterminateTint); + mIndeterminateTintMode = Drawable.parseTintMode(a.getInt( + R.styleable.ProgressBar_indeterminateTintMode, -1), + mIndeterminateTintMode); + mHasIndeterminateTint = true; + + applyIndeterminateTint(); + } + a.recycle(); // If not explicitly specified this view is important for accessibility. @@ -479,16 +549,111 @@ public class ProgressBar extends View { * @see #setIndeterminate(boolean) */ public void setIndeterminateDrawable(Drawable d) { - if (d != null) { - d.setCallback(this); - } - mIndeterminateDrawable = d; - if (mIndeterminateDrawable != null && canResolveLayoutDirection()) { - mIndeterminateDrawable.setLayoutDirection(getLayoutDirection()); + if (mIndeterminateDrawable != d) { + if (mIndeterminateDrawable != null) { + mIndeterminateDrawable.setCallback(null); + unscheduleDrawable(mIndeterminateDrawable); + } + + mIndeterminateDrawable = d; + + if (d != null) { + d.setCallback(this); + d.setLayoutDirection(getLayoutDirection()); + if (d.isStateful()) { + d.setState(getDrawableState()); + } + applyIndeterminateTint(); + } + + if (mIndeterminate) { + mCurrentDrawable = d; + postInvalidate(); + } } - if (mIndeterminate) { - mCurrentDrawable = d; - postInvalidate(); + } + + /** + * Applies a tint to the indeterminate drawable. + * <p> + * Subsequent calls to {@link #setVisibilminateDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and + * tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_indeterminateTint + * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode + * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + private void setIndeterminateTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mIndeterminateTint = tint; + mIndeterminateTintMode = tintMode; + mHasIndeterminateTint = true; + + applyIndeterminateTint(); + } + + /** + * Applies a tint to the indeterminate drawable. Does not modify the + * current tint mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and + * tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_indeterminateTint + * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode) + */ + public void setIndeterminateTint(@Nullable ColorStateList tint) { + setIndeterminateTint(tint, mIndeterminateTintMode); + } + + /** + * @return the tint applied to the indeterminate drawable + * @attr ref android.R.styleable#ProgressBar_indeterminateTint + * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getIndeterminateTint() { + return mIndeterminateTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setIndeterminateTint(ColorStateList)} to the indeterminate + * drawable. The default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode + * @see #setIndeterminateTint(ColorStateList) + */ + public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) { + setIndeterminateTint(mIndeterminateTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the indeterminate drawable + * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode + * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getIndeterminateTintMode() { + return mIndeterminateTintMode; + } + + private void applyIndeterminateTint() { + if (mIndeterminateDrawable != null && mHasIndeterminateTint) { + mIndeterminateDrawable = mIndeterminateDrawable.mutate(); + mIndeterminateDrawable.setTint(mIndeterminateTint, mIndeterminateTintMode); } } @@ -532,42 +697,340 @@ public class ProgressBar extends View { * @see #setIndeterminate(boolean) */ public void setProgressDrawable(Drawable d) { - boolean needUpdate; - if (mProgressDrawable != null && d != mProgressDrawable) { - mProgressDrawable.setCallback(null); - needUpdate = true; - } else { - needUpdate = false; - } + if (mProgressDrawable != d) { + if (mProgressDrawable != null) { + mProgressDrawable.setCallback(null); + unscheduleDrawable(mProgressDrawable); + } - if (d != null) { - d.setCallback(this); - if (canResolveLayoutDirection()) { + mProgressDrawable = d; + + if (d != null) { + d.setCallback(this); d.setLayoutDirection(getLayoutDirection()); + if (d.isStateful()) { + d.setState(getDrawableState()); + } + + // Make sure the ProgressBar is always tall enough + int drawableHeight = d.getMinimumHeight(); + if (mMaxHeight < drawableHeight) { + mMaxHeight = drawableHeight; + requestLayout(); + } + + if (mHasProgressTint) { + applyProgressLayerTint(R.id.progress, mProgressTint, mProgressTintMode, true); + } + + if (mHasProgressBackgroundTint) { + applyProgressLayerTint(R.id.background, mProgressBackgroundTint, + mProgressBackgroundTintMode, false); + } + + if (mHasSecondaryProgressTint) { + applyProgressLayerTint(R.id.secondaryProgress, mSecondaryProgressTint, + mSecondaryProgressTintMode, false); + } } - // Make sure the ProgressBar is always tall enough - int drawableHeight = d.getMinimumHeight(); - if (mMaxHeight < drawableHeight) { - mMaxHeight = drawableHeight; - requestLayout(); + if (!mIndeterminate) { + mCurrentDrawable = d; + postInvalidate(); } - } - mProgressDrawable = d; - if (!mIndeterminate) { - mCurrentDrawable = d; - postInvalidate(); - } - if (needUpdate) { updateDrawableBounds(getWidth(), getHeight()); updateDrawableState(); + doRefreshProgress(R.id.progress, mProgress, false, false); doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); } } /** + * Applies a tint to the progress indicator, if one exists, or to the + * entire progress drawable otherwise. + * <p> + * The progress indicator should be specified as a layer with + * id {@link android.R.id#progress} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and + * tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_progressTint + * @attr ref android.R.styleable#ProgressBar_progressTintMode + * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + private void setProgressTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mProgressTint = tint; + mProgressTintMode = tintMode; + mHasProgressTint = true; + + applyProgressLayerTint(R.id.progress, tint, tintMode, true); + } + + /** + * Applies a tint to the progress indicator, if one exists, or to the + * entire progress drawable otherwise. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * The progress indicator should be specified as a layer with + * id {@link android.R.id#progress} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and + * tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_progressTint + * @see #setProgressTint(ColorStateList) + */ + public void setProgressTint(@Nullable ColorStateList tint) { + setProgressTint(tint, mProgressTintMode); + } + + /** + * @return the tint applied to the progress drawable + * @attr ref android.R.styleable#ProgressBar_progressTint + * @see #setProgressTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getProgressTint() { + return mProgressTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setProgressTint(ColorStateList)}} to the progress + * indicator. The default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#ProgressBar_progressTintMode + * @see #setProgressTint(ColorStateList) + */ + public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) { + setProgressTint(mProgressTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the progress drawable + * @attr ref android.R.styleable#ProgressBar_progressTintMode + * @see #setProgressTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getProgressTintMode() { + return mProgressTintMode; + } + + /** + * Applies a tint to the progress background, if one exists. + * <p> + * The progress background must be specified as a layer with + * id {@link android.R.id#background} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the + * drawable contains a progress background will automatically mutate the + * drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode + * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + private void setProgressBackgroundTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mProgressBackgroundTint = tint; + mProgressBackgroundTintMode = tintMode; + mHasProgressBackgroundTint = true; + + applyProgressLayerTint(R.id.background, tint, tintMode, false); + } + + /** + * Applies a tint to the progress background, if one exists. Does not + * modify the current tint mode, which is + * {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * The progress background must be specified as a layer with + * id {@link android.R.id#background} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the + * drawable contains a progress background will automatically mutate the + * drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint + * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + public void setProgressBackgroundTint(@Nullable ColorStateList tint) { + setProgressBackgroundTint(tint, mProgressBackgroundTintMode); + } + + /** + * @return the tint applied to the progress background + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint + * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getProgressBackgroundTint() { + return mProgressBackgroundTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setProgressBackgroundTint(ColorStateList)}} to the progress + * background. The default mode is {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode + * @see #setProgressBackgroundTint(ColorStateList) + */ + public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + setProgressBackgroundTint(mProgressBackgroundTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the progress + * background + * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode + * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getProgressBackgroundTintMode() { + return mProgressBackgroundTintMode; + } + + /** + * Applies a tint to the secondary progress indicator, if one exists. + * <p> + * The secondary progress indicator must be specified as a layer with + * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the + * drawable contains a secondary progress indicator will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode + * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode) + */ + private void setSecondaryProgressTint(@Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode) { + mSecondaryProgressTint = tint; + mSecondaryProgressTintMode = tintMode; + mHasSecondaryProgressTint = true; + + applyProgressLayerTint(R.id.secondaryProgress, tint, tintMode, false); + } + + /** + * Applies a tint to the secondary progress indicator, if one exists. + * Does not modify the current tint mode, which is + * {@link PorterDuff.Mode#SRC_ATOP} by default. + * <p> + * The secondary progress indicator must be specified as a layer with + * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} + * used as the progress drawable. + * <p> + * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the + * drawable contains a secondary progress indicator will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint + * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode) + */ + public void setSecondaryProgressTint(@Nullable ColorStateList tint) { + setSecondaryProgressTint(tint, mSecondaryProgressTintMode); + } + + /** + * @return the tint applied to the secondary progress drawable + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint + * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public ColorStateList getSecondaryProgressTint() { + return mSecondaryProgressTint; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setSecondaryProgressTint(ColorStateList)}} to the secondary + * progress indicator. The default mode is + * {@link PorterDuff.Mode#SRC_ATOP}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode + * @see #setSecondaryProgressTint(ColorStateList) + */ + public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) { + setSecondaryProgressTint(mSecondaryProgressTint, tintMode); + } + + /** + * @return the blending mode used to apply the tint to the secondary + * progress drawable + * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode + * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getSecondaryProgressTintMode() { + return mSecondaryProgressTintMode; + } + + private void applyProgressLayerTint(int layerId, @Nullable ColorStateList tint, + @Nullable PorterDuff.Mode tintMode, boolean shouldFallback) { + final Drawable d = mProgressDrawable; + if (d != null) { + mProgressDrawable = d.mutate(); + + Drawable layer = null; + if (d instanceof LayerDrawable) { + layer = ((LayerDrawable) d).findDrawableByLayerId(layerId); + } + + if (shouldFallback && layer == null) { + layer = d; + } + + if (layer != null) { + layer.setTint(tint, tintMode); + } + } + } + + /** * Define the tileable drawable used to draw the progress bar in * progress mode. * <p> @@ -670,6 +1133,22 @@ public class ProgressBar extends View { } } + private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) { + Drawable layer = null; + + // We expect a layer drawable, so try to find the target ID. + final Drawable d = mCurrentDrawable; + if (d instanceof LayerDrawable) { + layer = ((LayerDrawable) d).findDrawableByLayerId(id); + } + + if (fallback && layer == null) { + layer = d; + } + + layer.mutate().setTint(tint, tintMode); + } + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, boolean callBackToApp) { float scale = mMax > 0 ? (float) progress / (float) mMax : 0; @@ -1144,6 +1623,19 @@ public class ProgressBar extends View { } } + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mProgressDrawable != null) { + mProgressDrawable.setHotspot(x, y); + } + + if (mIndeterminateDrawable != null) { + mIndeterminateDrawable.setHotspot(x, y); + } + } + static class SavedState extends BaseSavedState { int progress; int secondaryProgress; diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index 0c31496..23fa402 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -112,6 +112,15 @@ public class QuickContactBadge extends ImageView implements OnClickListener { } } + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mOverlay != null) { + mOverlay.setHotspot(x, y); + } + } + /** This call has no effect anymore, as there is only one QuickContact mode */ @SuppressWarnings("unused") public void setMode(int size) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index f7d20b53..82637a1 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1781,7 +1781,9 @@ public class RemoteViews implements Parcelable, Filter { Parcel p = Parcel.obtain(); writeToParcel(p, 0); p.setDataPosition(0); - return new RemoteViews(p); + RemoteViews rv = new RemoteViews(p); + p.recycle(); + return rv; } public String getPackage() { diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 9601d4a..9914800 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -25,6 +25,7 @@ import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -427,9 +428,15 @@ public class Spinner extends AbsSpinner implements OnClickListener { * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object * returned from {@link #getAdapter()} will always return 0. Calling * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return - * 1. + * 1. On API {@link Build.VERSION_CODES#L} and above, attempting to set an + * adapter with more than one view type will throw an + * {@link IllegalArgumentException}. + * + * @param adapter the adapter to set * * @see AbsSpinner#setAdapter(SpinnerAdapter) + * @throws IllegalArgumentException if the adapter has more than one view + * type */ @Override public void setAdapter(SpinnerAdapter adapter) { @@ -437,6 +444,12 @@ public class Spinner extends AbsSpinner implements OnClickListener { mRecycler.clear(); + final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; + if (targetSdkVersion >= Build.VERSION_CODES.L + && adapter != null && adapter.getViewTypeCount() != 1) { + throw new IllegalArgumentException("Spinner adapter view type count must be 1"); + } + if (mPopup != null) { mPopup.setAdapter(new DropDownAdapter(adapter)); } else { diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index c5c6e64..9a8380d 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -90,6 +90,7 @@ public class Switch extends CompoundButton { private boolean mSplitTrack; private CharSequence mTextOn; private CharSequence mTextOff; + private boolean mShowText; private int mTouchMode; private int mTouchSlop; @@ -188,6 +189,7 @@ public class Switch extends CompoundButton { mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track); mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn); mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff); + mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true); mThumbTextPadding = a.getDimensionPixelSize( com.android.internal.R.styleable.Switch_thumbTextPadding, 0); mSwitchMinWidth = a.getDimensionPixelSize( @@ -533,20 +535,43 @@ public class Switch extends CompoundButton { requestLayout(); } + /** + * Sets whether the on/off text should be displayed. + * + * @param showText {@code true} to display on/off text + * @hide + */ + public void setShowText(boolean showText) { + if (mShowText != showText) { + mShowText = showText; + requestLayout(); + } + } + + /** + * @return whether the on/off text should be displayed + * @hide + */ + public boolean getShowText() { + return mShowText; + } + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mOnLayout == null) { - mOnLayout = makeLayout(mTextOn); - } + if (mShowText) { + if (mOnLayout == null) { + mOnLayout = makeLayout(mTextOn); + } - if (mOffLayout == null) { - mOffLayout = makeLayout(mTextOff); + if (mOffLayout == null) { + mOffLayout = makeLayout(mTextOff); + } } mTrackDrawable.getPadding(mTempRect); - final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) - + mThumbTextPadding * 2; + final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) + + mThumbTextPadding * 2 : 0; mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth()); final int switchWidth = Math.max(mSwitchMinWidth, @@ -568,9 +593,10 @@ public class Switch extends CompoundButton { @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); - Layout layout = isChecked() ? mOnLayout : mOffLayout; - if (layout != null && !TextUtils.isEmpty(layout.getText())) { - event.getText().add(layout.getText()); + + final CharSequence text = isChecked() ? mTextOn : mTextOff; + if (text != null) { + event.getText().add(text); } } @@ -963,6 +989,19 @@ public class Switch extends CompoundButton { } @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mThumbDrawable != null) { + mThumbDrawable.setHotspot(x, y); + } + + if (mTrackDrawable != null) { + mTrackDrawable.setHotspot(x, y); + } + } + + @Override public void invalidateDrawable(Drawable drawable) { super.invalidateDrawable(drawable); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 84202eb..d470586 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2551,7 +2551,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener r = c.getResources(); setRawTextSize(TypedValue.applyDimension( - unit, size, r.getDisplayMetrics())); + unit, size, r.getDisplayMetrics())); } private void setRawTextSize(float size) { @@ -2727,7 +2727,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets whether the soft input method will be made visible when this * TextView gets focused. The default is true. - * @hide */ @android.view.RemotableViewMethod public final void setShowSoftInputOnFocus(boolean show) { @@ -2738,7 +2737,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns whether the soft input method will be made visible when this * TextView gets focused. The default is true. - * @hide */ public final boolean getShowSoftInputOnFocus() { // When there is no Editor, return default true value @@ -3506,6 +3504,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + final Drawables dr = mDrawables; + if (dr != null) { + if (dr.mDrawableTop != null) { + dr.mDrawableTop.setHotspot(x, y); + } + if (dr.mDrawableBottom != null) { + dr.mDrawableBottom.setHotspot(x, y); + } + if (dr.mDrawableLeft != null) { + dr.mDrawableLeft.setHotspot(x, y); + } + if (dr.mDrawableRight != null) { + dr.mDrawableRight.setHotspot(x, y); + } + if (dr.mDrawableStart != null) { + dr.mDrawableStart.setHotspot(x, y); + } + if (dr.mDrawableEnd != null) { + dr.mDrawableEnd.setHotspot(x, y); + } + } + } + + @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); @@ -9034,11 +9059,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void drawTextRun(Canvas c, int start, int end, - int contextStart, int contextEnd, float x, float y, int flags, Paint p) { + int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, - contextCount, x, y, flags, p); + contextCount, x, y, isRtl, p); } public float measureText(int start, int end, Paint p) { @@ -9050,20 +9075,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public float getTextRunAdvances(int start, int end, int contextStart, - int contextEnd, int flags, float[] advances, int advancesIndex, + int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; return p.getTextRunAdvances(mChars, start + mStart, count, - contextStart + mStart, contextCount, flags, advances, + contextStart + mStart, contextCount, isRtl, advances, advancesIndex); } - public int getTextRunCursor(int contextStart, int contextEnd, int flags, + public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p) { int contextCount = contextEnd - contextStart; return p.getTextRunCursor(mChars, contextStart + mStart, - contextCount, flags, offset + mStart, cursorOpt); + contextCount, dir, offset + mStart, cursorOpt); } } diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 4f1cd68..122df2c 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -124,6 +124,9 @@ public class Toolbar extends ViewGroup { private CharSequence mTitleText; private CharSequence mSubtitleText; + private int mTitleTextColor; + private int mSubtitleTextColor; + // Clear me after use. private final ArrayList<View> mTempViews = new ArrayList<View>(); @@ -498,7 +501,12 @@ public class Toolbar extends ViewGroup { mTitleTextView = new TextView(context); mTitleTextView.setSingleLine(); mTitleTextView.setEllipsize(TextUtils.TruncateAt.END); - mTitleTextView.setTextAppearance(context, mTitleTextAppearance); + if (mTitleTextAppearance != 0) { + mTitleTextView.setTextAppearance(context, mTitleTextAppearance); + } + if (mTitleTextColor != 0) { + mTitleTextView.setTextColor(mTitleTextColor); + } } if (mTitleTextView.getParent() == null) { addSystemView(mTitleTextView); @@ -546,7 +554,12 @@ public class Toolbar extends ViewGroup { mSubtitleTextView = new TextView(context); mSubtitleTextView.setSingleLine(); mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END); - mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance); + if (mSubtitleTextAppearance != 0) { + mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance); + } + if (mSubtitleTextColor != 0) { + mSubtitleTextView.setTextColor(mSubtitleTextColor); + } } if (mSubtitleTextView.getParent() == null) { addSystemView(mSubtitleTextView); @@ -583,6 +596,30 @@ public class Toolbar extends ViewGroup { } /** + * Sets the text color of the title, if present. + * + * @param color The new text color in 0xAARRGGBB format + */ + public void setTitleTextColor(int color) { + mTitleTextColor = color; + if (mTitleTextView != null) { + mTitleTextView.setTextColor(color); + } + } + + /** + * Sets the text color of the subtitle, if present. + * + * @param color The new text color in 0xAARRGGBB format + */ + public void setSubtitleTextColor(int color) { + mSubtitleTextColor = color; + if (mSubtitleTextView != null) { + mSubtitleTextView.setTextColor(color); + } + } + + /** * Set the icon to use for the toolbar's navigation button. * * <p>The navigation button appears at the start of the toolbar if present. Setting an icon @@ -598,6 +635,17 @@ public class Toolbar extends ViewGroup { } /** + * Retrieve the currently configured content description for the navigation button view. + * This will be used to describe the navigation action to users through mechanisms such + * as screen readers or tooltips. + * + * @return The navigation button's content description + */ + public CharSequence getNavigationContentDescription() { + return mNavButtonView != null ? mNavButtonView.getContentDescription() : null; + } + + /** * Set a content description for the navigation button if one is present. The content * description will be read via screen readers or other accessibility systems to explain * the action of the navigation button. diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 2b62552..8ee0a1b 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -22,6 +22,7 @@ import android.content.DialogInterface; import android.content.res.Resources; import android.graphics.Canvas; import android.media.AudioManager; +import android.media.ClosedCaptionRenderer; import android.media.MediaFormat; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; @@ -316,6 +317,7 @@ public class VideoView extends SurfaceView context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer); controller.registerRenderer(new WebVttRenderer(context)); controller.registerRenderer(new TtmlRenderer(context)); + controller.registerRenderer(new ClosedCaptionRenderer(context)); mMediaPlayer.setSubtitleAnchor(controller, this); if (mAudioSession != 0) { diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 0769b08..7a9137b 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -76,6 +76,8 @@ interface IBatteryStats { void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs); void noteWifiStopped(in WorkSource ws); void noteWifiState(int wifiState, String accessPoint); + void noteWifiSupplicantStateChanged(int supplState, boolean failedAuth); + void noteWifiRssiChanged(int newRssi); void noteBluetoothOn(); void noteBluetoothOff(); void noteBluetoothState(int bluetoothState); diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 03d3b22..77f0dec 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,16 +25,18 @@ import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, String containerId, String key, String resFileName, String publicResFileName, boolean isExternal, - boolean isForwardLocked); + boolean isForwardLocked, in String abiOverride); int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams, in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold); + PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold, + in String abiOverride); boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold); - boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked); + boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride); ObbInfo getObbInfo(in String filename); long calculateDirectorySize(in String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ long[] getFileSystemStats(in String path); void clearDirectory(in String directory); - long calculateInstalledSize(in String packagePath, boolean isForwardLocked); + long calculateInstalledSize(in String packagePath, boolean isForwardLocked, + in String abiOverride); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 47ef65a..01e5d40 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -35,8 +35,8 @@ import java.util.Set; /* - * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be - * passed in and out of a managed profile. + * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of + * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile. */ public class IntentForwarderActivity extends Activity { diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 7e11850..4995ea1 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -28,7 +28,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; -import android.webkit.WebViewFactory; import com.android.internal.util.GrowingArrayUtils; @@ -55,6 +54,11 @@ public final class ProcessStats implements Parcelable { // that is done. public static long COMMIT_PERIOD = 3*60*60*1000; // Commit current stats every 3 hours + // Minimum uptime period before committing. If the COMMIT_PERIOD has elapsed but + // the total uptime has not exceeded this amount, then the commit will be held until + // it is reached. + public static long COMMIT_UPTIME_PERIOD = 60*60*1000; // Must have at least 1 hour elapsed + public static final int STATE_NOTHING = -1; public static final int STATE_PERSISTENT = 0; public static final int STATE_TOP = 1; @@ -81,6 +85,24 @@ public final class ProcessStats implements Parcelable { public static final int PSS_USS_MAXIMUM = 6; public static final int PSS_COUNT = PSS_USS_MAXIMUM+1; + public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0; + public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1; + public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2; + public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3; + public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4; + public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5; + public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6; + public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7; + public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8; + public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9; + public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10; + public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11; + public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12; + public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13; + public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14; + public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15; + public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1; + public static final int ADJ_NOTHING = -1; public static final int ADJ_MEM_FACTOR_NORMAL = 0; public static final int ADJ_MEM_FACTOR_MODERATE = 1; @@ -174,7 +196,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 = 14; + private static final int PARCEL_VERSION = 18; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535453; @@ -200,11 +222,16 @@ public final class ProcessStats implements Parcelable { public int mMemFactor = STATE_NOTHING; public long mStartTime; + public int[] mSysMemUsageTable = null; + public int mSysMemUsageTableSize = 0; + public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT]; + public long mTimePeriodStartClock; public long mTimePeriodStartRealtime; public long mTimePeriodEndRealtime; + public long mTimePeriodStartUptime; + public long mTimePeriodEndUptime; String mRuntime; - String mWebView; boolean mRunning; static final int LONGS_SIZE = 4096; @@ -304,11 +331,77 @@ public final class ProcessStats implements Parcelable { mMemFactorDurations[i] += other.mMemFactorDurations[i]; } + for (int i=0; i<other.mSysMemUsageTableSize; i++) { + int ent = other.mSysMemUsageTable[i]; + int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; + long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); + addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK)); + } + if (other.mTimePeriodStartClock < mTimePeriodStartClock) { mTimePeriodStartClock = other.mTimePeriodStartClock; mTimePeriodStartClockStr = other.mTimePeriodStartClockStr; } mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime; + mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime; + } + + public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem, + long nativeMem) { + if (mMemFactor != STATE_NOTHING) { + int state = mMemFactor * STATE_COUNT; + mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1; + for (int i=0; i<3; i++) { + mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem; + mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem; + mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem; + mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem; + mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem; + } + addSysMemUsage(state, mSysMemUsageArgs, 0); + } + } + + void addSysMemUsage(int state, long[] data, int dataOff) { + int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state); + int off; + if (idx >= 0) { + off = mSysMemUsageTable[idx]; + } else { + mAddLongTable = mSysMemUsageTable; + mAddLongTableSize = mSysMemUsageTableSize; + off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT); + mSysMemUsageTable = mAddLongTable; + mSysMemUsageTableSize = mAddLongTableSize; + } + long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); + idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK; + addSysMemUsage(longs, idx, data, dataOff); + } + + static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) { + final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT]; + final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT]; + if (dstCount == 0) { + dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount; + for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) { + dstData[dstOff+i] = addData[addOff+i]; + } + } else if (addCount > 0) { + dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount; + for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) { + if (dstData[dstOff+i] > addData[addOff+i]) { + dstData[dstOff+i] = addData[addOff+i]; + } + dstData[dstOff+i+1] = (long)( + ((dstData[dstOff+i+1]*(double)dstCount) + + (addData[addOff+i+1]*(double)addCount)) + / (dstCount+addCount) ); + if (dstData[dstOff+i+2] < addData[addOff+i+2]) { + dstData[dstOff+i+2] = addData[addOff+i+2]; + } + } + } } public static final Parcelable.Creator<ProcessStats> CREATOR @@ -564,6 +657,164 @@ public final class ProcessStats implements Parcelable { return totalTime; } + static class PssAggr { + long pss = 0; + long samples = 0; + + void add(long newPss, long newSamples) { + pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) ) + / (samples+newSamples); + samples += newSamples; + } + } + + public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) { + data.totalTime = 0; + for (int i=0; i<STATE_COUNT; i++) { + data.processStateWeight[i] = 0; + data.processStatePss[i] = 0; + data.processStateTime[i] = 0; + data.processStateSamples[i] = 0; + } + for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) { + data.sysMemUsage[i] = 0; + } + data.sysMemCachedWeight = 0; + data.sysMemFreeWeight = 0; + data.sysMemZRamWeight = 0; + data.sysMemKernelWeight = 0; + data.sysMemNativeWeight = 0; + data.sysMemSamples = 0; + long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT]; + for (int i=0; i<mSysMemUsageTableSize; i++) { + int ent = mSysMemUsageTable[i]; + long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); + int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK; + addSysMemUsage(totalMemUsage, 0, longs, idx); + } + for (int is=0; is<data.screenStates.length; is++) { + for (int im=0; im<data.memStates.length; im++) { + int memBucket = data.screenStates[is] + data.memStates[im]; + int stateBucket = memBucket * STATE_COUNT; + long memTime = mMemFactorDurations[memBucket]; + if (mMemFactor == memBucket) { + memTime += now - mStartTime; + } + data.totalTime += memTime; + int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket); + long[] longs = totalMemUsage; + int idx = 0; + if (sysIdx >= 0) { + int ent = mSysMemUsageTable[sysIdx]; + long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); + int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK; + if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) { + addSysMemUsage(data.sysMemUsage, 0, longs, idx); + longs = tmpLongs; + idx = tmpIdx; + } + } + data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE] + * (double)memTime; + data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE] + * (double)memTime; + data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE] + * (double)memTime; + data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE] + * (double)memTime; + data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE] + * (double)memTime; + data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT]; + } + } + ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + for (int iproc=0; iproc<procMap.size(); iproc++) { + SparseArray<ProcessState> uids = procMap.valueAt(iproc); + for (int iu=0; iu<uids.size(); iu++) { + final ProcessState proc = uids.valueAt(iu); + final PssAggr fgPss = new PssAggr(); + final PssAggr bgPss = new PssAggr(); + final PssAggr cachedPss = new PssAggr(); + boolean havePss = false; + for (int i=0; i<proc.mDurationsTableSize; i++) { + int off = proc.mDurationsTable[i]; + int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; + int procState = type % STATE_COUNT; + long samples = proc.getPssSampleCount(type); + if (samples > 0) { + long avg = proc.getPssAverage(type); + havePss = true; + if (procState <= STATE_IMPORTANT_FOREGROUND) { + fgPss.add(avg, samples); + } else if (procState <= STATE_RECEIVER) { + bgPss.add(avg, samples); + } else { + cachedPss.add(avg, samples); + } + } + } + if (!havePss) { + continue; + } + boolean fgHasBg = false; + boolean fgHasCached = false; + boolean bgHasCached = false; + if (fgPss.samples < 3 && bgPss.samples > 0) { + fgHasBg = true; + fgPss.add(bgPss.pss, bgPss.samples); + } + if (fgPss.samples < 3 && cachedPss.samples > 0) { + fgHasCached = true; + fgPss.add(cachedPss.pss, cachedPss.samples); + } + if (bgPss.samples < 3 && cachedPss.samples > 0) { + bgHasCached = true; + bgPss.add(cachedPss.pss, cachedPss.samples); + } + if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) { + bgPss.add(fgPss.pss, fgPss.samples); + } + if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) { + cachedPss.add(bgPss.pss, bgPss.samples); + } + if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) { + cachedPss.add(fgPss.pss, fgPss.samples); + } + for (int i=0; i<proc.mDurationsTableSize; i++) { + final int off = proc.mDurationsTable[i]; + final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; + long time = getLong(off, 0); + if (proc.mCurState == type) { + time += now - proc.mStartTime; + } + final int procState = type % STATE_COUNT; + data.processStateTime[procState] += time; + long samples = proc.getPssSampleCount(type); + long avg; + if (samples > 0) { + avg = proc.getPssAverage(type); + } else if (procState <= STATE_IMPORTANT_FOREGROUND) { + samples = fgPss.samples; + avg = fgPss.pss; + } else if (procState <= STATE_RECEIVER) { + samples = bgPss.samples; + avg = bgPss.pss; + } else { + samples = cachedPss.samples; + avg = cachedPss.pss; + } + double newAvg = ( (data.processStatePss[procState] + * (double)data.processStateSamples[procState]) + + (avg*(double)samples) + ) / (data.processStateSamples[procState]+samples); + data.processStatePss[procState] = (long)newAvg; + data.processStateSamples[procState] += samples; + data.processStateWeight[procState] += avg * (double)time; + } + } + } + } + static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc, int[] screenStates, int[] memStates, int[] procStates, long now) { long totalTime = 0; @@ -679,6 +930,62 @@ public final class ProcessStats implements Parcelable { } } + long getSysMemUsageValue(int state, int index) { + int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state); + return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0; + } + + void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label, + int bucket, int index) { + pw.print(prefix); pw.print(label); + pw.print(": "); + printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024); + pw.print(" min, "); + printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024); + pw.print(" avg, "); + printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024); + pw.println(" max"); + } + + void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates, + int[] memStates) { + int printedScreen = -1; + for (int is=0; is<screenStates.length; is++) { + int printedMem = -1; + for (int im=0; im<memStates.length; im++) { + final int iscreen = screenStates[is]; + final int imem = memStates[im]; + final int bucket = ((iscreen + imem) * STATE_COUNT); + long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT); + if (count > 0) { + pw.print(prefix); + if (screenStates.length > 1) { + printScreenLabel(pw, printedScreen != iscreen + ? iscreen : STATE_NOTHING); + printedScreen = iscreen; + } + if (memStates.length > 1) { + printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0'); + printedMem = imem; + } + pw.print(": "); + pw.print(count); + pw.println(" samples:"); + dumpSysMemUsageCategory(pw, prefix, " Cached", bucket, + SYS_MEM_USAGE_CACHED_MINIMUM); + dumpSysMemUsageCategory(pw, prefix, " Free", bucket, + SYS_MEM_USAGE_FREE_MINIMUM); + dumpSysMemUsageCategory(pw, prefix, " ZRam", bucket, + SYS_MEM_USAGE_ZRAM_MINIMUM); + dumpSysMemUsageCategory(pw, prefix, " Kernel", bucket, + SYS_MEM_USAGE_KERNEL_MINIMUM); + dumpSysMemUsageCategory(pw, prefix, " Native", bucket, + SYS_MEM_USAGE_NATIVE_MINIMUM); + } + } + } + } + static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates, int[] memStates, int[] procStates) { final int NS = screenStates != null ? screenStates.length : 1; @@ -1088,10 +1395,13 @@ public final class ProcessStats implements Parcelable { mTimePeriodStartClock = System.currentTimeMillis(); buildTimePeriodStartClockStr(); mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis(); mLongs.clear(); mLongs.add(new long[LONGS_SIZE]); mNextLong = 0; Arrays.fill(mMemFactorDurations, 0); + mSysMemUsageTable = null; + mSysMemUsageTableSize = 0; mStartTime = 0; mReadError = null; mFlags = 0; @@ -1220,12 +1530,17 @@ public final class ProcessStats implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - long now = SystemClock.uptimeMillis(); + writeToParcel(out, SystemClock.uptimeMillis(), flags); + } + + /** @hide */ + public void writeToParcel(Parcel out, long now, int flags) { out.writeInt(MAGIC); out.writeInt(PARCEL_VERSION); out.writeInt(STATE_COUNT); out.writeInt(ADJ_COUNT); out.writeInt(PSS_COUNT); + out.writeInt(SYS_MEM_USAGE_COUNT); out.writeInt(LONGS_SIZE); mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size()); @@ -1268,8 +1583,9 @@ public final class ProcessStats implements Parcelable { out.writeLong(mTimePeriodStartClock); out.writeLong(mTimePeriodStartRealtime); out.writeLong(mTimePeriodEndRealtime); + out.writeLong(mTimePeriodStartUptime); + out.writeLong(mTimePeriodEndUptime); out.writeString(mRuntime); - out.writeString(mWebView); out.writeInt(mFlags); out.writeInt(mLongs.size()); @@ -1287,6 +1603,13 @@ public final class ProcessStats implements Parcelable { } writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length); + out.writeInt(mSysMemUsageTableSize); + for (int i=0; i<mSysMemUsageTableSize; i++) { + if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": " + + printLongOffset(mSysMemUsageTable[i])); + out.writeInt(mSysMemUsageTable[i]); + } + out.writeInt(NPROC); for (int ip=0; ip<NPROC; ip++) { writeCommonString(out, procMap.keyAt(ip)); @@ -1417,6 +1740,9 @@ public final class ProcessStats implements Parcelable { if (!readCheckedInt(in, PSS_COUNT, "pss count")) { return; } + if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) { + return; + } if (!readCheckedInt(in, LONGS_SIZE, "longs size")) { return; } @@ -1427,8 +1753,9 @@ public final class ProcessStats implements Parcelable { buildTimePeriodStartClockStr(); mTimePeriodStartRealtime = in.readLong(); mTimePeriodEndRealtime = in.readLong(); + mTimePeriodStartUptime = in.readLong(); + mTimePeriodEndUptime = in.readLong(); mRuntime = in.readString(); - mWebView = in.readString(); mFlags = in.readInt(); final int NLONGS = in.readInt(); @@ -1447,6 +1774,12 @@ public final class ProcessStats implements Parcelable { readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length); + mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage"); + if (mSysMemUsageTable == BAD_TABLE) { + return; + } + mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0; + int NPROC = in.readInt(); if (NPROC < 0) { mReadError = "bad process count: " + NPROC; @@ -1826,6 +2159,10 @@ public final class ProcessStats implements Parcelable { boolean dumpAll, boolean activeOnly) { long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor, mStartTime, now); + if (mSysMemUsageTable != null) { + pw.println("System memory usage:"); + dumpSysMemUsage(pw, " ", ALL_SCREEN_ADJ, ALL_MEM_ADJ); + } ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); boolean printedHeader = false; boolean sepNeeded = false; @@ -2089,10 +2426,57 @@ public final class ProcessStats implements Parcelable { dumpTotalsLocked(pw, now); } + long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight, + long totalTime, long curTotalMem, int samples) { + if (memWeight != 0) { + long mem = (long)(memWeight * 1024 / totalTime); + pw.print(prefix); + pw.print(label); + pw.print(": "); + printSizeValue(pw, mem); + pw.print(" ("); + pw.print(samples); + pw.print(" samples)"); + pw.println(); + return curTotalMem + mem; + } + return curTotalMem; + } + void dumpTotalsLocked(PrintWriter pw, long now) { pw.println("Run time Stats:"); dumpSingleTime(pw, " ", mMemFactorDurations, mMemFactor, mStartTime, now); pw.println(); + pw.println("Memory usage:"); + TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ, + ALL_MEM_ADJ); + computeTotalMemoryUse(totalMem, now); + long totalPss = 0; + totalPss = printMemoryCategory(pw, " ", "Kernel ", totalMem.sysMemKernelWeight, + totalMem.totalTime, totalPss, totalMem.sysMemSamples); + totalPss = printMemoryCategory(pw, " ", "Native ", totalMem.sysMemNativeWeight, + totalMem.totalTime, totalPss, totalMem.sysMemSamples); + for (int i=0; i<STATE_COUNT; i++) { + // Skip restarting service state -- that is not actually a running process. + if (i != STATE_SERVICE_RESTARTING) { + totalPss = printMemoryCategory(pw, " ", STATE_NAMES[i], + totalMem.processStateWeight[i], totalMem.totalTime, totalPss, + totalMem.processStateSamples[i]); + } + } + totalPss = printMemoryCategory(pw, " ", "Cached ", totalMem.sysMemCachedWeight, + totalMem.totalTime, totalPss, totalMem.sysMemSamples); + totalPss = printMemoryCategory(pw, " ", "Free ", totalMem.sysMemFreeWeight, + totalMem.totalTime, totalPss, totalMem.sysMemSamples); + totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight, + totalMem.totalTime, totalPss, totalMem.sysMemSamples); + pw.print(" TOTAL : "); + printSizeValue(pw, totalPss); + pw.println(); + printMemoryCategory(pw, " ", STATE_NAMES[STATE_SERVICE_RESTARTING], + totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss, + totalMem.processStateSamples[STATE_SERVICE_RESTARTING]); + pw.println(); pw.print(" Start time: "); pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock)); pw.println(); @@ -2118,8 +2502,6 @@ public final class ProcessStats implements Parcelable { } pw.print(' '); pw.print(mRuntime); - pw.print(' '); - pw.print(mWebView); pw.println(); } @@ -2208,7 +2590,7 @@ public final class ProcessStats implements Parcelable { public void dumpCheckinLocked(PrintWriter pw, String reqPackage) { final long now = SystemClock.uptimeMillis(); final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); - pw.println("vers,4"); + pw.println("vers,5"); pw.print("period,"); pw.print(mTimePeriodStartClockStr); pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(","); pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); @@ -2229,7 +2611,7 @@ public final class ProcessStats implements Parcelable { pw.print(",partial"); } pw.println(); - pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView); + pw.print("config,"); pw.println(mRuntime); for (int ip=0; ip<pkgMap.size(); ip++) { final String pkgName = pkgMap.keyAt(ip); if (reqPackage != null && !reqPackage.equals(pkgName)) { @@ -2362,6 +2744,53 @@ public final class ProcessStats implements Parcelable { pw.print("total"); dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor, mStartTime, now); + if (mSysMemUsageTable != null) { + pw.print("sysmemusage"); + for (int i=0; i<mSysMemUsageTableSize; i++) { + int off = mSysMemUsageTable[i]; + int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; + pw.print(","); + printProcStateTag(pw, type); + for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) { + if (j > SYS_MEM_USAGE_CACHED_MINIMUM) { + pw.print(":"); + } + pw.print(getLong(off, j)); + } + } + } + pw.println(); + TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ, + ALL_MEM_ADJ); + computeTotalMemoryUse(totalMem, now); + pw.print("weights,"); + pw.print(totalMem.totalTime); + pw.print(","); + pw.print(totalMem.sysMemCachedWeight); + pw.print(":"); + pw.print(totalMem.sysMemSamples); + pw.print(","); + pw.print(totalMem.sysMemFreeWeight); + pw.print(":"); + pw.print(totalMem.sysMemSamples); + pw.print(","); + pw.print(totalMem.sysMemZRamWeight); + pw.print(":"); + pw.print(totalMem.sysMemSamples); + pw.print(","); + pw.print(totalMem.sysMemKernelWeight); + pw.print(":"); + pw.print(totalMem.sysMemSamples); + pw.print(","); + pw.print(totalMem.sysMemNativeWeight); + pw.print(":"); + pw.print(totalMem.sysMemSamples); + for (int i=0; i<STATE_COUNT; i++) { + pw.print(","); + pw.print(totalMem.processStateWeight[i]); + pw.print(":"); + pw.print(totalMem.processStateSamples[i]); + } pw.println(); } @@ -2452,6 +2881,15 @@ public final class ProcessStats implements Parcelable { } } + final public static class ProcessStateHolder { + public final int appVersion; + public ProcessStats.ProcessState state; + + public ProcessStateHolder(int _appVersion) { + appVersion = _appVersion; + } + } + public static final class ProcessState extends DurationsTable { public ProcessState mCommonProcess; public final String mPackage; @@ -2660,7 +3098,7 @@ public final class ProcessStats implements Parcelable { * @param pkgList Processes to update. */ public void setState(int state, int memFactor, long now, - ArrayMap<String, ProcessState> pkgList) { + ArrayMap<String, ProcessStateHolder> pkgList) { if (state < 0) { state = mNumStartedServices > 0 ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING; @@ -2770,7 +3208,7 @@ public final class ProcessStats implements Parcelable { } public void addPss(long pss, long uss, boolean always, - ArrayMap<String, ProcessState> pkgList) { + ArrayMap<String, ProcessStateHolder> pkgList) { ensureNotDead(); if (!always) { if (mLastPssState == mCurState && SystemClock.uptimeMillis() @@ -2845,7 +3283,7 @@ public final class ProcessStats implements Parcelable { } } - public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) { + public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) { ensureNotDead(); mCommonProcess.mNumExcessiveWake++; if (!mCommonProcess.mMultiPackage) { @@ -2857,7 +3295,7 @@ public final class ProcessStats implements Parcelable { } } - public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) { + public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) { ensureNotDead(); mCommonProcess.mNumExcessiveCpu++; if (!mCommonProcess.mMultiPackage) { @@ -2888,7 +3326,7 @@ public final class ProcessStats implements Parcelable { } } - public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) { + public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) { ensureNotDead(); mCommonProcess.addCachedKill(1, pss, pss, pss); if (!mCommonProcess.mMultiPackage) { @@ -2925,8 +3363,10 @@ public final class ProcessStats implements Parcelable { return this; } - private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) { - ProcessState proc = pkgList.valueAt(index); + private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList, + int index) { + ProcessStateHolder holder = pkgList.valueAt(index); + ProcessState proc = holder.state; if (mDead && proc.mCommonProcess != proc) { // Somehow we are contining to use a process state that is dead, because // it was not being told it was active during the last commit. We can recover @@ -2959,7 +3399,7 @@ public final class ProcessStats implements Parcelable { throw new IllegalStateException("Didn't create per-package process " + proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid); } - pkgList.setValueAt(index, proc); + holder.state = proc; } return proc; } @@ -3351,4 +3791,27 @@ public final class ProcessStats implements Parcelable { } } } + + public static class TotalMemoryUseCollection { + final int[] screenStates; + final int[] memStates; + + public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) { + screenStates = _screenStates; + memStates = _memStates; + } + + public long totalTime; + public long[] processStatePss = new long[STATE_COUNT]; + public double[] processStateWeight = new double[STATE_COUNT]; + public long[] processStateTime = new long[STATE_COUNT]; + public int[] processStateSamples = new int[STATE_COUNT]; + public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT]; + public double sysMemCachedWeight; + public double sysMemFreeWeight; + public double sysMemZRamWeight; + public double sysMemKernelWeight; + public double sysMemNativeWeight; + public int sysMemSamples; + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 591267e..9d42738 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -259,11 +259,11 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte super.onRestoreInstanceState(savedInstanceState); if (mAlwaysUseOption) { final int checkedPos = mListView.getCheckedItemPosition(); - final boolean enabled = checkedPos != ListView.INVALID_POSITION; + final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; mLastSelected = checkedPos; - mAlwaysButton.setEnabled(enabled); - mOnceButton.setEnabled(enabled); - if (enabled) { + setAlwaysButtonEnabled(hasValidSelection, checkedPos); + mOnceButton.setEnabled(hasValidSelection); + if (hasValidSelection) { mListView.setSelection(checkedPos); } } @@ -274,7 +274,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte final int checkedPos = mListView.getCheckedItemPosition(); final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) { - mAlwaysButton.setEnabled(hasValidSelection); + setAlwaysButtonEnabled(hasValidSelection, checkedPos); mOnceButton.setEnabled(hasValidSelection); if (hasValidSelection) { mListView.smoothScrollToPosition(checkedPos); @@ -285,6 +285,17 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte } } + private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos) { + boolean enabled = false; + if (hasValidSelection) { + ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos); + if (ri.targetUserId == UserHandle.USER_CURRENT) { + enabled = true; + } + } + mAlwaysButton.setEnabled(enabled); + } + public void onButtonClick(View v) { final int id = v.getId(); startSelected(mListView.getCheckedItemPosition(), id == R.id.button_always); @@ -484,8 +495,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte mList.clear(); if (mBaseResolveList != null) { - currentResolveList = mBaseResolveList; - mOrigResolveList = null; + currentResolveList = mOrigResolveList = mBaseResolveList; } else { currentResolveList = mOrigResolveList = mPm.queryIntentActivities( mIntent, PackageManager.MATCH_DEFAULT_ONLY diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 87a80ac..7bd316f 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -830,7 +830,9 @@ public class WindowDecorActionBar extends ActionBar implements } public boolean isShowing() { - return mNowShowing && getHideOffset() < getHeight(); + final int height = getHeight(); + // Take into account the case where the bar has a 0 height due to not being measured yet. + return mNowShowing && (height == 0 || getHideOffset() < height); } void animateToMode(boolean toActionMode) { diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 1e37fd9..643225d 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -16,6 +16,7 @@ package com.android.internal.backup; +import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.content.Intent; import android.content.pm.PackageInfo; @@ -168,17 +169,30 @@ interface IBackupTransport { int startRestore(long token, in PackageInfo[] packages); /** - * Get the package name of the next application with data in the backup store. - * @return The name of one of the packages supplied to {@link #startRestore}, - * or "" (the empty string) if no more backup data is available, - * or null if an error occurred (the restore should be aborted and rescheduled). + * Get the package name of the next application with data in the backup store, plus + * a description of the structure of the restored archive: either TYPE_KEY_VALUE for + * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream. + * + * <p>If the package name in the returned RestoreDescription object is the singleton + * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available + * in the current restore session: all packages described in startRestore() have been + * processed. + * + * <p>If this method returns {@code null}, it means that a transport-level error has + * occurred and the entire restore operation should be abandoned. + * + * @return A RestoreDescription object containing the name of one of the packages + * supplied to {@link #startRestore} plus an indicator of the data type of that + * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that + * no more packages can be restored in this session; or {@code null} to indicate + * a transport-level error. */ - String nextRestorePackage(); + RestoreDescription nextRestorePackage(); /** * Get the data for the application returned by {@link #nextRestorePackage}. * @param data An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #nextRestorePackage}. + * @return the same error codes as {@link #startRestore}. */ int getRestoreData(in ParcelFileDescriptor outFd); @@ -187,4 +201,59 @@ interface IBackupTransport { * freeing any resources and connections used during the restore process. */ void finishRestore(); + + // full backup stuff + + long requestFullBackupTime(); + int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket); + int sendBackupData(int numBytes); + + // full restore stuff + + /** + * Ask the transport to provide data for the "current" package being restored. This + * is the package that was just reported by {@link #nextRestorePackage()} as having + * {@link RestoreDescription#TYPE_FULL_STREAM} data. + * + * The transport writes some data to the socket supplied to this call, and returns + * the number of bytes written. The system will then read that many bytes and + * stream them to the application's agent for restore, then will call this method again + * to receive the next chunk of the archive. This sequence will be repeated until the + * transport returns zero indicating that all of the package's data has been delivered + * (or returns a negative value indicating some sort of hard error condition at the + * transport level). + * + * <p>After this method returns zero, the system will then call + * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * application, and the sequence begins again. + * + * <p>The transport should always close this socket when returning from this method. + * Do not cache this socket across multiple calls or you may leak file descriptors. + * + * @param socket The file descriptor that the transport will use for delivering the + * streamed archive. The transport must close this socket in all cases when returning + * from this method. + * @return 0 when no more data for the current package is available. A positive value + * indicates the presence of that many bytes to be delivered to the app. Any negative + * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, + * indicating a fatal error condition that precludes further restore operations + * on the current dataset. + */ + int getNextFullRestoreDataChunk(in ParcelFileDescriptor socket); + + /** + * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} + * data for restore, it will invoke this method to tell the transport that it should + * abandon the data download for the current package. The OS will then either call + * {@link #nextRestorePackage()} again to move on to restoring the next package in the + * set being iterated over, or will call {@link #finishRestore()} to shut down the restore + * operation. + * + * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the + * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious + * transport-level failure. If the transport reports an error here, the entire restore + * operation will immediately be finished with no further attempts to restore app data. + */ + int abortFullRestore(); + } diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 446ef55..b098de8 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -18,6 +18,8 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; +import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.content.ComponentName; import android.content.Context; @@ -33,12 +35,17 @@ import android.util.Log; import com.android.org.bouncycastle.util.encoders.Base64; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.List; import static android.system.OsConstants.*; @@ -47,7 +54,7 @@ import static android.system.OsConstants.*; * later restoring from there. For testing only. */ -public class LocalTransport extends IBackupTransport.Stub { +public class LocalTransport extends BackupTransport { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; @@ -57,55 +64,84 @@ public class LocalTransport extends IBackupTransport.Stub { private static final String TRANSPORT_DESTINATION_STRING = "Backing up to debug-only private cache"; + private static final String INCREMENTAL_DIR = "_delta"; + private static final String FULL_DATA_DIR = "_full"; + // The currently-active restore set always has the same (nonzero!) token private static final long CURRENT_SET_TOKEN = 1; private Context mContext; private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); + private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR); + private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR); private PackageInfo[] mRestorePackages = null; private int mRestorePackage = -1; // Index into mRestorePackages - private File mRestoreDataDir; + private int mRestoreType; + private File mRestoreSetDir; + private File mRestoreSetIncrementalDir; + private File mRestoreSetFullDir; private long mRestoreToken; + // Additional bookkeeping for full backup + private String mFullTargetPackage; + private ParcelFileDescriptor mSocket; + private FileInputStream mSocketInputStream; + private BufferedOutputStream mFullBackupOutputStream; + private byte[] mFullBackupBuffer; + + private File mFullRestoreSetDir; + private HashSet<String> mFullRestorePackages; + private FileInputStream mCurFullRestoreStream; + private FileOutputStream mFullRestoreSocketStream; + private byte[] mFullRestoreBuffer; public LocalTransport(Context context) { mContext = context; mCurrentSetDir.mkdirs(); + mCurrentSetFullDir.mkdir(); + mCurrentSetIncrementalDir.mkdir(); if (!SELinux.restorecon(mCurrentSetDir)) { Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir); } } + @Override public String name() { return new ComponentName(mContext, this.getClass()).flattenToShortString(); } + @Override public Intent configurationIntent() { // The local transport is not user-configurable return null; } + @Override public String currentDestinationString() { return TRANSPORT_DESTINATION_STRING; } + @Override public String transportDirName() { return TRANSPORT_DIR_NAME; } + @Override public long requestBackupTime() { // any time is a good time for local backup return 0; } + @Override public int initializeDevice() { if (DEBUG) Log.v(TAG, "wiping all data"); deleteContents(mCurrentSetDir); - return BackupConstants.TRANSPORT_OK; + return TRANSPORT_OK; } + @Override public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { if (DEBUG) { try { @@ -118,7 +154,7 @@ public class LocalTransport extends IBackupTransport.Stub { } } - File packageDir = new File(mCurrentSetDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); packageDir.mkdirs(); // Each 'record' in the restore set is kept in its own file, named by @@ -165,7 +201,7 @@ public class LocalTransport extends IBackupTransport.Stub { entity.write(buf, 0, dataSize); } catch (IOException e) { Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); - return BackupConstants.TRANSPORT_ERROR; + return TRANSPORT_ERROR; } finally { entity.close(); } @@ -173,11 +209,11 @@ public class LocalTransport extends IBackupTransport.Stub { entityFile.delete(); } } - return BackupConstants.TRANSPORT_OK; + return TRANSPORT_OK; } catch (IOException e) { // oops, something went wrong. abort the operation and return error. Log.v(TAG, "Exception reading backup input:", e); - return BackupConstants.TRANSPORT_ERROR; + return TRANSPORT_ERROR; } } @@ -196,10 +232,11 @@ public class LocalTransport extends IBackupTransport.Stub { } } + @Override public int clearBackupData(PackageInfo packageInfo) { if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName); - File packageDir = new File(mCurrentSetDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); final File[] fileset = packageDir.listFiles(); if (fileset != null) { for (File f : fileset) { @@ -207,27 +244,129 @@ public class LocalTransport extends IBackupTransport.Stub { } packageDir.delete(); } - return BackupConstants.TRANSPORT_OK; + + packageDir = new File(mCurrentSetFullDir, packageInfo.packageName); + final File[] tarballs = packageDir.listFiles(); + if (tarballs != null) { + for (File f : tarballs) { + f.delete(); + } + packageDir.delete(); + } + + return TRANSPORT_OK; } + @Override public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); - return BackupConstants.TRANSPORT_OK; + if (mSocket != null) { + if (DEBUG) { + Log.v(TAG, "Concluding full backup of " + mFullTargetPackage); + } + try { + mFullBackupOutputStream.flush(); + mFullBackupOutputStream.close(); + mSocketInputStream = null; + mFullTargetPackage = null; + mSocket.close(); + } catch (IOException e) { + return TRANSPORT_ERROR; + } finally { + mSocket = null; + } + } + return TRANSPORT_OK; } + // ------------------------------------------------------------------------------------ + // Full backup handling + + @Override + public long requestFullBackupTime() { + return 0; + } + + @Override + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { + if (mSocket != null) { + Log.e(TAG, "Attempt to initiate full backup while one is in progress"); + return TRANSPORT_ERROR; + } + + if (DEBUG) { + Log.i(TAG, "performFullBackup : " + targetPackage); + } + + // We know a priori that we run in the system process, so we need to make + // sure to dup() our own copy of the socket fd. Transports which run in + // their own processes must not do this. + try { + mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor()); + } catch (IOException e) { + Log.e(TAG, "Unable to process socket for full backup"); + return TRANSPORT_ERROR; + } + + mFullTargetPackage = targetPackage.packageName; + FileOutputStream tarstream; + try { + File tarball = new File(mCurrentSetFullDir, mFullTargetPackage); + tarstream = new FileOutputStream(tarball); + } catch (FileNotFoundException e) { + return TRANSPORT_ERROR; + } + mFullBackupOutputStream = new BufferedOutputStream(tarstream); + mFullBackupBuffer = new byte[4096]; + + return TRANSPORT_OK; + } + + @Override + public int sendBackupData(int numBytes) { + if (mFullBackupBuffer == null) { + Log.w(TAG, "Attempted sendBackupData before performFullBackup"); + return TRANSPORT_ERROR; + } + + if (numBytes > mFullBackupBuffer.length) { + mFullBackupBuffer = new byte[numBytes]; + } + while (numBytes > 0) { + try { + int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, numBytes); + if (nRead < 0) { + // Something went wrong if we expect data but saw EOD + Log.w(TAG, "Unexpected EOD; failing backup"); + return TRANSPORT_ERROR; + } + mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead); + numBytes -= nRead; + } catch (IOException e) { + Log.e(TAG, "Error handling backup data for " + mFullTargetPackage); + return TRANSPORT_ERROR; + } + } + return TRANSPORT_OK; + } + + // ------------------------------------------------------------------------------------ // Restore handling static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { + + @Override + public RestoreSet[] getAvailableRestoreSets() { long[] existing = new long[POSSIBLE_SETS.length + 1]; int num = 0; - // see which possible non-current sets exist, then put the current set at the end + // see which possible non-current sets exist... for (long token : POSSIBLE_SETS) { if ((new File(mDataDir, Long.toString(token))).exists()) { existing[num++] = token; } } - // and always the currently-active set last + // ...and always the currently-active set last existing[num++] = CURRENT_SET_TOKEN; RestoreSet[] available = new RestoreSet[num]; @@ -237,40 +376,69 @@ public class LocalTransport extends IBackupTransport.Stub { return available; } + @Override public long getCurrentRestoreSet() { // The current restore set always has the same token return CURRENT_SET_TOKEN; } + @Override public int startRestore(long token, PackageInfo[] packages) { if (DEBUG) Log.v(TAG, "start restore " + token); mRestorePackages = packages; mRestorePackage = -1; mRestoreToken = token; - mRestoreDataDir = new File(mDataDir, Long.toString(token)); - return BackupConstants.TRANSPORT_OK; + mRestoreSetDir = new File(mDataDir, Long.toString(token)); + mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR); + mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR); + return TRANSPORT_OK; } - public String nextRestorePackage() { + @Override + public RestoreDescription nextRestorePackage() { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); + + boolean found = false; while (++mRestorePackage < mRestorePackages.length) { String name = mRestorePackages[mRestorePackage].packageName; + + // If we have key/value data for this package, deliver that // skip packages where we have a data dir but no actual contents - String[] contents = (new File(mRestoreDataDir, name)).list(); + String[] contents = (new File(mRestoreSetIncrementalDir, name)).list(); if (contents != null && contents.length > 0) { - if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); - return name; + if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) = " + name); + mRestoreType = RestoreDescription.TYPE_KEY_VALUE; + found = true; + } + + if (!found) { + // No key/value data; check for [non-empty] full data + File maybeFullData = new File(mRestoreSetFullDir, name); + if (maybeFullData.length() > 0) { + if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) = " + name); + mRestoreType = RestoreDescription.TYPE_FULL_STREAM; + mCurFullRestoreStream = null; // ensure starting from the ground state + found = true; + } + } + + if (found) { + return new RestoreDescription(name, mRestoreType); } } if (DEBUG) Log.v(TAG, " no more packages to restore"); - return ""; + return RestoreDescription.NO_MORE_PACKAGES; } + @Override public int getRestoreData(ParcelFileDescriptor outFd) { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); - File packageDir = new File(mRestoreDataDir, mRestorePackages[mRestorePackage].packageName); + if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) { + throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset"); + } + File packageDir = new File(mRestoreSetDir, mRestorePackages[mRestorePackage].packageName); // The restore set is the concatenation of the individual record blobs, // each of which is a file in the package's directory. We return the @@ -280,7 +448,7 @@ public class LocalTransport extends IBackupTransport.Stub { ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error Log.e(TAG, "No keys for package: " + packageDir); - return BackupConstants.TRANSPORT_ERROR; + return TRANSPORT_ERROR; } // We expect at least some data if the directory exists in the first place @@ -301,10 +469,10 @@ public class LocalTransport extends IBackupTransport.Stub { in.close(); } } - return BackupConstants.TRANSPORT_OK; + return TRANSPORT_OK; } catch (IOException e) { Log.e(TAG, "Unable to read backup records", e); - return BackupConstants.TRANSPORT_ERROR; + return TRANSPORT_ERROR; } } @@ -341,7 +509,124 @@ public class LocalTransport extends IBackupTransport.Stub { return contents; } + @Override public void finishRestore() { if (DEBUG) Log.v(TAG, "finishRestore()"); + if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) { + resetFullRestoreState(); + } + mRestoreType = 0; + } + + // ------------------------------------------------------------------------------------ + // Full restore handling + + private void resetFullRestoreState() { + try { + mCurFullRestoreStream.close(); + } catch (IOException e) { + Log.w(TAG, "Unable to close full restore input stream"); + } + mCurFullRestoreStream = null; + mFullRestoreSocketStream = null; + mFullRestoreBuffer = null; } + + /** + * Ask the transport to provide data for the "current" package being restored. The + * transport then writes some data to the socket supplied to this call, and returns + * the number of bytes written. The system will then read that many bytes and + * stream them to the application's agent for restore, then will call this method again + * to receive the next chunk of the archive. This sequence will be repeated until the + * transport returns zero indicating that all of the package's data has been delivered + * (or returns a negative value indicating some sort of hard error condition at the + * transport level). + * + * <p>After this method returns zero, the system will then call + * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * application, and the sequence begins again. + * + * @param socket The file descriptor that the transport will use for delivering the + * streamed archive. + * @return 0 when no more data for the current package is available. A positive value + * indicates the presence of that much data to be delivered to the app. A negative + * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, + * indicating a fatal error condition that precludes further restore operations + * on the current dataset. + */ + @Override + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { + if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) { + throw new IllegalStateException("Asked for full restore data for non-stream package"); + } + + // first chunk? + if (mCurFullRestoreStream == null) { + final String name = mRestorePackages[mRestorePackage].packageName; + if (DEBUG) Log.i(TAG, "Starting full restore of " + name); + File dataset = new File(mRestoreSetFullDir, name); + try { + mCurFullRestoreStream = new FileInputStream(dataset); + } catch (IOException e) { + // If we can't open the target package's tarball, we return the single-package + // error code and let the caller go on to the next package. + Log.e(TAG, "Unable to read archive for " + name); + return TRANSPORT_PACKAGE_REJECTED; + } + mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor()); + mFullRestoreBuffer = new byte[32*1024]; + } + + int nRead; + try { + nRead = mCurFullRestoreStream.read(mFullRestoreBuffer); + if (nRead < 0) { + // EOF: tell the caller we're done + nRead = NO_MORE_DATA; + } else if (nRead == 0) { + // This shouldn't happen when reading a FileInputStream; we should always + // get either a positive nonzero byte count or -1. Log the situation and + // treat it as EOF. + Log.w(TAG, "read() of archive file returned 0; treating as EOF"); + nRead = NO_MORE_DATA; + } else { + if (DEBUG) { + Log.i(TAG, " delivering restore chunk: " + nRead); + } + mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead); + } + } catch (IOException e) { + return TRANSPORT_ERROR; // Hard error accessing the file; shouldn't happen + } finally { + // Most transports will need to explicitly close 'socket' here, but this transport + // is in the same process as the caller so it can leave it up to the backup manager + // to manage both socket fds. + } + + return nRead; + } + + /** + * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} + * data for restore, it will invoke this method to tell the transport that it should + * abandon the data download for the current package. The OS will then either call + * {@link #nextRestorePackage()} again to move on to restoring the next package in the + * set being iterated over, or will call {@link #finishRestore()} to shut down the restore + * operation. + * + * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the + * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious + * transport-level failure. If the transport reports an error here, the entire restore + * operation will immediately be finished with no further attempts to restore app data. + */ + @Override + public int abortFullRestore() { + if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) { + throw new IllegalStateException("abortFullRestore() but not currently restoring"); + } + resetFullRestoreState(); + mRestoreType = 0; + return TRANSPORT_OK; + } + } diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java index d05699a..77ac313 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -32,6 +32,6 @@ public class LocalTransportService extends Service { @Override public IBinder onBind(Intent intent) { - return sTransport; + return sTransport.getBinder(); } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index ba419f9..dab3aff 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -20,6 +20,7 @@ import android.content.pm.PackageManager; import android.util.Slog; import java.io.File; +import java.io.IOException; /** * Native libraries helper. @@ -141,4 +142,18 @@ public class NativeLibraryHelper { return deletedFiles; } + + // We don't care about the other return values for now. + private static final int BITCODE_PRESENT = 1; + + public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException { + final int returnVal = hasRenderscriptBitcode(handle.apkHandle); + if (returnVal < 0) { + throw new IOException("Error scanning APK, code: " + returnVal); + } + + return (returnVal == BITCODE_PRESENT); + } + + private static native int hasRenderscriptBitcode(long apkHandle); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 495d5c6..fdd24a6 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -33,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.TreeMap; /** @@ -116,6 +118,24 @@ public class InputMethodSubtypeSwitchingController { + " mIsSystemLanguage=" + mIsSystemLanguage + "}"; } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof ImeSubtypeListItem) { + final ImeSubtypeListItem that = (ImeSubtypeListItem)o; + if (!Objects.equals(this.mImi, that.mImi)) { + return false; + } + if (this.mSubtypeId != that.mSubtypeId) { + return false; + } + return true; + } + return false; + } } private static class InputMethodAndSubtypeList { @@ -211,54 +231,233 @@ public class InputMethodSubtypeSwitchingController { } } - private final InputMethodSettings mSettings; - private InputMethodAndSubtypeList mSubtypeList; + private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + } - @VisibleForTesting - public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return null; + private static class StaticRotationList { + private final List<ImeSubtypeListItem> mImeSubtypeList; + public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + mImeSubtypeList = imeSubtypeList; } - if (imList.size() <= 1) { - return null; - } - // Here we have two rotation groups, depending on the returned boolean value of - // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. - final boolean expectedValueOfSupportsSwitchingToNextInputMethod = - imi.supportsSwitchingToNextInputMethod(); - 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); - // Skip until the current IME/subtype is found. - if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { - continue; - } - // Found the current IME/subtype. Start searching the next IME/subtype from here. - for (int j = 0; j < N - 1; ++j) { - final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); - // Skip if the candidate doesn't belong to the expected rotation group. - if (expectedValueOfSupportsSwitchingToNextInputMethod != - candidate.mImi.supportsSwitchingToNextInputMethod()) { - continue; + + /** + * Returns the index of the specified input method and subtype in the given list. + * @param imi The {@link InputMethodInfo} to be searched. + * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method + * does not have a subtype. + * @return The index in the given list. -1 if not found. + */ + private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + // Skip until the current IME/subtype is found. + if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + return i; } + } + return -1; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (mImeSubtypeList.size() <= 1) { + return null; + } + final int currentIndex = getIndex(imi, subtype); + if (currentIndex < 0) { + return null; + } + final int N = mImeSubtypeList.size(); + for (int offset = 1; offset < N; ++offset) { + // Start searching the next IME/subtype from the next of the current index. + final int candidateIndex = (currentIndex + offset) % N; + final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. - if (onlyCurrentIme && !candidate.mImi.equals(imi)) { + if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } - // No appropriate IME/subtype is found in the list. Give up. return null; } - // The current IME/subtype is not found in the list. Give up. - return null; } + private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + private final List<ImeSubtypeListItem> mImeSubtypeList; + private final int[] mUsageHistoryOfSubtypeListItemIndex; + + private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + mImeSubtypeList = imeSubtypeListItems; + mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; i++) { + mUsageHistoryOfSubtypeListItemIndex[i] = i; + } + } + + /** + * Returns the index of the specified object in + * {@link #mUsageHistoryOfSubtypeListItemIndex}. + * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" + * so as not to be confused with the index in {@link #mImeSubtypeList}. + * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. + */ + private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int usageRank = 0; usageRank < N; usageRank++) { + final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (subtypeListItem.mImi.equals(imi) && + subtypeListItem.mSubtypeId == currentSubtypeId) { + return usageRank; + } + } + // Not found in the known IME/Subtype list. + return -1; + } + + public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentUsageRank = getUsageRank(imi, subtype); + // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 + if (currentUsageRank <= 0) { + return; + } + final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank]; + System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0, + mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank); + mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + int currentUsageRank = getUsageRank(imi, subtype); + if (currentUsageRank < 0) { + if (DEBUG) { + Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype); + } + return null; + } + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int i = 1; i < N; i++) { + final int subtypeListItemRank = (currentUsageRank + i) % N; + final int subtypeListItemIndex = + mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { + continue; + } + return subtypeListItem; + } + return null; + } + } + + @VisibleForTesting + public static class ControllerImpl { + private final DynamicRotationList mSwitchingAwareRotationList; + private final StaticRotationList mSwitchingUnawareRotationList; + + public static ControllerImpl createFrom(final ControllerImpl currentInstance, + final List<ImeSubtypeListItem> sortedEnabledItems) { + DynamicRotationList switchingAwareRotationList = null; + { + final List<ImeSubtypeListItem> switchingAwareImeSubtypes = + filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingAwareRotationList != null && + Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } + if (switchingAwareRotationList == null) { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); + } + } + + StaticRotationList switchingUnawareRotationList = null; + { + final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( + sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingUnawareRotationList != null && + Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } + if (switchingUnawareRotationList == null) { + switchingUnawareRotationList = + new StaticRotationList(switchingUnawareImeSubtypes); + } + } + + return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); + } + + private ControllerImpl(final DynamicRotationList switchingAwareRotationList, + final StaticRotationList switchingUnawareRotationList) { + mSwitchingAwareRotationList = switchingAwareRotationList; + mSwitchingUnawareRotationList = switchingUnawareRotationList; + } + + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } + } + + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return; + } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } + } + + private static List<ImeSubtypeListItem> filterImeSubtypeList( + final List<ImeSubtypeListItem> items, + final boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int ALL_ITEMS_COUNT = items.size(); + for (int i = 0; i < ALL_ITEMS_COUNT; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() == + supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + } + + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeList mSubtypeList; + private ControllerImpl mController; + private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; resetCircularListLocked(context); @@ -269,19 +468,31 @@ public class InputMethodSubtypeSwitchingController { return new InputMethodSubtypeSwitchingController(settings, context); } - // TODO: write unit tests for this method and the logic that determines the next subtype - public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - // TODO: Implement this. + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return; + } + mController.onUserActionLocked(imi, subtype); } public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); + mController = ControllerImpl.createFrom(mController, + mSubtypeList.getSortedInputMethodAndSubtypeList()); } - public ImeSubtypeListItem getNextInputMethodLocked( - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), - onlyCurrentIme, imi, subtype); + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return null; + } + return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 0d00f41..73d3738 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -17,8 +17,10 @@ package com.android.internal.net; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -45,7 +47,10 @@ public class VpnConfig implements Parcelable { public static Intent getIntentForConfirmation() { Intent intent = new Intent(); - intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ConfirmDialog"); + ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + com.android.internal.R.string.config_customVpnConfirmDialogComponent)); + intent.setClassName(componentName.getPackageName(), componentName.getClassName()); return intent; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 240d520..02e4b3f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothHeadset; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkStats; +import android.net.wifi.WifiManager; import android.os.BadParcelableException; import android.os.BatteryManager; import android.os.BatteryStats; @@ -88,7 +89,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 106 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 107 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -284,6 +285,13 @@ public final class BatteryStatsImpl extends BatteryStats { int mWifiState = -1; final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES]; + int mWifiSupplState = -1; + final StopwatchTimer[] mWifiSupplStateTimer = new StopwatchTimer[NUM_WIFI_SUPPL_STATES]; + + int mWifiSignalStrengthBin = -1; + final StopwatchTimer[] mWifiSignalStrengthsTimer = + new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS]; + boolean mBluetoothOn; StopwatchTimer mBluetoothOnTimer; @@ -291,6 +299,7 @@ public final class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES]; int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + long mMobileRadioActiveStartTime; StopwatchTimer mMobileRadioActiveTimer; StopwatchTimer mMobileRadioActivePerAppTimer; LongSamplingCounter mMobileRadioActiveAdjustedTime; @@ -1425,10 +1434,6 @@ public final class BatteryStatsImpl extends BatteryStats { return 0; } - long getLastUpdateTimeMs() { - return mUpdateTime; - } - void stopRunningLocked(long elapsedRealtimeMs) { // Ignore attempt to stop a timer that isn't running if (mNesting == 0) { @@ -2089,7 +2094,9 @@ public final class BatteryStatsImpl extends BatteryStats { if (mHistoryLastWritten.batteryLevel == cur.batteryLevel && (dataSize >= MAX_MAX_HISTORY_BUFFER || ((mHistoryLastWritten.states^cur.states) - & HistoryItem.MOST_INTERESTING_STATES) == 0)) { + & HistoryItem.MOST_INTERESTING_STATES) == 0 + || ((mHistoryLastWritten.states2^cur.states2) + & HistoryItem.MOST_INTERESTING_STATES2) == 0)) { return; } @@ -2790,11 +2797,11 @@ public final class BatteryStatsImpl extends BatteryStats { powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; if (active) { - realElapsedRealtimeMs = elapsedRealtime; + mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime; mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; } else { realElapsedRealtimeMs = timestampNs / (1000*1000); - long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs(); + long lastUpdateTimeMs = mMobileRadioActiveStartTime; if (realElapsedRealtimeMs < lastUpdateTimeMs) { Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs + " is before start time " + lastUpdateTimeMs); @@ -2866,7 +2873,7 @@ public final class BatteryStatsImpl extends BatteryStats { } } - void stopAllSignalStrengthTimersLocked(int except) { + void stopAllPhoneSignalStrengthTimersLocked(int except) { final long elapsedRealtime = SystemClock.elapsedRealtime(); for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { if (i == except) { @@ -2962,13 +2969,13 @@ public final class BatteryStatsImpl extends BatteryStats { if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) { mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); } - mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK) - | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK) + | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT); if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; } else { - stopAllSignalStrengthTimersLocked(-1); + stopAllPhoneSignalStrengthTimersLocked(-1); } mPhoneSignalStrengthBin = strengthBin; } @@ -3068,7 +3075,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (!mWifiOn) { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3081,7 +3088,7 @@ public final class BatteryStatsImpl extends BatteryStats { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); if (mWifiOn) { - mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG; + mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3174,7 +3181,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (!mGlobalWifiRunning) { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3212,7 +3219,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (mGlobalWifiRunning) { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG; + mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3240,6 +3247,64 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteWifiSupplicantStateChangedLocked(int supplState, boolean failedAuth) { + if (DEBUG) Log.i(TAG, "WiFi suppl state -> " + supplState); + if (mWifiSupplState != supplState) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (mWifiSupplState >= 0) { + mWifiSupplStateTimer[mWifiSupplState].stopRunningLocked(elapsedRealtime); + } + mWifiSupplState = supplState; + mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtime); + mHistoryCur.states2 = + (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK) + | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtime, uptime); + } + } + + void stopAllWifiSignalStrengthTimersLocked(int except) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + if (i == except) { + continue; + } + while (mWifiSignalStrengthsTimer[i].isRunningLocked()) { + mWifiSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime); + } + } + } + + public void noteWifiRssiChangedLocked(int newRssi) { + int strengthBin = WifiManager.calculateSignalLevel(newRssi, NUM_WIFI_SIGNAL_STRENGTH_BINS); + if (DEBUG) Log.i(TAG, "WiFi rssi -> " + newRssi + " bin=" + strengthBin); + if (mWifiSignalStrengthBin != strengthBin) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (mWifiSignalStrengthBin >= 0) { + mWifiSignalStrengthsTimer[mWifiSignalStrengthBin].stopRunningLocked( + elapsedRealtime); + } + if (strengthBin >= 0) { + if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) { + mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); + } + mHistoryCur.states2 = + (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK) + | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: " + + Integer.toHexString(mHistoryCur.states2)); + addHistoryRecordLocked(elapsedRealtime, uptime); + } else { + stopAllWifiSignalStrengthTimersLocked(-1); + } + mWifiSignalStrengthBin = strengthBin; + } + } + public void noteBluetoothOnLocked() { if (!mBluetoothOn) { final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -3583,6 +3648,26 @@ public final class BatteryStatsImpl extends BatteryStats { return mWifiStateTimer[wifiState].getCountLocked(which); } + @Override public long getWifiSupplStateTime(int state, + long elapsedRealtimeUs, int which) { + return mWifiSupplStateTimer[state].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getWifiSupplStateCount(int state, int which) { + return mWifiSupplStateTimer[state].getCountLocked(which); + } + + @Override public long getWifiSignalStrengthTime(int strengthBin, + long elapsedRealtimeUs, int which) { + return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getWifiSignalStrengthCount(int strengthBin, int which) { + return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which); + } + @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) { return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -5583,6 +5668,13 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_STATES; i++) { mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i] = new StopwatchTimer(null, -700-i, null, mOnBatteryTimeBase); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i, null, + mOnBatteryTimeBase); + } mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase); @@ -5859,6 +5951,12 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_STATES; i++) { mWifiStateTimer[i].reset(false); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i].reset(false); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[i].reset(false); + } mBluetoothOnTimer.reset(false); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { mBluetoothStateTimer[i].reset(false); @@ -7015,6 +7113,12 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_STATES; i++) { mWifiStateTimer[i].readSummaryFromParcelLocked(in); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i].readSummaryFromParcelLocked(in); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[i].readSummaryFromParcelLocked(in); + } mBluetoothOn = false; mBluetoothOnTimer.readSummaryFromParcelLocked(in); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { @@ -7267,6 +7371,12 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_STATES; i++) { mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[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); @@ -7539,6 +7649,14 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase, in); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i] = new StopwatchTimer(null, -700-i, + null, mOnBatteryTimeBase, in); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i, + null, mOnBatteryTimeBase, in); + } mBluetoothOn = false; mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase, in); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { @@ -7670,6 +7788,12 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_STATES; i++) { mWifiStateTimer[i].writeToParcel(out, uSecRealtime); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + mWifiSupplStateTimer[i].writeToParcel(out, uSecRealtime); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); + } mBluetoothOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime); @@ -7771,7 +7895,7 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Phone timer:"); mPhoneOnTimer.logState(pr, " "); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - pr.println("*** Signal strength #" + i + ":"); + pr.println("*** Phone signal strength #" + i + ":"); mPhoneSignalStrengthsTimer[i].logState(pr, " "); } pr.println("*** Signal scanning :"); @@ -7793,6 +7917,14 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Wifi state #" + i + ":"); mWifiStateTimer[i].logState(pr, " "); } + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + pr.println("*** Wifi suppl state #" + i + ":"); + mWifiSupplStateTimer[i].logState(pr, " "); + } + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + pr.println("*** Wifi signal strength #" + i + ":"); + mWifiSignalStrengthsTimer[i].logState(pr, " "); + } pr.println("*** Bluetooth timer:"); mBluetoothOnTimer.logState(pr, " "); for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java index 58cd60d..e58d68f 100644 --- a/core/java/com/android/internal/os/ProcessCpuTracker.java +++ b/core/java/com/android/internal/os/ProcessCpuTracker.java @@ -442,8 +442,9 @@ public class ProcessCpuTracker { final String[] procStatsString = mProcessFullStatsStringData; final long[] procStats = mProcessFullStatsData; st.base_uptime = SystemClock.uptimeMillis(); - if (Process.readProcFile(st.statFile.toString(), - PROCESS_FULL_STATS_FORMAT, procStatsString, + String path = st.statFile.toString(); + //Slog.d(TAG, "Reading proc file: " + path); + if (Process.readProcFile(path, PROCESS_FULL_STATS_FORMAT, procStatsString, procStats, null)) { // This is a possible way to filter out processes that // are actually kernel threads... do we want to? Some diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index 7edf4cc..c977997 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -45,6 +45,7 @@ public final class SomeArgs { public Object arg3; public Object arg4; public Object arg5; + public Object arg6; public int argi1; public int argi2; public int argi3; @@ -95,6 +96,7 @@ public final class SomeArgs { arg3 = null; arg4 = null; arg5 = null; + arg6 = null; argi1 = 0; argi2 = 0; argi3 = 0; diff --git a/core/java/com/android/internal/policy/IFaceLockInterface.aidl b/core/java/com/android/internal/policy/IFaceLockInterface.aidl index 017801b..bc1f002 100644 --- a/core/java/com/android/internal/policy/IFaceLockInterface.aidl +++ b/core/java/com/android/internal/policy/IFaceLockInterface.aidl @@ -23,6 +23,7 @@ interface IFaceLockInterface { void startUi(IBinder containingWindowToken, int x, int y, int width, int height, boolean useLiveliness); void stopUi(); + void startWithoutUi(); void registerCallback(IFaceLockCallback cb); void unregisterCallback(IFaceLockCallback cb); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index a01e9b7..84bd443 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -24,9 +24,6 @@ oneway interface IStatusBar { void setIcon(int index, in StatusBarIcon icon); void removeIcon(int index); - void addNotification(in StatusBarNotification notification); - void updateNotification(in StatusBarNotification notification); - void removeNotification(String key); void disable(int state); void animateExpandNotificationsPanel(); void animateExpandSettingsPanel(); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index a3b417f..f3430e7 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -39,8 +39,7 @@ interface IStatusBarService // ---- Methods below are for use by the status bar policy services ---- // You need the STATUS_BAR_SERVICE permission void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, - out List<StatusBarNotification> notifications, out int[] switches, - out List<IBinder> binders); + out int[] switches, out List<IBinder> binders); void onPanelRevealed(); void onPanelHidden(); void onNotificationClick(String key); diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index a56fa36..d66ef83 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -169,6 +169,15 @@ public class ArrayUtils return false; } + public static boolean contains(long[] array, long value) { + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(long[] array) { long total = 0; for (long value : array) { @@ -229,6 +238,14 @@ public class ArrayUtils return array; } + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ public static int[] appendInt(int[] cur, int val) { if (cur == null) { return new int[] { val }; @@ -264,4 +281,48 @@ public class ArrayUtils } return cur; } + + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ + public static long[] appendLong(long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + public static long[] removeLong(long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } } diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 9e8d12b..b100d27 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -28,4 +28,5 @@ oneway interface IInputMethodClient { void onUnbindMethod(int sequence); void setActive(boolean active); void setCursorAnchorMonitorMode(int monitorMode); + void setUserActionNotificationSequenceNumber(int sequenceNumber); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5336174..b84c359 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -77,6 +77,6 @@ interface IInputMethodManager { boolean setInputMethodEnabled(String id, boolean enabled); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); int getInputMethodWindowVisibleHeight(); - oneway void notifyTextCommitted(); + oneway void notifyUserAction(int sequenceNumber); void setCursorAnchorMonitorMode(in IBinder token, int monitorMode); } diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 14afe21..3a3e56d 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -47,13 +47,19 @@ public final class InputBindResult implements Parcelable { * Sequence number of this binding. */ public final int sequence; - + + /** + * Sequence number of user action notification. + */ + public final int userActionNotificationSequenceNumber; + public InputBindResult(IInputMethodSession _method, InputChannel _channel, - String _id, int _sequence) { + String _id, int _sequence, int _userActionNotificationSequenceNumber) { method = _method; channel = _channel; id = _id; sequence = _sequence; + userActionNotificationSequenceNumber = _userActionNotificationSequenceNumber; } InputBindResult(Parcel source) { @@ -65,12 +71,15 @@ public final class InputBindResult implements Parcelable { } id = source.readString(); sequence = source.readInt(); + userActionNotificationSequenceNumber = source.readInt(); } @Override public String toString() { return "InputBindResult{" + method + " " + id - + " #" + sequence + "}"; + + " sequence:" + sequence + + " userActionNotificationSequenceNumber:" + userActionNotificationSequenceNumber + + "}"; } /** @@ -90,6 +99,7 @@ public final class InputBindResult implements Parcelable { } dest.writeString(id); dest.writeInt(sequence); + dest.writeInt(userActionNotificationSequenceNumber); } /** diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 002573e..97b1634 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -45,10 +45,9 @@ public class SwipeDismissLayout extends FrameLayout { /** * Called when the layout has been swiped and the position of the window should change. * - * @param progress A number in [-1, 1] representing how far to the left - * or right the window has been swiped. Negative values are swipes - * left, and positives are right. - * @param translate A number in [-w, w], where w is the width of the + * @param progress A number in [0, 1] representing how far to the + * right the window has been swiped + * @param translate A number in [0, w], where w is the width of the * layout. This is equivalent to progress * layout.getWidth(). */ void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate); @@ -207,7 +206,7 @@ public class SwipeDismissLayout extends FrameLayout { private void setProgress(float deltaX) { mTranslationX = deltaX; - if (mProgressListener != null) { + if (mProgressListener != null && deltaX >= 0) { mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX); } } diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java index bed0b88..30cd11b 100644 --- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -152,6 +152,11 @@ public class ToolbarWidgetWrapper implements DecorToolbar { a.recycle(); + if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) { + mToolbar.setNavigationContentDescription( + getContext().getResources().getText(R.string.action_bar_up_description)); + } + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), 0, android.R.id.home, 0, 0, mTitle); @@ -480,7 +485,7 @@ public class ToolbarWidgetWrapper implements DecorToolbar { private void ensureSpinner() { if (mSpinner == null) { - mSpinner = new Spinner(getContext()); + mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle); Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL); mSpinner.setLayoutParams(lp); diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java index bf36bb1..43a05d0 100644 --- a/core/java/com/android/server/SystemService.java +++ b/core/java/com/android/server/SystemService.java @@ -193,58 +193,4 @@ public abstract class SystemService { private SystemServiceManager getManager() { return LocalServices.getService(SystemServiceManager.class); } - -// /** -// * Called when a new user has been created. If your service deals with multiple users, this -// * method should be overridden. -// * -// * @param userHandle The user that was created. -// */ -// public void onUserCreated(int userHandle) { -// } -// -// /** -// * Called when an existing user has started a new session. If your service deals with multiple -// * users, this method should be overridden. -// * -// * @param userHandle The user who started a new session. -// */ -// public void onUserStarted(int userHandle) { -// } -// -// /** -// * Called when a background user session has entered the foreground. If your service deals with -// * multiple users, this method should be overridden. -// * -// * @param userHandle The user who's session entered the foreground. -// */ -// public void onUserForeground(int userHandle) { -// } -// -// /** -// * Called when a foreground user session has entered the background. If your service deals with -// * multiple users, this method should be overridden; -// * -// * @param userHandle The user who's session entered the background. -// */ -// public void onUserBackground(int userHandle) { -// } -// -// /** -// * Called when a user's active session has stopped. If your service deals with multiple users, -// * this method should be overridden. -// * -// * @param userHandle The user who's session has stopped. -// */ -// public void onUserStopped(int userHandle) { -// } -// -// /** -// * Called when a user has been removed from the system. If your service deals with multiple -// * users, this method should be overridden. -// * -// * @param userHandle The user who has been removed. -// */ -// public void onUserRemoved(int userHandle) { -// } } diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java index 87a50a9..fda6479 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/core/java/com/android/server/SystemServiceManager.java @@ -50,8 +50,19 @@ public class SystemServiceManager { * @return The service instance. */ @SuppressWarnings("unchecked") - public SystemService startService(String className) throws ClassNotFoundException { - return startService((Class<SystemService>) Class.forName(className)); + public SystemService startService(String className) { + final Class<SystemService> serviceClass; + try { + serviceClass = (Class<SystemService>)Class.forName(className); + } catch (ClassNotFoundException ex) { + Slog.i(TAG, "Starting " + className); + throw new RuntimeException("Failed to create service " + className + + ": service class not found, usually indicates that the caller should " + + "have called PackageManager.hasSystemFeature() to check whether the " + + "feature is available on this device before trying to start the " + + "services that implement it", ex); + } + return startService(serviceClass); } /** diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java index 430dd63..3d9fb5c 100644 --- a/core/java/com/android/server/net/BaseNetworkObserver.java +++ b/core/java/com/android/server/net/BaseNetworkObserver.java @@ -18,6 +18,7 @@ package com.android.server.net; import android.net.INetworkManagementEventObserver; import android.net.LinkAddress; +import android.net.RouteInfo; /** * Base {@link INetworkManagementEventObserver} that provides no-op @@ -70,4 +71,14 @@ public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { public void interfaceDnsServerInfo(String iface, long lifetime, String[] servers) { // default no-op } + + @Override + public void routeUpdated(RouteInfo route) { + // default no-op + } + + @Override + public void routeRemoved(RouteInfo route) { + // default no-op + } } diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java new file mode 100644 index 0000000..7dd8dd8 --- /dev/null +++ b/core/java/com/android/server/net/NetlinkTracker.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.RouteInfo; +import android.util.Log; + +/** + * Keeps track of link configuration received from Netlink. + * + * Instances of this class are expected to be owned by subsystems such as Wi-Fi + * or Ethernet that manage one or more network interfaces. Each interface to be + * tracked needs its own {@code NetlinkTracker}. + * + * An instance of this class is constructed by passing in an interface name and + * a callback. The owner is then responsible for registering the tracker with + * NetworkManagementService. When the class receives update notifications from + * the NetworkManagementService notification threads, it applies the update to + * its local LinkProperties, and if something has changed, notifies its owner of + * the update via the callback. + * + * The owner can then call {@code getLinkProperties()} in order to find out + * what changed. If in the meantime the LinkProperties stored here have changed, + * this class will return the current LinkProperties. Because each change + * triggers an update callback after the change is made, the owner may get more + * callbacks than strictly necessary (some of which may be no-ops), but will not + * be out of sync once all callbacks have been processed. + * + * Threading model: + * + * - The owner of this class is expected to create it, register it, and call + * getLinkProperties or clearLinkProperties on its thread. + * - Most of the methods in the class are inherited from BaseNetworkObserver + * and are called by NetworkManagementService notification threads. + * - All accesses to mLinkProperties must be synchronized(this). All the other + * member variables are immutable once the object is constructed. + * + * This class currently tracks IPv4 and IPv6 addresses. In the future it will + * track routes and DNS servers. + * + * @hide + */ +public class NetlinkTracker extends BaseNetworkObserver { + + private final String TAG; + + public interface Callback { + public void update(); + } + + private final String mInterfaceName; + private final Callback mCallback; + private final LinkProperties mLinkProperties; + + private static final boolean DBG = true; + + public NetlinkTracker(String iface, Callback callback) { + TAG = "NetlinkTracker/" + iface; + mInterfaceName = iface; + mCallback = callback; + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + + private void maybeLog(String operation, String iface, LinkAddress address) { + if (DBG) { + Log.d(TAG, operation + ": " + address + " on " + iface + + " flags " + address.getFlags() + " scope " + address.getScope()); + } + } + + private void maybeLog(String operation, Object o) { + if (DBG) { + Log.d(TAG, operation + ": " + o.toString()); + } + } + + @Override + public void addressUpdated(String iface, LinkAddress address) { + if (mInterfaceName.equals(iface)) { + maybeLog("addressUpdated", iface, address); + boolean changed; + synchronized (this) { + changed = mLinkProperties.addLinkAddress(address); + } + if (changed) { + mCallback.update(); + } + } + } + + @Override + public void addressRemoved(String iface, LinkAddress address) { + if (mInterfaceName.equals(iface)) { + maybeLog("addressRemoved", iface, address); + boolean changed; + synchronized (this) { + changed = mLinkProperties.removeLinkAddress(address); + } + if (changed) { + mCallback.update(); + } + } + } + + @Override + public void routeUpdated(RouteInfo route) { + if (mInterfaceName.equals(route.getInterface())) { + maybeLog("routeUpdated", route); + boolean changed; + synchronized (this) { + changed = mLinkProperties.addRoute(route); + } + if (changed) { + mCallback.update(); + } + } + } + + @Override + public void routeRemoved(RouteInfo route) { + if (mInterfaceName.equals(route.getInterface())) { + maybeLog("routeRemoved", route); + boolean changed; + synchronized (this) { + changed = mLinkProperties.removeRoute(route); + } + if (changed) { + mCallback.update(); + } + } + } + + /** + * Returns a copy of this object's LinkProperties. + */ + public synchronized LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + public synchronized void clearLinkProperties() { + mLinkProperties.clear(); + mLinkProperties.setInterfaceName(mInterfaceName); + } +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index f446c3a..cb00062 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -57,7 +57,6 @@ LOCAL_SRC_FILES:= \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_GraphicBuffer.cpp \ - android_view_GLRenderer.cpp \ android_view_GLES20Canvas.cpp \ android_view_HardwareLayer.cpp \ android_view_ThreadedRenderer.cpp \ @@ -90,6 +89,7 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ + android_graphics_Picture.cpp \ android/graphics/AutoDecodeCancel.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ @@ -141,6 +141,7 @@ LOCAL_SRC_FILES:= \ android_hardware_camera2_DngCreator.cpp \ android_hardware_SensorManager.cpp \ android_hardware_SerialPort.cpp \ + android_hardware_SoundTrigger.cpp \ android_hardware_UsbDevice.cpp \ android_hardware_UsbDeviceConnection.cpp \ android_hardware_UsbRequest.cpp \ @@ -236,6 +237,7 @@ LOCAL_SHARED_LIBRARIES := \ libpdfium \ libimg_utils \ libnetd_client \ + libsoundtrigger ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index e069876..598d6c1 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -29,7 +29,6 @@ #include <SkGraphics.h> #include <SkImageDecoder.h> -#include <SkImageRef_GlobalPool.h> #include "jni.h" #include "JNIHelp.h" @@ -83,6 +82,7 @@ extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *e extern int register_android_hardware_camera2_DngCreator(JNIEnv *env); extern int register_android_hardware_SensorManager(JNIEnv *env); extern int register_android_hardware_SerialPort(JNIEnv *env); +extern int register_android_hardware_SoundTrigger(JNIEnv *env); extern int register_android_hardware_UsbDevice(JNIEnv *env); extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env); extern int register_android_hardware_UsbRequest(JNIEnv *env); @@ -130,7 +130,6 @@ extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_RenderNodeAnimator(JNIEnv* env); extern int register_android_view_GraphicBuffer(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); -extern int register_android_view_GLRenderer(JNIEnv* env); extern int register_android_view_HardwareLayer(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); @@ -160,6 +159,7 @@ extern int register_android_net_TrafficStats(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_AndroidBidi(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); +extern int register_android_server_fingerprint_FingerprintService(JNIEnv* env); extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env); extern int register_android_server_Watchdog(JNIEnv* env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); @@ -245,14 +245,6 @@ AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) mArgBlockLength(argBlockLength) { SkGraphics::Init(); - // this sets our preference for 16bit images during decode - // in case the src is opaque and 24bit - SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config); - // This cache is shared between browser native images, and java "purgeable" - // bitmaps. This globalpool is for images that do not either use the java - // heap, or are not backed by ashmem. See BitmapFactory.cpp for the key - // java call site. - SkImageRef_GlobalPool::SetRAMBudget(512 * 1024); // There is also a global font cache, but its budget is specified in code // see SkFontHost_android.cpp @@ -499,6 +491,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char profile_duration[sizeof("-Xprofile-duration:") + PROPERTY_VALUE_MAX]; char profile_interval[sizeof("-Xprofile-interval:") + PROPERTY_VALUE_MAX]; char profile_backoff[sizeof("-Xprofile-backoff:") + PROPERTY_VALUE_MAX]; + char profile_top_k_threshold[sizeof("-Xprofile-top-k-threshold:") + PROPERTY_VALUE_MAX]; + char profile_top_k_change_threshold[sizeof("-Xprofile-top-k-change-threshold:") + PROPERTY_VALUE_MAX]; char langOption[sizeof("-Duser.language=") + 3]; char regionOption[sizeof("-Duser.region=") + 3]; char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:") + sizeof(propBuf)]; @@ -822,31 +816,65 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) * Set profiler options */ if (libart) { - // Number of seconds during profile runs. - strcpy(profile_period, "-Xprofile-period:"); - property_get("dalvik.vm.profile.period_secs", profile_period+17, "10"); - opt.optionString = profile_period; - mOptions.add(opt); + // Whether or not the profiler should be enabled. + property_get("dalvik.vm.profiler", propBuf, "0"); + if (propBuf[0] == '1') { + opt.optionString = "-Xenable-profiler"; + mOptions.add(opt); + } - // Length of each profile run (seconds). - strcpy(profile_duration, "-Xprofile-duration:"); - property_get("dalvik.vm.profile.duration_secs", profile_duration+19, "30"); - opt.optionString = profile_duration; - mOptions.add(opt); + // Whether the profile should start upon app startup or be delayed by some random offset + // (in seconds) that is bound between 0 and a fixed value. + property_get("dalvik.vm.profile.start-immed", propBuf, "0"); + if (propBuf[0] == '1') { + opt.optionString = "-Xprofile-start-immediately"; + mOptions.add(opt); + } + // Number of seconds during profile runs. + strcpy(profile_period, "-Xprofile-period:"); + if (property_get("dalvik.vm.profile.period-secs", profile_period+17, NULL) > 0) { + opt.optionString = profile_period; + mOptions.add(opt); + } - // Polling interval during profile run (microseconds). - strcpy(profile_interval, "-Xprofile-interval:"); - property_get("dalvik.vm.profile.interval_us", profile_interval+19, "10000"); - opt.optionString = profile_interval; - mOptions.add(opt); + // Length of each profile run (seconds). + strcpy(profile_duration, "-Xprofile-duration:"); + if (property_get("dalvik.vm.profile.duration-secs", profile_duration+19, NULL) > 0) { + opt.optionString = profile_duration; + mOptions.add(opt); + } - // Coefficient for period backoff. The the period is multiplied - // by this value after each profile run. - strcpy(profile_backoff, "-Xprofile-backoff:"); - property_get("dalvik.vm.profile.backoff_coeff", profile_backoff+18, "2.0"); - opt.optionString = profile_backoff; - mOptions.add(opt); + // Polling interval during profile run (microseconds). + strcpy(profile_interval, "-Xprofile-interval:"); + if (property_get("dalvik.vm.profile.interval-us", profile_interval+19, NULL) > 0) { + opt.optionString = profile_interval; + mOptions.add(opt); + } + + // Coefficient for period backoff. The the period is multiplied + // by this value after each profile run. + strcpy(profile_backoff, "-Xprofile-backoff:"); + if (property_get("dalvik.vm.profile.backoff-coeff", profile_backoff+18, NULL) > 0) { + opt.optionString = profile_backoff; + mOptions.add(opt); + } + + // Top K% of samples that are considered relevant when deciding if the app should be recompiled. + strcpy(profile_top_k_threshold, "-Xprofile-top-k-threshold:"); + if (property_get("dalvik.vm.profile.top-k-thr", profile_top_k_threshold+26, NULL) > 0) { + opt.optionString = profile_top_k_threshold; + mOptions.add(opt); + } + + // The threshold after which a change in the structure of the top K% profiled samples becomes significant + // and triggers recompilation. A change in profile is considered significant if X% (top-k-change-threshold) + // of the top K% (top-k-threshold property) samples has changed. + strcpy(profile_top_k_change_threshold, "-Xprofile-top-k-change-threshold:"); + if (property_get("dalvik.vm.profile.top-k-ch-thr", profile_top_k_change_threshold+33, NULL) > 0) { + opt.optionString = profile_top_k_change_threshold; + mOptions.add(opt); + } } initArgs.version = JNI_VERSION_1_4; @@ -1214,7 +1242,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_RenderNodeAnimator), REG_JNI(register_android_view_GraphicBuffer), REG_JNI(register_android_view_GLES20Canvas), - REG_JNI(register_android_view_GLRenderer), REG_JNI(register_android_view_HardwareLayer), REG_JNI(register_android_view_ThreadedRenderer), REG_JNI(register_android_view_Surface), @@ -1290,6 +1317,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_camera2_DngCreator), REG_JNI(register_android_hardware_SensorManager), REG_JNI(register_android_hardware_SerialPort), + REG_JNI(register_android_hardware_SoundTrigger), REG_JNI(register_android_hardware_UsbDevice), REG_JNI(register_android_hardware_UsbDeviceConnection), REG_JNI(register_android_hardware_UsbRequest), @@ -1301,6 +1329,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_ToneGenerator), REG_JNI(register_android_opengl_classes), + REG_JNI(register_android_server_fingerprint_FingerprintService), REG_JNI(register_android_server_NetworkManagementSocketTagger), REG_JNI(register_android_server_Watchdog), REG_JNI(register_android_ddm_DdmHandleNativeHeap), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 0328517..c139c9d 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -258,16 +258,16 @@ static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width, // can return NULL static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) { - switch (src.config()) { - case SkBitmap::kARGB_8888_Config: + switch (src.colorType()) { + case kN32_SkColorType: if (src.isOpaque()) return ToColor_S32_Opaque; return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw; - case SkBitmap::kARGB_4444_Config: + case kARGB_4444_SkColorType: if (src.isOpaque()) return ToColor_S4444_Opaque; return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw; - case SkBitmap::kRGB_565_Config: + case kRGB_565_SkColorType: return ToColor_S565; - case SkBitmap::kIndex8_Config: + case kIndex_8_SkColorType: if (src.getColorTable() == NULL) { return NULL; } @@ -291,7 +291,7 @@ static int getPremulBitmapCreateFlags(bool isMutable) { static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, jint offset, jint stride, jint width, jint height, jint configHandle, jboolean isMutable) { - SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle); + SkColorType colorType = SkBitmapConfigToColorType(static_cast<SkBitmap::Config>(configHandle)); if (NULL != jColors) { size_t n = env->GetArrayLength(jColors); if (n < SkAbs32(stride) * (size_t)height) { @@ -301,12 +301,12 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } // ARGB_4444 is a deprecated format, convert automatically to 8888 - if (config == SkBitmap::kARGB_4444_Config) { - config = SkBitmap::kARGB_8888_Config; + if (colorType == kARGB_4444_SkColorType) { + colorType = kN32_SkColorType; } SkBitmap bitmap; - bitmap.setConfig(config, width, height); + bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType)); jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL); if (NULL == buff) { @@ -361,24 +361,50 @@ static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) { } static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, - jint width, jint height, jint configHandle, jint allocSize) { + jint width, jint height, jint configHandle, jint allocSize, + jboolean requestPremul) { SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle); - if (width * height * SkBitmap::ComputeBytesPerPixel(config) > allocSize) { + SkColorType colorType = SkBitmapConfigToColorType(config); + + // ARGB_4444 is a deprecated format, convert automatically to 8888 + if (colorType == kARGB_4444_SkColorType) { + colorType = kN32_SkColorType; + } + + if (width * height * SkColorTypeBytesPerPixel(colorType) > allocSize) { // done in native as there's no way to get BytesPerPixel in Java doThrowIAE(env, "Bitmap not large enough to support new configuration"); return; } SkPixelRef* ref = bitmap->pixelRef(); - SkSafeRef(ref); - bitmap->setConfig(config, width, height); + ref->ref(); + SkAlphaType alphaType; + if (bitmap->colorType() != kRGB_565_SkColorType + && bitmap->alphaType() == kOpaque_SkAlphaType) { + // If the original bitmap was set to opaque, keep that setting, unless it + // was 565, which is required to be opaque. + alphaType = kOpaque_SkAlphaType; + } else { + // Otherwise respect the premultiplied request. + alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + } + bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType)); + // FIXME: Skia thinks of an SkPixelRef as having a constant SkImageInfo (except for + // its alphatype), so it would make more sense from Skia's perspective to create a + // new SkPixelRef. That said, libhwui uses the pointer to the SkPixelRef as a key + // for its cache, so it won't realize this is the same Java Bitmap. + SkImageInfo& info = const_cast<SkImageInfo&>(ref->info()); + // Use the updated from the SkBitmap, which may have corrected an invalid alphatype. + // (e.g. 565 non-opaque) + info = bitmap->info(); bitmap->setPixelRef(ref); // notifyPixelsChanged will increment the generation ID even though the actual pixel data // hasn't been touched. This signals the renderer that the bitmap (including width, height, - // and config) has changed. + // colortype and alphatype) has changed. ref->notifyPixelsChanged(); - SkSafeUnref(ref); + ref->unref(); } // These must match the int values in Bitmap.java @@ -489,28 +515,29 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { android::Parcel* p = android::parcelForJavaObject(env, parcel); - const bool isMutable = p->readInt32() != 0; - const SkBitmap::Config config = (SkBitmap::Config)p->readInt32(); - const int width = p->readInt32(); - const int height = p->readInt32(); - const int rowBytes = p->readInt32(); - const int density = p->readInt32(); - - if (SkBitmap::kARGB_8888_Config != config && - SkBitmap::kRGB_565_Config != config && - SkBitmap::kARGB_4444_Config != config && - SkBitmap::kIndex8_Config != config && - SkBitmap::kA8_Config != config) { - SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config); + const bool isMutable = p->readInt32() != 0; + const SkColorType colorType = (SkColorType)p->readInt32(); + const SkAlphaType alphaType = (SkAlphaType)p->readInt32(); + const int width = p->readInt32(); + const int height = p->readInt32(); + const int rowBytes = p->readInt32(); + const int density = p->readInt32(); + + if (kN32_SkColorType != colorType && + kRGB_565_SkColorType != colorType && + kARGB_4444_SkColorType != colorType && + kIndex_8_SkColorType != colorType && + kAlpha_8_SkColorType != colorType) { + SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType); return NULL; } SkBitmap* bitmap = new SkBitmap; - bitmap->setConfig(config, width, height, rowBytes); + bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes); SkColorTable* ctable = NULL; - if (config == SkBitmap::kIndex8_Config) { + if (colorType == kIndex_8_SkColorType) { int count = p->readInt32(); if (count > 0) { size_t size = count * sizeof(SkPMColor); @@ -561,13 +588,14 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, android::Parcel* p = android::parcelForJavaObject(env, parcel); p->writeInt32(isMutable); - p->writeInt32(bitmap->config()); + p->writeInt32(bitmap->colorType()); + p->writeInt32(bitmap->alphaType()); p->writeInt32(bitmap->width()); p->writeInt32(bitmap->height()); p->writeInt32(bitmap->rowBytes()); p->writeInt32(density); - if (bitmap->config() == SkBitmap::kIndex8_Config) { + if (bitmap->colorType() == kIndex_8_SkColorType) { SkColorTable* ctable = bitmap->getColorTable(); if (ctable != NULL) { int count = ctable->count(); @@ -799,7 +827,7 @@ static JNINativeMethod gBitmapMethods[] = { (void*)Bitmap_copy }, { "nativeDestructor", "(J)V", (void*)Bitmap_destructor }, { "nativeRecycle", "(J)Z", (void*)Bitmap_recycle }, - { "nativeReconfigure", "(JIIII)V", (void*)Bitmap_reconfigure }, + { "nativeReconfigure", "(JIIIIZ)V", (void*)Bitmap_reconfigure }, { "nativeCompress", "(JIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress }, { "nativeErase", "(JI)V", (void*)Bitmap_erase }, diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 7aa241a..86ed677 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -2,11 +2,8 @@ #include "BitmapFactory.h" #include "NinePatchPeeker.h" -#include "SkData.h" #include "SkFrontBufferedStream.h" #include "SkImageDecoder.h" -#include "SkImageRef_ashmem.h" -#include "SkImageRef_GlobalPool.h" #include "SkMath.h" #include "SkPixelRef.h" #include "SkStream.h" @@ -32,8 +29,6 @@ jfieldID gOptions_configFieldID; jfieldID gOptions_premultipliedFieldID; jfieldID gOptions_mutableFieldID; jfieldID gOptions_ditherFieldID; -jfieldID gOptions_purgeableFieldID; -jfieldID gOptions_shareableFieldID; jfieldID gOptions_preferQualityOverSpeedFieldID; jfieldID gOptions_scaledFieldID; jfieldID gOptions_densityFieldID; @@ -90,14 +85,6 @@ jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { return jstr; } -static bool optionsPurgeable(JNIEnv* env, jobject options) { - return options != NULL && env->GetBooleanField(options, gOptions_purgeableFieldID); -} - -static bool optionsShareable(JNIEnv* env, jobject options) { - return options != NULL && env->GetBooleanField(options, gOptions_shareableFieldID); -} - static bool optionsJustBounds(JNIEnv* env, jobject options) { return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID); } @@ -125,28 +112,6 @@ static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { } } -static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream, - int sampleSize, bool ditherImage) { - - SkImageInfo bitmapInfo; - if (!bitmap->asImageInfo(&bitmapInfo)) { - ALOGW("bitmap has unknown configuration so no memory has been allocated"); - return NULL; - } - - SkImageRef* pr; - // only use ashmem for large images, since mmaps come at a price - if (bitmap->getSize() >= 32 * 1024) { - pr = new SkImageRef_ashmem(bitmapInfo, stream, sampleSize); - } else { - pr = new SkImageRef_GlobalPool(bitmapInfo, stream, sampleSize); - } - pr->setDitherImage(ditherImage); - bitmap->setPixelRef(pr)->unref(); - pr->isOpaque(bitmap); - return pr; -} - static SkColorType colorTypeForScaledOutput(SkColorType colorType) { switch (colorType) { case kUnknown_SkColorType: @@ -230,21 +195,17 @@ private: const unsigned int mSize; }; -// since we "may" create a purgeable imageref, we require the stream be ref'able -// i.e. dynamically allocated, since its lifetime may exceed the current stack -// frame. static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, - jobject options, bool allowPurgeable, bool forcePurgeable = false) { + jobject options) { int sampleSize = 1; - SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; + SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; bool doDither = true; bool isMutable = false; float scale = 1.0f; - bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); bool preferQualityOverSpeed = false; bool requireUnpremultiplied = false; @@ -253,7 +214,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { - mode = SkImageDecoder::kDecodeBounds_Mode; + decodeMode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on @@ -281,7 +242,6 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } const bool willScale = scale != 1.0f; - isPurgeable &= !willScale; SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (decoder == NULL) { @@ -312,8 +272,6 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding NinePatchPeeker peeker(decoder); decoder->setPeeker(&peeker); - SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode; - JavaPixelAllocator javaAllocator(env); RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); @@ -354,7 +312,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding int scaledWidth = decodingBitmap.width(); int scaledHeight = decodingBitmap.height(); - if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { + if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } @@ -368,7 +326,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } // if we're in justBounds mode, return now (skip the java bitmap) - if (mode == SkImageDecoder::kDecodeBounds_Mode) { + if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) { return NULL; } @@ -428,7 +386,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. - outputBitmap->setConfig(SkImageInfo::Make(scaledWidth, scaledHeight, + outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType())); if (!outputBitmap->allocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); @@ -460,21 +418,15 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } } - SkPixelRef* pr; - if (isPurgeable) { - pr = installPixelRef(outputBitmap, stream, sampleSize, doDither); - } else { - // if we get here, we're in kDecodePixels_Mode and will therefore - // already have a pixelref installed. - pr = outputBitmap->pixelRef(); - } - if (pr == NULL) { + // if we get here, we're in kDecodePixels_Mode and will therefore + // already have a pixelref installed. + if (outputBitmap->pixelRef() == NULL) { return nullObjectReturn("Got null SkPixelRef"); } if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) - pr->setImmutable(); + outputBitmap->setImmutable(); } // detach bitmap from its autodeleter, since we want to own it now @@ -513,8 +465,7 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA SkAutoTUnref<SkStreamRewindable> bufferedStream( SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER)); SkASSERT(bufferedStream.get() != NULL); - // for now we don't allow purgeable with java inputstreams - bitmap = doDecode(env, bufferedStream, padding, options, false, false); + bitmap = doDecode(env, bufferedStream, padding, options); } return bitmap; } @@ -543,76 +494,33 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file, SkFILEStream::kCallerRetains_Ownership)); - SkAutoTUnref<SkStreamRewindable> stream; - - // Retain the old behavior of allowing purgeable if both purgeable and - // shareable are set to true. - bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions) - && optionsShareable(env, bitmapFactoryOptions); - if (isPurgeable) { - // Copy the stream, so the image can be decoded multiple times without - // continuing to modify the original file descriptor. - // Copy beginning from the current position. - const size_t fileSize = fileStream->getLength() - fileStream->getPosition(); - void* buffer = sk_malloc_flags(fileSize, 0); - if (buffer == NULL) { - return nullObjectReturn("Could not make a copy for ashmem"); - } - - SkAutoTUnref<SkData> data(SkData::NewFromMalloc(buffer, fileSize)); + // Use a buffered stream. Although an SkFILEStream can be rewound, this + // ensures that SkImageDecoder::Factory never rewinds beyond the + // current position of the file descriptor. + SkAutoTUnref<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream, + BYTES_TO_BUFFER)); - if (fileStream->read(buffer, fileSize) != fileSize) { - return nullObjectReturn("Could not read the file."); - } - - stream.reset(new SkMemoryStream(data)); - } else { - // Use a buffered stream. Although an SkFILEStream can be rewound, this - // ensures that SkImageDecoder::Factory never rewinds beyond the - // current position of the file descriptor. - stream.reset(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER)); - } - - return doDecode(env, stream, padding, bitmapFactoryOptions, isPurgeable); + return doDecode(env, stream, padding, bitmapFactoryOptions); } static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset, jobject padding, jobject options) { - SkStreamRewindable* stream; Asset* asset = reinterpret_cast<Asset*>(native_asset); - bool forcePurgeable = optionsPurgeable(env, options); - if (forcePurgeable) { - // if we could "ref/reopen" the asset, we may not need to copy it here - // and we could assume optionsShareable, since assets are always RO - stream = CopyAssetToStream(asset); - if (stream == NULL) { - return NULL; - } - } else { - // since we know we'll be done with the asset when we return, we can - // just use a simple wrapper - stream = new AssetStreamAdaptor(asset, - AssetStreamAdaptor::kNo_OwnAsset, - AssetStreamAdaptor::kNo_HasMemoryBase); - } - SkAutoUnref aur(stream); - return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable); + // since we know we'll be done with the asset when we return, we can + // just use a simple wrapper + SkAutoTUnref<SkStreamRewindable> stream(new AssetStreamAdaptor(asset, + AssetStreamAdaptor::kNo_OwnAsset, AssetStreamAdaptor::kNo_HasMemoryBase)); + return doDecode(env, stream, padding, options); } static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, jint offset, jint length, jobject options) { - /* If optionsShareable() we could decide to just wrap the java array and - share it, but that means adding a globalref to the java array object - and managing its lifetime. For now we just always copy the array's data - if optionsPurgeable(), unless we're just decoding bounds. - */ - bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); AutoJavaByteArray ar(env, byteArray); - SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); + SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, false); SkAutoUnref aur(stream); - return doDecode(env, stream, NULL, options, purgeable); + return doDecode(env, stream, NULL, options); } static void nativeRequestCancel(JNIEnv*, jobject joptions) { @@ -676,8 +584,6 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { gOptions_premultipliedFieldID = getFieldIDCheck(env, options_class, "inPremultiplied", "Z"); gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z"); gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z"); - gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z"); - gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z"); gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class, "inPreferQualityOverSpeed", "Z"); gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z"); diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp index ef57e3d..d17f46c 100644 --- a/core/jni/android/graphics/Camera.cpp +++ b/core/jni/android/graphics/Camera.cpp @@ -3,6 +3,8 @@ #include "SkCamera.h" +#include "GraphicsJNI.h" + static jfieldID gNativeInstanceFieldID; static void Camera_constructor(JNIEnv* env, jobject obj) { @@ -93,7 +95,7 @@ static void Camera_getMatrix(JNIEnv* env, jobject obj, jlong matrixHandle) { } static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) { - SkCanvas* native_canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle); jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); v->applyToCanvas((SkCanvas*)native_canvas); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 432a615..9e09280 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -19,12 +19,14 @@ #include <android_runtime/AndroidRuntime.h> #include "SkCanvas.h" +#include "SkClipStack.h" #include "SkDevice.h" +#include "SkDeque.h" #include "SkDrawFilter.h" #include "SkGraphics.h" -#include "SkImageRef_GlobalPool.h" #include "SkPorterDuff.h" #include "SkShader.h" +#include "SkTArray.h" #include "SkTemplates.h" #ifdef USE_MINIKIN @@ -42,21 +44,6 @@ #include <utils/Log.h> -static uint32_t get_thread_msec() { -#if defined(HAVE_POSIX_CLOCKS) - struct timespec tm; - - clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); - - return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; -#else - struct timeval tv; - - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000LL + tv.tv_usec / 1000; -#endif -} - namespace android { class ClipCopier : public SkCanvas::ClipVisitor { @@ -77,6 +64,155 @@ private: SkCanvas* m_dstCanvas; }; +// Holds an SkCanvas reference plus additional native data. +class NativeCanvasWrapper { +private: + struct SaveRec { + int saveCount; + SkCanvas::SaveFlags saveFlags; + }; + +public: + NativeCanvasWrapper(SkCanvas* canvas) + : mCanvas(canvas) + , mSaveStack(NULL) { + SkASSERT(canvas); + } + + ~NativeCanvasWrapper() { + delete mSaveStack; + } + + SkCanvas* getCanvas() const { + return mCanvas.get(); + } + + void setCanvas(SkCanvas* canvas) { + SkASSERT(canvas); + mCanvas.reset(canvas); + + delete mSaveStack; + mSaveStack = NULL; + } + + int save(SkCanvas::SaveFlags flags) { + int count = mCanvas->save(); + recordPartialSave(flags); + return count; + } + + int saveLayer(const SkRect* bounds, const SkPaint* paint, + SkCanvas::SaveFlags flags) { + int count = mCanvas->saveLayer(bounds, paint, + static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag)); + recordPartialSave(flags); + return count; + } + + int saveLayerAlpha(const SkRect* bounds, U8CPU alpha, + SkCanvas::SaveFlags flags) { + int count = mCanvas->saveLayerAlpha(bounds, alpha, + static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag)); + recordPartialSave(flags); + return count; + } + + void restore() { + const SaveRec* rec = (NULL == mSaveStack) + ? NULL + : static_cast<SaveRec*>(mSaveStack->back()); + int currentSaveCount = mCanvas->getSaveCount() - 1; + SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount); + + if (NULL == rec || rec->saveCount != currentSaveCount) { + // Fast path - no record for this frame. + mCanvas->restore(); + return; + } + + bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag); + bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag); + + SkMatrix savedMatrix; + if (preserveMatrix) { + savedMatrix = mCanvas->getTotalMatrix(); + } + + SkTArray<SkClipStack::Element> savedClips; + if (preserveClip) { + saveClipsForFrame(savedClips, currentSaveCount); + } + + mCanvas->restore(); + + if (preserveMatrix) { + mCanvas->setMatrix(savedMatrix); + } + + if (preserveClip && !savedClips.empty()) { + applyClips(savedClips); + } + + mSaveStack->pop_back(); + } + +private: + void recordPartialSave(SkCanvas::SaveFlags flags) { + // A partial save is a save operation which doesn't capture the full canvas state. + // (either kMatrix_SaveFlags or kClip_SaveFlag is missing). + + // Mask-out non canvas state bits. + flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag); + + if (SkCanvas::kMatrixClip_SaveFlag == flags) { + // not a partial save. + return; + } + + if (NULL == mSaveStack) { + mSaveStack = new SkDeque(sizeof(struct SaveRec), 8); + } + + SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back()); + // Store the save counter in the SkClipStack domain. + // (0-based, equal to the number of save ops on the stack). + rec->saveCount = mCanvas->getSaveCount() - 1; + rec->saveFlags = flags; + } + + void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, + int frameSaveCount) { + SkClipStack::Iter clipIterator(*mCanvas->getClipStack(), + SkClipStack::Iter::kTop_IterStart); + while (const SkClipStack::Element* elem = clipIterator.next()) { + if (elem->getSaveCount() < frameSaveCount) { + // done with the current frame. + break; + } + SkASSERT(elem->getSaveCount() == frameSaveCount); + clips.push_back(*elem); + } + } + + void applyClips(const SkTArray<SkClipStack::Element>& clips) { + ClipCopier clipCopier(mCanvas); + + // The clip stack stores clips in device space. + SkMatrix origMatrix = mCanvas->getTotalMatrix(); + mCanvas->resetMatrix(); + + // We pushed the clips in reverse order. + for (int i = clips.count() - 1; i >= 0; --i) { + clips[i].replay(&clipCopier); + } + + mCanvas->setMatrix(origMatrix); + } + + SkAutoTUnref<SkCanvas> mCanvas; + SkDeque* mSaveStack; // lazily allocated, tracks partial saves. +}; + // Returns true if the SkCanvas's clip is non-empty. static jboolean hasNonEmptyClip(const SkCanvas& canvas) { bool emptyClip = canvas.isClipEmpty(); @@ -85,28 +221,35 @@ static jboolean hasNonEmptyClip(const SkCanvas& canvas) { class SkCanvasGlue { public: + // Get the native wrapper for a given handle. + static inline NativeCanvasWrapper* getNativeWrapper(jlong nativeHandle) { + SkASSERT(nativeHandle); + return reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + } - static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - canvas->unref(); + // Get the SkCanvas for a given native handle. + static inline SkCanvas* getNativeCanvas(jlong nativeHandle) { + NativeCanvasWrapper* wrapper = getNativeWrapper(nativeHandle); + SkCanvas* canvas = wrapper->getCanvas(); + SkASSERT(canvas); + + return canvas; } - static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + // Construct an SkCanvas from the bitmap. + static SkCanvas* createCanvas(SkBitmap* bitmap) { if (bitmap) { - return reinterpret_cast<jlong>(new SkCanvas(*bitmap)); - } else { - // Create an empty bitmap device to prevent callers from crashing - // if they attempt to draw into this canvas. - SkBitmap emptyBitmap; - return reinterpret_cast<jlong>(new SkCanvas(emptyBitmap)); + return SkNEW_ARGS(SkCanvas, (*bitmap)); } + + // Create an empty bitmap device to prevent callers from crashing + // if they attempt to draw into this canvas. + SkBitmap emptyBitmap; + return new SkCanvas(emptyBitmap); } - static void copyCanvasState(JNIEnv* env, jobject clazz, - jlong srcCanvasHandle, jlong dstCanvasHandle) { - SkCanvas* srcCanvas = reinterpret_cast<SkCanvas*>(srcCanvasHandle); - SkCanvas* dstCanvas = reinterpret_cast<SkCanvas*>(dstCanvasHandle); + // Copy the canvas matrix & clip state. + static void copyCanvasState(SkCanvas* srcCanvas, SkCanvas* dstCanvas) { if (srcCanvas && dstCanvas) { dstCanvas->setMatrix(srcCanvas->getTotalMatrix()); if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) { @@ -116,10 +259,44 @@ public: } } + // Native JNI handlers + static void finalizer(JNIEnv* env, jobject clazz, jlong nativeHandle) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + delete wrapper; + } + + // Native wrapper constructor used by Canvas(Bitmap) + static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { + // No check - 0 is a valid bitmapHandle. + SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkCanvas* canvas = createCanvas(bitmap); + + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Native wrapper constructor used by Canvas(native_canvas) + static jlong initCanvas(JNIEnv* env, jobject, jlong canvasHandle) { + SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Set the given bitmap as the new draw target (wrapped in a new SkCanvas), + // optionally copying canvas matrix & clip state. + static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jboolean copyState) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(canvasHandle); + SkCanvas* newCanvas = createCanvas(reinterpret_cast<SkBitmap*>(bitmapHandle)); + NPE_CHECK_RETURN_VOID(env, newCanvas); + + if (copyState == JNI_TRUE) { + copyCanvasState(wrapper->getCanvas(), newCanvas); + } + + // setCanvas() unrefs the old canvas. + wrapper->setCanvas(newCanvas); + } static void freeCaches(JNIEnv* env, jobject) { - // these are called in no particular order - SkImageRef_GlobalPool::SetRAMUsed(0); SkGraphics::PurgeFontCache(); } @@ -127,146 +304,107 @@ public: TextLayoutEngine::getInstance().purgeCaches(); } - static jboolean isOpaque(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); + static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); bool result = canvas->getDevice()->accessBitmap(false).isOpaque(); return result ? JNI_TRUE : JNI_FALSE; } - static jint getWidth(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); + static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); int width = canvas->getDevice()->accessBitmap(false).width(); return static_cast<jint>(width); } - static jint getHeight(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); + static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); int height = canvas->getDevice()->accessBitmap(false).height(); return static_cast<jint>(height); } - static jint saveAll(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->save(); - return static_cast<jint>(result); - } - - static jint save(JNIEnv* env, jobject jcanvas, jint flagsHandle) { + static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) { + NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle); SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle); - NPE_CHECK_RETURN_ZERO(env, jcanvas); - int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->save(flags); - return static_cast<jint>(result); + return static_cast<jint>(wrapper->save(flags)); } - static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds, - jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); - SkRect* bounds_ = NULL; - SkRect storage; - if (bounds != NULL) { - GraphicsJNI::jrectf_to_rect(env, bounds, &storage); - bounds_ = &storage; - } - return canvas->saveLayer(bounds_, paint, static_cast<SkCanvas::SaveFlags>(flags)); - } - - static jint saveLayer4F(JNIEnv* env, jobject, jlong canvasHandle, - jfloat l, jfloat t, jfloat r, jfloat b, - jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, + jfloat l, jfloat t, jfloat r, jfloat b, + jlong paintHandle, jint flagsHandle) { + NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle); SkRect bounds; bounds.set(l, t, r, b); - int result = canvas->saveLayer(&bounds, paint, - static_cast<SkCanvas::SaveFlags>(flags)); - return static_cast<jint>(result); + return static_cast<jint>(wrapper->saveLayer(&bounds, paint, flags)); } static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, - jobject bounds, jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - SkRect* bounds_ = NULL; - SkRect storage; - if (bounds != NULL) { - GraphicsJNI::jrectf_to_rect(env, bounds, &storage); - bounds_ = &storage; - } - int result = canvas->saveLayerAlpha(bounds_, alpha, - static_cast<SkCanvas::SaveFlags>(flags)); - return static_cast<jint>(result); - } - - static jint saveLayerAlpha4F(JNIEnv* env, jobject, jlong canvasHandle, - jfloat l, jfloat t, jfloat r, jfloat b, - jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jfloat l, jfloat t, jfloat r, jfloat b, + jint alpha, jint flagsHandle) { + NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle); + SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle); SkRect bounds; bounds.set(l, t, r, b); - int result = canvas->saveLayerAlpha(&bounds, alpha, - static_cast<SkCanvas::SaveFlags>(flags)); - return static_cast<jint>(result); + return static_cast<jint>(wrapper->saveLayerAlpha(&bounds, alpha, flags)); } - static void restore(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); - if (canvas->getSaveCount() <= 1) { // cannot restore anymore + static void restore(JNIEnv* env, jobject, jlong canvasHandle) { + NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle); + if (wrapper->getCanvas()->getSaveCount() <= 1) { // cannot restore anymore doThrowISE(env, "Underflow in restore"); return; } - canvas->restore(); + wrapper->restore(); } - static jint getSaveCount(JNIEnv* env, jobject jcanvas) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->getSaveCount(); - return static_cast<jint>(result); + static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) { + return static_cast<jint>(getNativeCanvas(canvasHandle)->getSaveCount()); } - static void restoreToCount(JNIEnv* env, jobject jcanvas, jint restoreCount) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); + static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, + jint restoreCount) { + NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle); if (restoreCount < 1) { doThrowIAE(env, "Underflow in restoreToCount"); return; } - canvas->restoreToCount(restoreCount); + + while (wrapper->getCanvas()->getSaveCount() > restoreCount) { + wrapper->restore(); + } } - static void translate(JNIEnv* env, jobject jcanvas, jfloat dx, jfloat dy) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->translate(dx, dy); + static void translate(JNIEnv*, jobject, jlong canvasHandle, + jfloat dx, jfloat dy) { + getNativeCanvas(canvasHandle)->translate(dx, dy); } - static void scale__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->scale(sx, sy); + static void scale__FF(JNIEnv*, jobject, jlong canvasHandle, + jfloat sx, jfloat sy) { + getNativeCanvas(canvasHandle)->scale(sx, sy); } - static void rotate__F(JNIEnv* env, jobject jcanvas, jfloat degrees) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->rotate(degrees); + static void rotate__F(JNIEnv*, jobject, jlong canvasHandle, + jfloat degrees) { + getNativeCanvas(canvasHandle)->rotate(degrees); } - static void skew__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->skew(sx, sy); + static void skew__FF(JNIEnv*, jobject, jlong canvasHandle, + jfloat sx, jfloat sy) { + getNativeCanvas(canvasHandle)->skew(sx, sy); } static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); canvas->concat(*matrix); } static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); if (NULL == matrix) { canvas->resetMatrix(); @@ -275,59 +413,19 @@ public: } } - static jboolean clipRect_FFFF(JNIEnv* env, jobject jcanvas, jfloat left, - jfloat top, jfloat right, jfloat bottom) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); + static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle, + jfloat left, jfloat top, jfloat right, + jfloat bottom, jint op) { SkRect r; r.set(left, top, right, bottom); - SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas); - c->clipRect(r); + SkCanvas* c = getNativeCanvas(canvasHandle); + c->clipRect(r, static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*c); } - static jboolean clipRect_IIII(JNIEnv* env, jobject jcanvas, jint left, - jint top, jint right, jint bottom) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - SkRect r; - r.set(SkIntToScalar(left), SkIntToScalar(top), - SkIntToScalar(right), SkIntToScalar(bottom)); - SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas); - c->clipRect(r); - return hasNonEmptyClip(*c); - } - - static jboolean clipRect_RectF(JNIEnv* env, jobject jcanvas, jobject rectf) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - NPE_CHECK_RETURN_ZERO(env, rectf); - SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas); - SkRect tmp; - c->clipRect(*GraphicsJNI::jrectf_to_rect(env, rectf, &tmp)); - return hasNonEmptyClip(*c); - } - - static jboolean clipRect_Rect(JNIEnv* env, jobject jcanvas, jobject rect) { - NPE_CHECK_RETURN_ZERO(env, jcanvas); - NPE_CHECK_RETURN_ZERO(env, rect); - SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas); - SkRect tmp; - c->clipRect(*GraphicsJNI::jrect_to_rect(env, rect, &tmp)); - return hasNonEmptyClip(*c); - - } - - static jboolean clipRect(JNIEnv* env, jobject, jlong canvasHandle, - jfloat left, jfloat top, jfloat right, jfloat bottom, - jint op) { - SkRect rect; - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - rect.set(left, top, right, bottom); - canvas->clipRect(rect, static_cast<SkRegion::Op>(op)); - return hasNonEmptyClip(*canvas); - } - static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle), static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -335,30 +433,30 @@ public: static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong deviceRgnHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle); - canvas->clipRegion(*deviceRgn, static_cast<SkRegion::Op>(op)); + SkPath rgnPath; + if (deviceRgn->getBoundaryPath(&rgnPath)) { + // The region is specified in device space. + SkMatrix savedMatrix = canvas->getTotalMatrix(); + canvas->resetMatrix(); + canvas->clipPath(rgnPath, static_cast<SkRegion::Op>(op)); + canvas->setMatrix(savedMatrix); + } else { + canvas->clipRect(SkRect::MakeEmpty(), static_cast<SkRegion::Op>(op)); + } return hasNonEmptyClip(*canvas); } static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle)); } - static jboolean quickReject__RectF(JNIEnv* env, jobject, jlong canvasHandle, - jobject rect) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - SkRect rect_; - GraphicsJNI::jrectf_to_rect(env, rect, &rect_); - bool result = canvas->quickReject(rect_); - return result ? JNI_TRUE : JNI_FALSE; - } - static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle)); return result ? JNI_TRUE : JNI_FALSE; } @@ -366,7 +464,7 @@ public: static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; r.set(left, top, right, bottom); bool result = canvas->quickReject(r); @@ -375,45 +473,43 @@ public: static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(0xFF, r, g, b); } static void drawARGB(JNIEnv* env, jobject, jlong canvasHandle, jint a, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(a, r, g, b); } static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle, jint color) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawColor(color); } static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle); canvas->drawColor(color, SkPorterDuff::ToXfermodeMode(mode)); } static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPaint(*paint); } - static void doPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray, - jint offset, jint count, jobject jpaint, - jint modeHandle) { - NPE_CHECK_RETURN_VOID(env, jcanvas); + static void doPoints(JNIEnv* env, jlong canvasHandle, + jfloatArray jptsArray, jint offset, jint count, + jlong paintHandle, jint modeHandle) { NPE_CHECK_RETURN_VOID(env, jptsArray); - NPE_CHECK_RETURN_VOID(env, jpaint); SkCanvas::PointMode mode = static_cast<SkCanvas::PointMode>(modeHandle); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); - const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint); + SkCanvas* canvas = getNativeCanvas(canvasHandle); + const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); AutoJavaFloatArray autoPts(env, jptsArray); float* floats = autoPts.ptr(); @@ -433,86 +529,74 @@ public: pts[i].set(src[0], src[1]); src += 2; } - canvas->drawPoints(mode, count, pts, paint); + canvas->drawPoints(mode, count, pts, *paint); } - static void drawPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray, - jint offset, jint count, jobject jpaint) { - doPoints(env, jcanvas, jptsArray, offset, count, jpaint, + static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle, + jfloatArray jptsArray, jint offset, + jint count, jlong paintHandle) { + doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle, SkCanvas::kPoints_PointMode); } - static void drawLines(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray, - jint offset, jint count, jobject jpaint) { - doPoints(env, jcanvas, jptsArray, offset, count, jpaint, + static void drawLines(JNIEnv* env, jobject, jlong canvasHandle, + jfloatArray jptsArray, jint offset, jint count, + jlong paintHandle) { + doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle, SkCanvas::kLines_PointMode); } - static void drawPoint(JNIEnv* env, jobject jcanvas, jfloat x, jfloat y, - jobject jpaint) { - NPE_CHECK_RETURN_VOID(env, jcanvas); - NPE_CHECK_RETURN_VOID(env, jpaint); - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas); - const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint); - - canvas->drawPoint(x, y, paint); + static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y, + jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); + const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + canvas->drawPoint(x, y, *paint); } static void drawLine__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY, jfloat stopX, jfloat stopY, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawLine(startX, startY, stopX, stopY, *paint); } - static void drawRect__RectFPaint(JNIEnv* env, jobject, jlong canvasHandle, - jobject rect, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); - SkRect rect_; - GraphicsJNI::jrectf_to_rect(env, rect, &rect_); - canvas->drawRect(rect_, *paint); - } - static void drawRect__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawRectCoords(left, top, right, bottom, *paint); } - static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, - jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); - SkRect oval; - GraphicsJNI::jrectf_to_rect(env, joval, &oval); + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawOval(oval, *paint); } static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy, jfloat radius, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawCircle(cx, cy, radius, *paint); } - static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, - jfloat startAngle, jfloat sweepAngle, - jboolean useCenter, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, jboolean useCenter, + jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); - SkRect oval; - GraphicsJNI::jrectf_to_rect(env, joval, &oval); + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawArc(oval, startAngle, sweepAngle, useCenter, *paint); } static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawRoundRect(rect, rx, ry, *paint); @@ -520,7 +604,7 @@ public: static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPath(*path, *paint); @@ -531,7 +615,7 @@ public: jfloat left, jfloat top, jlong paintHandle, jint canvasDensity, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -591,7 +675,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRectF, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -604,7 +688,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRect, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -616,14 +700,14 @@ public: static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, jintArray jcolors, jint offset, jint stride, jfloat x, jfloat y, jint width, jint height, - jboolean hasAlpha, jlong paintHandle) - { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jboolean hasAlpha, jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + SkImageInfo info = SkImageInfo::Make(width, height, + hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType, + kPremul_SkAlphaType); SkBitmap bitmap; - bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : - SkBitmap::kRGB_565_Config, width, height); - if (!bitmap.allocPixels()) { + if (!bitmap.allocPixels(info)) { return; } @@ -638,7 +722,7 @@ public: static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, jlong matrixHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -649,7 +733,7 @@ public: jlong bitmapHandle, jint meshWidth, jint meshHeight, jfloatArray jverts, jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -759,7 +843,7 @@ public: jintArray jcolors, jint colorIndex, jshortArray jindices, jint indexIndex, jint indexCount, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -797,26 +881,26 @@ public: static void drawText___CIIFFIPaintTypeface(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, - jfloat x, jfloat y, jint flags, + jfloat x, jfloat y, jint bidiFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); - drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint, typeface); + drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void drawText__StringIIFFIPaintTypeface(JNIEnv* env, jobject, jlong canvasHandle, jstring text, jint start, jint end, - jfloat x, jfloat y, jint flags, + jfloat x, jfloat y, jint bidiFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, NULL); - drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint, typeface); + drawTextWithGlyphs(canvas, textArray, start, end, x, y, bidiFlags, paint, typeface); env->ReleaseStringChars(text, textArray); } @@ -866,24 +950,25 @@ public: static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray, int start, int end, - jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) { + jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) { jint count = end - start; - drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, flags, paint, typeface); + drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, bidiFlags, paint, + typeface); } static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray, int start, int count, int contextCount, - jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) { + jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) { #ifdef USE_MINIKIN Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); layout.doLayout(textArray, start, count, contextCount, css); drawGlyphsToSkia(canvas, paint, layout, x, y); #else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, - textArray, start, count, contextCount, flags); + textArray, start, count, contextCount, bidiFlags); if (value == NULL) { return; } @@ -894,7 +979,8 @@ public: x -= value->getTotalAdvance(); } paint->setTextAlign(SkPaint::kLeft_Align); - doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint); + doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), + x, y, paint); doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint); paint->setTextAlign(align); #endif @@ -905,42 +991,37 @@ public: #define kStdUnderline_Offset (1.0f / 9.0f) #define kStdUnderline_Thickness (1.0f / 18.0f) -static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length, SkPaint* paint) { - uint32_t flags; - SkDrawFilter* drawFilter = canvas->getDrawFilter(); - if (drawFilter) { - SkPaint paintCopy(*paint); - drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); - flags = paintCopy.getFlags(); - } else { - flags = paint->getFlags(); - } - if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { - SkScalar left = x; - SkScalar right = x + length; - float textSize = paint->getTextSize(); - float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); - if (flags & SkPaint::kUnderlineText_Flag) { - SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; - canvas->drawRectCoords(left, top, right, bottom, *paint); + static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length, + SkPaint* paint) { + uint32_t flags; + SkDrawFilter* drawFilter = canvas->getDrawFilter(); + if (drawFilter) { + SkPaint paintCopy(*paint); + drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); + flags = paintCopy.getFlags(); + } else { + flags = paint->getFlags(); } - if (flags & SkPaint::kStrikeThruText_Flag) { - SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; - canvas->drawRectCoords(left, top, right, bottom, *paint); + if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + SkScalar left = x; + SkScalar right = x + length; + float textSize = paint->getTextSize(); + float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); + if (flags & SkPaint::kUnderlineText_Flag) { + SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; + canvas->drawRectCoords(left, top, right, bottom, *paint); + } + if (flags & SkPaint::kStrikeThruText_Flag) { + SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; + canvas->drawRectCoords(left, top, right, bottom, *paint); + } } } -} - - static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count, - jfloat x, jfloat y, int flags, SkPaint* paint) { - // Beware: this needs Glyph encoding (already done on the Paint constructor) - canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint); - } static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray, - int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) { + int index, int count, jfloat x, jfloat y, SkPaint* paint) { SkPoint* posPtr = new SkPoint[count]; for (int indx = 0; indx < count; indx++) { posPtr[indx].fX = x + posArray[indx * 2]; @@ -950,40 +1031,42 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l delete[] posPtr; } - static void drawTextRun___CIIIIFFIPaintTypeface( - JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, - jint count, jint contextIndex, jint contextCount, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + static void drawTextRun___CIIIIFFZPaintTypeface( + JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, + jint count, jint contextIndex, jint contextCount, + jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; jchar* chars = env->GetCharArrayElements(text, NULL); drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex, - count, contextCount, x, y, dirFlags, paint, typeface); + count, contextCount, x, y, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, chars, JNI_ABORT); } - static void drawTextRun__StringIIIIFFIPaintTypeface( - JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, - jint end, jint contextStart, jint contextEnd, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + static void drawTextRun__StringIIIIFFZPaintTypeface( + JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, + jint end, jint contextStart, jint contextEnd, + jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; jint count = end - start; jint contextCount = contextEnd - contextStart; const jchar* chars = env->GetStringChars(text, NULL); drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart, - count, contextCount, x, y, dirFlags, paint, typeface); + count, contextCount, x, y, bidiFlags, paint, typeface); env->ReleaseStringChars(text, chars); } static void drawPosText___CII_FPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL; jsize textCount = text ? env->GetArrayLength(text) : NULL; @@ -1014,7 +1097,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l jlong canvasHandle, jstring text, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const void* text_ = text ? env->GetStringChars(text, NULL) : NULL; int byteLength = text ? env->GetStringLength(text) : 0; @@ -1041,29 +1124,82 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l delete[] posPtr; } +#ifdef USE_MINIKIN + class DrawTextOnPathFunctor { + public: + DrawTextOnPathFunctor(const Layout& layout, SkCanvas* canvas, float hOffset, + float vOffset, SkPaint* paint, SkPath* path) + : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset), + paint(paint), path(path) { + } + void operator()(size_t start, size_t end) { + uint16_t glyphs[1]; + for (size_t i = start; i < end; i++) { + glyphs[0] = layout.getGlyphId(i); + float x = hOffset + layout.getX(i); + float y = vOffset + layout.getY(i); + canvas->drawTextOnPathHV(glyphs, sizeof(glyphs), *path, x, y, *paint); + } + } + private: + const Layout& layout; + SkCanvas* canvas; + float hOffset; + float vOffset; + SkPaint* paint; + SkPath* path; + }; +#endif + + static void doDrawTextOnPath(SkPaint* paint, const jchar* text, int count, int bidiFlags, + float hOffset, float vOffset, SkPath* path, SkCanvas* canvas, TypefaceImpl* typeface) { +#ifdef USE_MINIKIN + Layout layout; + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); + hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path); + // Set align to left for drawing, as we don't want individual + // glyphs centered or right-aligned; the offset above takes + // care of all alignment. + SkPaint::Align align = paint->getTextAlign(); + paint->setTextAlign(SkPaint::kLeft_Align); + + DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paint, path); + MinikinUtils::forFontRun(layout, paint, f); + paint->setTextAlign(align); +#else + TextLayout::drawTextOnPath(paint, text, count, bidiFlags, hOffset, vOffset, path, canvas); +#endif + } + static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, - jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle, + jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); - TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset, - path, canvas); + doDrawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset, + path, canvas, typeface); env->ReleaseCharArrayElements(text, textArray, 0); } static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jstring text, jlong pathHandle, - jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle, + jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + const jchar* text_ = env->GetStringChars(text, NULL); int count = env->GetStringLength(text); - TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset, - path, canvas); + doDrawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset, + path, canvas, typeface); env->ReleaseStringChars(text, text_); } @@ -1096,7 +1232,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; SkIRect ir; bool result = getHardClipBounds(canvas, &r); @@ -1112,7 +1248,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); *matrix = canvas->getTotalMatrix(); } @@ -1120,35 +1256,24 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static JNINativeMethod gCanvasMethods[] = { {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer}, - {"initRaster","(J)J", (void*) SkCanvasGlue::initRaster}, - {"copyNativeCanvasState","(JJ)V", (void*) SkCanvasGlue::copyCanvasState}, - {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque}, - {"getWidth","()I", (void*) SkCanvasGlue::getWidth}, - {"getHeight","()I", (void*) SkCanvasGlue::getHeight}, - {"save","()I", (void*) SkCanvasGlue::saveAll}, - {"save","(I)I", (void*) SkCanvasGlue::save}, - {"native_saveLayer","(JLandroid/graphics/RectF;JI)I", - (void*) SkCanvasGlue::saveLayer}, - {"native_saveLayer","(JFFFFJI)I", (void*) SkCanvasGlue::saveLayer4F}, - {"native_saveLayerAlpha","(JLandroid/graphics/RectF;II)I", - (void*) SkCanvasGlue::saveLayerAlpha}, - {"native_saveLayerAlpha","(JFFFFII)I", - (void*) SkCanvasGlue::saveLayerAlpha4F}, - {"restore","()V", (void*) SkCanvasGlue::restore}, - {"getSaveCount","()I", (void*) SkCanvasGlue::getSaveCount}, - {"restoreToCount","(I)V", (void*) SkCanvasGlue::restoreToCount}, - {"translate","(FF)V", (void*) SkCanvasGlue::translate}, - {"scale","(FF)V", (void*) SkCanvasGlue::scale__FF}, - {"rotate","(F)V", (void*) SkCanvasGlue::rotate__F}, - {"skew","(FF)V", (void*) SkCanvasGlue::skew__FF}, + {"initRaster", "(J)J", (void*) SkCanvasGlue::initRaster}, + {"initCanvas", "(J)J", (void*) SkCanvasGlue::initCanvas}, + {"native_setBitmap", "(JJZ)V", (void*) SkCanvasGlue::setBitmap}, + {"native_isOpaque","(J)Z", (void*) SkCanvasGlue::isOpaque}, + {"native_getWidth","(J)I", (void*) SkCanvasGlue::getWidth}, + {"native_getHeight","(J)I", (void*) SkCanvasGlue::getHeight}, + {"native_save","(JI)I", (void*) SkCanvasGlue::save}, + {"native_saveLayer","(JFFFFJI)I", (void*) SkCanvasGlue::saveLayer}, + {"native_saveLayerAlpha","(JFFFFII)I", (void*) SkCanvasGlue::saveLayerAlpha}, + {"native_restore","(J)V", (void*) SkCanvasGlue::restore}, + {"native_getSaveCount","(J)I", (void*) SkCanvasGlue::getSaveCount}, + {"native_restoreToCount","(JI)V", (void*) SkCanvasGlue::restoreToCount}, + {"native_translate","(JFF)V", (void*) SkCanvasGlue::translate}, + {"native_scale","(JFF)V", (void*) SkCanvasGlue::scale__FF}, + {"native_rotate","(JF)V", (void*) SkCanvasGlue::rotate__F}, + {"native_skew","(JFF)V", (void*) SkCanvasGlue::skew__FF}, {"native_concat","(JJ)V", (void*) SkCanvasGlue::concat}, {"native_setMatrix","(JJ)V", (void*) SkCanvasGlue::setMatrix}, - {"clipRect","(FFFF)Z", (void*) SkCanvasGlue::clipRect_FFFF}, - {"clipRect","(IIII)Z", (void*) SkCanvasGlue::clipRect_IIII}, - {"clipRect","(Landroid/graphics/RectF;)Z", - (void*) SkCanvasGlue::clipRect_RectF}, - {"clipRect","(Landroid/graphics/Rect;)Z", - (void*) SkCanvasGlue::clipRect_Rect}, {"native_clipRect","(JFFFFI)Z", (void*) SkCanvasGlue::clipRect}, {"native_clipPath","(JJI)Z", (void*) SkCanvasGlue::clipPath}, {"native_clipRegion","(JJI)Z", (void*) SkCanvasGlue::clipRegion}, @@ -1156,8 +1281,6 @@ static JNINativeMethod gCanvasMethods[] = { {"native_getClipBounds","(JLandroid/graphics/Rect;)Z", (void*) SkCanvasGlue::getClipBounds}, {"native_getCTM", "(JJ)V", (void*)SkCanvasGlue::getCTM}, - {"native_quickReject","(JLandroid/graphics/RectF;)Z", - (void*) SkCanvasGlue::quickReject__RectF}, {"native_quickReject","(JJ)Z", (void*) SkCanvasGlue::quickReject__Path}, {"native_quickReject","(JFFFF)Z", (void*)SkCanvasGlue::quickReject__FFFF}, {"native_drawRGB","(JIII)V", (void*) SkCanvasGlue::drawRGB}, @@ -1165,21 +1288,14 @@ static JNINativeMethod gCanvasMethods[] = { {"native_drawColor","(JI)V", (void*) SkCanvasGlue::drawColor__I}, {"native_drawColor","(JII)V", (void*) SkCanvasGlue::drawColor__II}, {"native_drawPaint","(JJ)V", (void*) SkCanvasGlue::drawPaint}, - {"drawPoint", "(FFLandroid/graphics/Paint;)V", - (void*) SkCanvasGlue::drawPoint}, - {"drawPoints", "([FIILandroid/graphics/Paint;)V", - (void*) SkCanvasGlue::drawPoints}, - {"drawLines", "([FIILandroid/graphics/Paint;)V", - (void*) SkCanvasGlue::drawLines}, + {"native_drawPoint", "(JFFJ)V", (void*) SkCanvasGlue::drawPoint}, + {"native_drawPoints", "(J[FIIJ)V", (void*) SkCanvasGlue::drawPoints}, + {"native_drawLines", "(J[FIIJ)V", (void*) SkCanvasGlue::drawLines}, {"native_drawLine","(JFFFFJ)V", (void*) SkCanvasGlue::drawLine__FFFFPaint}, - {"native_drawRect","(JLandroid/graphics/RectF;J)V", - (void*) SkCanvasGlue::drawRect__RectFPaint}, {"native_drawRect","(JFFFFJ)V", (void*) SkCanvasGlue::drawRect__FFFFPaint}, - {"native_drawOval","(JLandroid/graphics/RectF;J)V", - (void*) SkCanvasGlue::drawOval}, + {"native_drawOval","(JFFFFJ)V", (void*) SkCanvasGlue::drawOval}, {"native_drawCircle","(JFFFJ)V", (void*) SkCanvasGlue::drawCircle}, - {"native_drawArc","(JLandroid/graphics/RectF;FFZJ)V", - (void*) SkCanvasGlue::drawArc}, + {"native_drawArc","(JFFFFFFZJ)V", (void*) SkCanvasGlue::drawArc}, {"native_drawRoundRect","(JFFFFFFJ)V", (void*) SkCanvasGlue::drawRoundRect}, {"native_drawPath","(JJJ)V", (void*) SkCanvasGlue::drawPath}, @@ -1201,17 +1317,13 @@ static JNINativeMethod gCanvasMethods[] = { (void*) SkCanvasGlue::drawText___CIIFFIPaintTypeface}, {"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) SkCanvasGlue::drawText__StringIIFFIPaintTypeface}, - {"native_drawTextRun","(J[CIIIIFFIJJ)V", - (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaintTypeface}, - {"native_drawTextRun","(JLjava/lang/String;IIIIFFIJJ)V", - (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaintTypeface}, - {"native_drawPosText","(J[CII[FJ)V", - (void*) SkCanvasGlue::drawPosText___CII_FPaint}, - {"native_drawPosText","(JLjava/lang/String;[FJ)V", - (void*) SkCanvasGlue::drawPosText__String_FPaint}, - {"native_drawTextOnPath","(J[CIIJFFIJ)V", + {"native_drawTextRun","(J[CIIIIFFZJJ)V", + (void*) SkCanvasGlue::drawTextRun___CIIIIFFZPaintTypeface}, + {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V", + (void*) SkCanvasGlue::drawTextRun__StringIIIIFFZPaintTypeface}, + {"native_drawTextOnPath","(J[CIIJFFIJJ)V", (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint}, - {"native_drawTextOnPath","(JLjava/lang/String;JFFIJ)V", + {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint}, {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}, @@ -1236,4 +1348,11 @@ int register_android_graphics_Canvas(JNIEnv* env) { return result; } +} // namespace android + +// GraphicsJNI helper for external clients. +// We keep the implementation here to avoid exposing NativeCanvasWrapper +// externally. +SkCanvas* GraphicsJNI::getNativeCanvas(jlong nativeHandle) { + return android::SkCanvasGlue::getNativeCanvas(nativeHandle); } diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/DrawFilter.cpp index fbfa2ec..3275875 100644 --- a/core/jni/android/graphics/DrawFilter.cpp +++ b/core/jni/android/graphics/DrawFilter.cpp @@ -30,6 +30,35 @@ namespace android { +// Custom version of SkPaintFlagsDrawFilter that also calls setFilterLevel. +class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter { +public: + CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags, + SkPaint::FilterLevel desiredLevel) + : SkPaintFlagsDrawFilter(clearFlags, setFlags) + , fDesiredLevel(desiredLevel) { + } + + virtual bool filter(SkPaint* paint, Type type) { + SkPaintFlagsDrawFilter::filter(paint, type); + paint->setFilterLevel(fDesiredLevel); + return true; + } + +private: + const SkPaint::FilterLevel fDesiredLevel; +}; + +// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it. +static inline bool hadFiltering(jint& flags) { + // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. + static const uint32_t sFilterBitmapFlag = 0x02; + + const bool result = (flags & sFilterBitmapFlag) != 0; + flags &= ~sFilterBitmapFlag; + return result; +} + class SkDrawFilterGlue { public: @@ -40,12 +69,25 @@ public: static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz, jint clearFlags, jint setFlags) { - // trim off any out-of-range bits - clearFlags &= SkPaint::kAllFlags; - setFlags &= SkPaint::kAllFlags; - if (clearFlags | setFlags) { - SkDrawFilter* filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags); + // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no + // longer has a Skia equivalent flag (instead it corresponds to + // calling setFilterLevel), and keep track of which group(s), if + // any, had the flag set. + const bool turnFilteringOn = hadFiltering(setFlags); + const bool turnFilteringOff = hadFiltering(clearFlags); + + SkDrawFilter* filter; + if (turnFilteringOn) { + // Turning filtering on overrides turning it off. + filter = new CompatFlagsDrawFilter(clearFlags, setFlags, + SkPaint::kLow_FilterLevel); + } else if (turnFilteringOff) { + filter = new CompatFlagsDrawFilter(clearFlags, setFlags, + SkPaint::kNone_FilterLevel); + } else { + filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags); + } return reinterpret_cast<jlong>(filter); } else { return NULL; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index dce185d..2bd7a28 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -320,7 +320,7 @@ SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) { SkASSERT(canvas); SkASSERT(env->IsInstanceOf(canvas, gCanvas_class)); jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID); - SkCanvas* c = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* c = getNativeCanvas(canvasHandle); SkASSERT(c); return c; } @@ -345,17 +345,6 @@ android::TypefaceImpl* GraphicsJNI::getNativeTypeface(JNIEnv* env, jobject paint return p; } -SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture) -{ - SkASSERT(env); - SkASSERT(picture); - SkASSERT(env->IsInstanceOf(picture, gPicture_class)); - jlong pictureHandle = env->GetLongField(picture, gPicture_nativeInstanceID); - SkPicture* p = reinterpret_cast<SkPicture*>(pictureHandle); - SkASSERT(p); - return p; -} - SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) { SkASSERT(env); @@ -698,7 +687,7 @@ int register_android_graphics_Graphics(JNIEnv* env) "nativeInt", "I"); gCanvas_class = make_globalref(env, "android/graphics/Canvas"); - gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "J"); + gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvasWrapper", "J"); gPaint_class = make_globalref(env, "android/graphics/Paint"); gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "J"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index db7b6d9..ad174f7 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -14,7 +14,6 @@ class SkBitmapRegionDecoder; class SkCanvas; class SkPaint; -class SkPicture; class GraphicsJNI { public: @@ -45,11 +44,11 @@ public: static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point); static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf); + static SkCanvas* getNativeCanvas(jlong nativeHandle); static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas); static SkPaint* getNativePaint(JNIEnv*, jobject paint); static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint); static SkBitmap* getNativeBitmap(JNIEnv*, jobject bitmap); - static SkPicture* getNativePicture(JNIEnv*, jobject picture); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a diff --git a/core/jni/android/graphics/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp index 2113330..b394905 100644 --- a/core/jni/android/graphics/MaskFilter.cpp +++ b/core/jni/android/graphics/MaskFilter.cpp @@ -21,8 +21,7 @@ public: static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) { SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); - SkMaskFilter* filter = SkBlurMaskFilter::Create( - (SkBlurMaskFilter::BlurStyle)blurStyle, sigma); + SkMaskFilter* filter = SkBlurMaskFilter::Create((SkBlurStyle)blurStyle, sigma); ThrowIAE_IfNull(env, filter); return reinterpret_cast<jlong>(filter); } diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index a9360ea..fc92d53 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -19,6 +19,7 @@ #include <string> #include "SkPaint.h" +#include "SkPathMeasure.h" #include "minikin/Layout.h" #include "TypefaceImpl.h" #include "MinikinSkia.h" @@ -76,4 +77,20 @@ float MinikinUtils::xOffsetForTextAlign(SkPaint* paint, const Layout& layout) { return 0; } +float MinikinUtils::hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path) { + float align = 0; + switch (paint->getTextAlign()) { + case SkPaint::kCenter_Align: + align = -0.5f; + break; + case SkPaint::kRight_Align: + align = -1; + break; + default: + return 0; + } + SkPathMeasure measure(path, false); + return align * (layout.getAdvance() - measure.getLength()); +} + } diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h index a96c6b1..b2662a1 100644 --- a/core/jni/android/graphics/MinikinUtils.h +++ b/core/jni/android/graphics/MinikinUtils.h @@ -36,6 +36,7 @@ public: static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout); + static float hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path); // f is a functor of type void f(size_t start, size_t end); template <typename F> static void forFontRun(const Layout& layout, SkPaint* paint, F& f) { diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp index 855d267..ab5bdb0 100644 --- a/core/jni/android/graphics/NinePatch.cpp +++ b/core/jni/android/graphics/NinePatch.cpp @@ -119,7 +119,7 @@ public: static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -138,7 +138,7 @@ public: static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 8b11d31..dc30814 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -37,6 +37,7 @@ #include "TextLayout.h" #ifdef USE_MINIKIN +#include <minikin/GraphemeBreak.h> #include <minikin/Layout.h> #include "MinikinSkia.h" #include "MinikinUtils.h" @@ -707,7 +708,7 @@ public: } static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, TypefaceImpl* typeface, const jchar *text, - jint start, jint count, jint contextCount, jint flags, + jint start, jint count, jint contextCount, jboolean isRtl, jfloatArray advances, jint advancesIndex) { NPE_CHECK_RETURN_ZERO(env, paint); NPE_CHECK_RETURN_ZERO(env, text); @@ -729,14 +730,16 @@ public: jfloat* advancesArray = new jfloat[count]; jfloat totalAdvance = 0; + int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; + #ifdef USE_MINIKIN Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); layout.doLayout(text, start, count, contextCount, css); layout.getAdvances(advancesArray); totalAdvance = layout.getAdvance(); #else - TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags, + TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, bidiFlags, advancesArray, &totalAdvance); #endif @@ -747,28 +750,28 @@ public: return totalAdvance; } - static jfloat getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle, + static jfloat getTextRunAdvances___CIIIIZ_FI(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, - jint flags, jfloatArray advances, jint advancesIndex) { + jboolean isRtl, jfloatArray advances, jint advancesIndex) { SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); jfloat result = doTextRunAdvances(env, paint, typeface, textArray + contextIndex, - index - contextIndex, count, contextCount, flags, advances, advancesIndex); + index - contextIndex, count, contextCount, isRtl, advances, advancesIndex); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); return result; } - static jfloat getTextRunAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle, + static jfloat getTextRunAdvances__StringIIIIZ_FI(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, - jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl, jfloatArray advances, jint advancesIndex) { SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, NULL); jfloat result = doTextRunAdvances(env, paint, typeface, textArray + contextStart, - start - contextStart, end - start, contextEnd - contextStart, flags, + start - contextStart, end - start, contextEnd - contextStart, isRtl, advances, advancesIndex); env->ReleaseStringChars(text, textArray); return result; @@ -776,6 +779,11 @@ public: static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start, jint count, jint flags, jint offset, jint opt) { +#ifdef USE_MINIKIN + GraphemeBreak::MoveOpt moveOpt = GraphemeBreak::MoveOpt(opt); + size_t result = GraphemeBreak::getTextRunCursor(text, start, count, offset, moveOpt); + return static_cast<jint>(result); +#else jfloat scalarArray[count]; TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags, @@ -816,24 +824,25 @@ public: } return pos; +#endif } static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text, - jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) { + jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) { SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); - jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags, + jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, dir, offset, cursorOpt); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); return result; } static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, jstring text, - jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) { + jint contextStart, jint contextEnd, jint dir, jint offset, jint cursorOpt) { SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const jchar* textArray = env->GetStringChars(text, NULL); jint result = doTextRunCursor(env, paint, textArray, contextStart, - contextEnd - contextStart, flags, offset, cursorOpt); + contextEnd - contextStart, dir, offset, cursorOpt); env->ReleaseStringChars(text, textArray); return result; } @@ -934,32 +943,62 @@ public: return paint->getLooper() && paint->getLooper()->asABlurShadow(NULL); } - static int breakText(JNIEnv* env, SkPaint& paint, const jchar text[], + static int breakText(JNIEnv* env, const SkPaint& paint, TypefaceImpl* typeface, const jchar text[], int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured, - SkPaint::TextBufferDirection tbd) { + SkPaint::TextBufferDirection textBufferDirection) { + size_t measuredCount = 0; + float measured = 0; + +#ifdef USE_MINIKIN + Layout layout; + std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); + float* advances = new float[count]; + layout.getAdvances(advances); + const bool forwardScan = (textBufferDirection == SkPaint::kForward_TextBufferDirection); + for (int i = 0; i < count; i++) { + // traverse in the given direction + int index = forwardScan ? i : (count - i - 1); + float width = advances[index]; + if (measured + width > maxWidth) { + break; + } + // properly handle clusters when scanning backwards + if (forwardScan || width != 0.0f) { + measuredCount = i + 1; + } + measured += width; + } + delete[] advances; +#else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint, text, 0, count, count, bidiFlags); if (value == NULL) { return 0; } - SkScalar measured; - size_t bytes = paint.breakText(value->getGlyphs(), value->getGlyphsCount() << 1, - maxWidth, &measured, tbd); + SkScalar m; + size_t bytes = paint.breakText(value->getGlyphs(), value->getGlyphsCount() << 1, + maxWidth, &m, textBufferDirection); SkASSERT((bytes & 1) == 0); + measuredCount = bytes >> 1; + measured = SkScalarToFloat(m); +#endif if (jmeasured && env->GetArrayLength(jmeasured) > 0) { AutoJavaFloatArray autoMeasured(env, jmeasured, 1); jfloat* array = autoMeasured.ptr(); - array[0] = SkScalarToFloat(measured); + array[0] = measured; } - return bytes >> 1; + return measuredCount; } - static jint breakTextC(JNIEnv* env, jobject jpaint, jcharArray jtext, + static jint breakTextC(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jcharArray jtext, jint index, jint count, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) { - NPE_CHECK_RETURN_ZERO(env, jpaint); NPE_CHECK_RETURN_ZERO(env, jtext); + SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + SkPaint::TextBufferDirection tbd; if (count < 0) { tbd = SkPaint::kBackward_TextBufferDirection; @@ -974,28 +1013,28 @@ public: return 0; } - SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint); const jchar* text = env->GetCharArrayElements(jtext, NULL); - count = breakText(env, *paint, text + index, count, maxWidth, + count = breakText(env, *paint, typeface, text + index, count, maxWidth, bidiFlags, jmeasuredWidth, tbd); env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text), JNI_ABORT); return count; } - static jint breakTextS(JNIEnv* env, jobject jpaint, jstring jtext, + static jint breakTextS(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jstring jtext, jboolean forwards, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) { - NPE_CHECK_RETURN_ZERO(env, jpaint); NPE_CHECK_RETURN_ZERO(env, jtext); + SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + SkPaint::TextBufferDirection tbd = forwards ? SkPaint::kForward_TextBufferDirection : SkPaint::kBackward_TextBufferDirection; - SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint); int count = env->GetStringLength(jtext); const jchar* text = env->GetStringChars(jtext, NULL); - count = breakText(env, *paint, text, count, maxWidth, bidiFlags, jmeasuredWidth, tbd); + count = breakText(env, *paint, typeface, text, count, maxWidth, bidiFlags, jmeasuredWidth, tbd); env->ReleaseStringChars(jtext, text); return count; } @@ -1106,14 +1145,14 @@ static JNINativeMethod methods[] = { {"native_measureText","([CIII)F", (void*) SkPaintGlue::measureText_CIII}, {"native_measureText","(Ljava/lang/String;I)F", (void*) SkPaintGlue::measureText_StringI}, {"native_measureText","(Ljava/lang/String;III)F", (void*) SkPaintGlue::measureText_StringIII}, - {"native_breakText","([CIIFI[F)I", (void*) SkPaintGlue::breakTextC}, - {"native_breakText","(Ljava/lang/String;ZFI[F)I", (void*) SkPaintGlue::breakTextS}, + {"native_breakText","(JJ[CIIFI[F)I", (void*) SkPaintGlue::breakTextC}, + {"native_breakText","(JJLjava/lang/String;ZFI[F)I", (void*) SkPaintGlue::breakTextS}, {"native_getTextWidths","(JJ[CIII[F)I", (void*) SkPaintGlue::getTextWidths___CIII_F}, {"native_getTextWidths","(JJLjava/lang/String;III[F)I", (void*) SkPaintGlue::getTextWidths__StringIII_F}, - {"native_getTextRunAdvances","(JJ[CIIIII[FI)F", - (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FI}, - {"native_getTextRunAdvances","(JJLjava/lang/String;IIIII[FI)F", - (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI}, + {"native_getTextRunAdvances","(JJ[CIIIIZ[FI)F", + (void*) SkPaintGlue::getTextRunAdvances___CIIIIZ_FI}, + {"native_getTextRunAdvances","(JJLjava/lang/String;IIIIZ[FI)F", + (void*) SkPaintGlue::getTextRunAdvances__StringIIIIZ_FI}, {"native_getTextGlyphs","(JLjava/lang/String;IIIII[C)I", diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp index 420a17f..6ef1d2c 100644 --- a/core/jni/android/graphics/Path.cpp +++ b/core/jni/android/graphics/Path.cpp @@ -2,22 +2,22 @@ ** ** Copyright 2006, 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 +** 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 +** 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 +** 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. */ // This file was generated from the C++ include file: SkPath.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, // or one of the auxilary file specifications in device/tools/gluemaker. #include "jni.h" @@ -92,7 +92,7 @@ public: SkPath* obj = reinterpret_cast<SkPath*>(objHandle); return obj->isEmpty(); } - + static jboolean isRect(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect) { SkRect rect; SkPath* obj = reinterpret_cast<SkPath*>(objHandle); @@ -100,65 +100,66 @@ public: GraphicsJNI::rect_to_jrectf(rect, env, jrect); return result; } - + static void computeBounds(JNIEnv* env, jobject clazz, jlong objHandle, jobject jbounds) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); const SkRect& bounds = obj->getBounds(); GraphicsJNI::rect_to_jrectf(bounds, env, jbounds); } - + static void incReserve(JNIEnv* env, jobject clazz, jlong objHandle, jint extraPtCount) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->incReserve(extraPtCount); } - + static void moveTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->moveTo(x, y); } - + static void rMoveTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rMoveTo(dx, dy); } - + static void lineTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->lineTo(x, y); } - + static void rLineTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rLineTo(dx, dy); } - + static void quadTo__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->quadTo(x1, y1, x2, y2); } - + static void rQuadTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rQuadTo(dx1, dy1, dx2, dy2); } - + static void cubicTo__FFFFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->cubicTo(x1, y1, x2, y2, x3, y3); } - + static void rCubicTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rCubicTo(x1, y1, x2, y2, x3, y3); } - - static void arcTo(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jfloat startAngle, jfloat sweepAngle, jboolean forceMoveTo) { + + static void arcTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, + jboolean forceMoveTo) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - SkRect oval_; - GraphicsJNI::jrectf_to_rect(env, oval, &oval_); - obj->arcTo(oval_, startAngle, sweepAngle, forceMoveTo); + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); + obj->arcTo(oval, startAngle, sweepAngle, forceMoveTo); } - + static void close(JNIEnv* env, jobject clazz, jlong objHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->close(); @@ -185,28 +186,26 @@ public: obj->addCircle(x, y, radius, dir); } - static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jfloat startAngle, jfloat sweepAngle) { - SkRect oval_; + static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle) { + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - GraphicsJNI::jrectf_to_rect(env, oval, &oval_); - obj->addArc(oval_, startAngle, sweepAngle); + obj->addArc(oval, startAngle, sweepAngle); } - static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect, - jfloat rx, jfloat ry, jint dirHandle) { - SkRect rect; + static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) { + SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); - GraphicsJNI::jrectf_to_rect(env, jrect, &rect); obj->addRoundRect(rect, rx, ry, dir); } - - static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jobject jrect, - jfloatArray array, jint dirHandle) { - SkRect rect; + + static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) { + SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); - GraphicsJNI::jrectf_to_rect(env, jrect, &rect); AutoJavaFloatArray afa(env, array, 8); #ifdef SK_SCALAR_IS_FLOAT const float* src = afa.ptr(); @@ -215,32 +214,32 @@ public: #endif obj->addRoundRect(rect, src, dir); } - + static void addPath__PathFF(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); obj->addPath(*src, dx, dy); } - + static void addPath__Path(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); obj->addPath(*src); } - + static void addPath__PathMatrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jlong matrixHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); obj->addPath(*src, *matrix); } - + static void offset__FFPath(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy, jlong dstHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); obj->offset(dx, dy, dst); } - + static void offset__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->offset(dx, dy); @@ -250,14 +249,14 @@ public: SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->setLastPt(dx, dy); } - + static void transform__MatrixPath(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle, jlong dstHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); obj->transform(*matrix, dst); } - + static void transform__Matrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); @@ -487,14 +486,14 @@ static JNINativeMethod methods[] = { {"native_rQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo}, {"native_cubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF}, {"native_rCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo}, - {"native_arcTo","(JLandroid/graphics/RectF;FFZ)V", (void*) SkPathGlue::arcTo}, + {"native_arcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo}, {"native_close","(J)V", (void*) SkPathGlue::close}, {"native_addRect","(JFFFFI)V", (void*) SkPathGlue::addRect}, {"native_addOval","(JFFFFI)V", (void*) SkPathGlue::addOval}, {"native_addCircle","(JFFFI)V", (void*) SkPathGlue::addCircle}, - {"native_addArc","(JLandroid/graphics/RectF;FF)V", (void*) SkPathGlue::addArc}, - {"native_addRoundRect","(JLandroid/graphics/RectF;FFI)V", (void*) SkPathGlue::addRoundRectXY}, - {"native_addRoundRect","(JLandroid/graphics/RectF;[FI)V", (void*) SkPathGlue::addRoundRect8}, + {"native_addArc","(JFFFFFF)V", (void*) SkPathGlue::addArc}, + {"native_addRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY}, + {"native_addRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8}, {"native_addPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF}, {"native_addPath","(JJ)V", (void*) SkPathGlue::addPath__Path}, {"native_addPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix}, diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index bac8ef7..bc0c25f 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -14,125 +14,104 @@ * limitations under the License. */ -#include "jni.h" -#include "GraphicsJNI.h" -#include <android_runtime/AndroidRuntime.h> +#include "Picture.h" #include "SkCanvas.h" -#include "SkPicture.h" #include "SkStream.h" -#include "SkTemplates.h" -#include "CreateJavaOutputStreamAdaptor.h" namespace android { -class SkPictureGlue { -public: - static jlong newPicture(JNIEnv* env, jobject, jlong srcHandle) { - const SkPicture* src = reinterpret_cast<SkPicture*>(srcHandle); - if (src) { - return reinterpret_cast<jlong>(new SkPicture(*src)); - } else { - return reinterpret_cast<jlong>(new SkPicture); +Picture::Picture(const Picture* src) { + if (NULL != src) { + mWidth = src->width(); + mHeight = src->height(); + if (NULL != src->mPicture.get()) { + mPicture.reset(SkRef(src->mPicture.get())); + } if (NULL != src->mRecorder.get()) { + mPicture.reset(src->makePartialCopy()); } + } else { + mWidth = 0; + mHeight = 0; } +} - static jlong deserialize(JNIEnv* env, jobject, jobject jstream, - jbyteArray jstorage) { - SkPicture* picture = NULL; - SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); - if (strm) { - picture = SkPicture::CreateFromStream(strm); - delete strm; - } - return reinterpret_cast<jlong>(picture); - } +SkCanvas* Picture::beginRecording(int width, int height) { + mPicture.reset(NULL); + mRecorder.reset(new SkPictureRecorder); + mWidth = width; + mHeight = height; + return mRecorder->beginRecording(width, height, NULL, 0); +} - static void killPicture(JNIEnv* env, jobject, jlong pictureHandle) { - SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle); - SkASSERT(picture); - picture->unref(); +void Picture::endRecording() { + if (NULL != mRecorder.get()) { + mPicture.reset(mRecorder->endRecording()); + mRecorder.reset(NULL); } +} - static void draw(JNIEnv* env, jobject, jlong canvasHandle, - jlong pictureHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle); - SkASSERT(canvas); - SkASSERT(picture); - picture->draw(canvas); +int Picture::width() const { + if (NULL != mPicture.get()) { + SkASSERT(mPicture->width() == mWidth); + SkASSERT(mPicture->height() == mHeight); } - static jboolean serialize(JNIEnv* env, jobject, jlong pictureHandle, - jobject jstream, jbyteArray jstorage) { - SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle); - SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); - - if (NULL != strm) { - picture->serialize(strm); - delete strm; - return JNI_TRUE; - } - return JNI_FALSE; - } + return mWidth; +} - static jint getWidth(JNIEnv* env, jobject jpic) { - NPE_CHECK_RETURN_ZERO(env, jpic); - int width = GraphicsJNI::getNativePicture(env, jpic)->width(); - return static_cast<jint>(width); +int Picture::height() const { + if (NULL != mPicture.get()) { + SkASSERT(mPicture->width() == mWidth); + SkASSERT(mPicture->height() == mHeight); } - static jint getHeight(JNIEnv* env, jobject jpic) { - NPE_CHECK_RETURN_ZERO(env, jpic); - int height = GraphicsJNI::getNativePicture(env, jpic)->height(); - return static_cast<jint>(height); - } + return mHeight; +} - static jlong beginRecording(JNIEnv* env, jobject, jlong pictHandle, - jint w, jint h) { - SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle); - // beginRecording does not ref its return value, it just returns it. - SkCanvas* canvas = pict->beginRecording(w, h); - // the java side will wrap this guy in a Canvas.java, which will call - // unref in its finalizer, so we have to ref it here, so that both that - // Canvas.java and our picture can both be owners - canvas->ref(); - return reinterpret_cast<jlong>(canvas); - } +Picture* Picture::CreateFromStream(SkStream* stream) { + Picture* newPict = new Picture; - static void endRecording(JNIEnv* env, jobject, jlong pictHandle) { - SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle); - pict->endRecording(); + newPict->mPicture.reset(SkPicture::CreateFromStream(stream)); + if (NULL != newPict->mPicture.get()) { + newPict->mWidth = newPict->mPicture->width(); + newPict->mHeight = newPict->mPicture->height(); } -}; - -static JNINativeMethod gPictureMethods[] = { - {"getWidth", "()I", (void*) SkPictureGlue::getWidth}, - {"getHeight", "()I", (void*) SkPictureGlue::getHeight}, - {"nativeConstructor", "(J)J", (void*) SkPictureGlue::newPicture}, - {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)SkPictureGlue::deserialize}, - {"nativeBeginRecording", "(JII)J", (void*) SkPictureGlue::beginRecording}, - {"nativeEndRecording", "(J)V", (void*) SkPictureGlue::endRecording}, - {"nativeDraw", "(JJ)V", (void*) SkPictureGlue::draw}, - {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)SkPictureGlue::serialize}, - {"nativeDestructor","(J)V", (void*) SkPictureGlue::killPicture} -}; -#include <android_runtime/AndroidRuntime.h> + return newPict; +} -#define REG(env, name, array) \ - result = android::AndroidRuntime::registerNativeMethods(env, name, array, \ - SK_ARRAY_COUNT(array)); \ - if (result < 0) return result +void Picture::serialize(SkWStream* stream) const { + if (NULL != mRecorder.get()) { + SkAutoTDelete<SkPicture> tempPict(this->makePartialCopy()); + tempPict->serialize(stream); + } else if (NULL != mPicture.get()) { + mPicture->serialize(stream); + } else { + SkPicture empty; + empty.serialize(stream); + } +} -int register_android_graphics_Picture(JNIEnv* env) { - int result; +void Picture::draw(SkCanvas* canvas) { + if (NULL != mRecorder.get()) { + this->endRecording(); + SkASSERT(NULL != mPicture.get()); + } + if (NULL != mPicture.get()) { + // TODO: remove this const_cast once pictures are immutable + const_cast<SkPicture*>(mPicture.get())->draw(canvas); + } +} - REG(env, "android/graphics/Picture", gPictureMethods); +SkPicture* Picture::makePartialCopy() const { + SkASSERT(NULL != mRecorder.get()); - return result; -} + SkPictureRecorder reRecorder; + SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0); + mRecorder->partialReplay(canvas); + return reRecorder.endRecording(); } - +}; // namespace android diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h new file mode 100644 index 0000000..abb0403 --- /dev/null +++ b/core/jni/android/graphics/Picture.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#ifndef ANDROID_GRAPHICS_PICTURE_H +#define ANDROID_GRAPHICS_PICTURE_H + +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkRefCnt.h" +#include "SkTemplates.h" + +class SkCanvas; +class SkPicture; +class SkPictureRecorder; +class SkStream; +class SkWStream; + +namespace android { + +// Skia's SkPicture class has been split into an SkPictureRecorder +// and an SkPicture. AndroidPicture recreates the functionality +// of the old SkPicture interface by flip-flopping between the two +// new classes. +class Picture { +public: + explicit Picture(const Picture* src = NULL); + + SkCanvas* beginRecording(int width, int height); + + void endRecording(); + + int width() const; + + int height() const; + + static Picture* CreateFromStream(SkStream* stream); + + void serialize(SkWStream* stream) const; + + void draw(SkCanvas* canvas); + +private: + int mWidth; + int mHeight; + SkAutoTUnref<const SkPicture> mPicture; + SkAutoTDelete<SkPictureRecorder> mRecorder; + + // Make a copy of a picture that is in the midst of being recorded. The + // resulting picture will have balanced saves and restores. + SkPicture* makePartialCopy() const; +}; + +}; // namespace android +#endif // ANDROID_GRAPHICS_PICTURE_H diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp index d54aaa8..3812c27 100644 --- a/core/jni/android/graphics/pdf/PdfDocument.cpp +++ b/core/jni/android/graphics/pdf/PdfDocument.cpp @@ -24,6 +24,7 @@ #include "SkCanvas.h" #include "SkDocument.h" #include "SkPicture.h" +#include "SkPictureRecorder.h" #include "SkStream.h" #include "SkRect.h" @@ -32,15 +33,22 @@ namespace android { struct PageRecord { PageRecord(int width, int height, const SkRect& contentRect) - : mPicture(new SkPicture()), mWidth(width), mHeight(height) { + : mPictureRecorder(new SkPictureRecorder()) + , mPicture(NULL) + , mWidth(width) + , mHeight(height) { mContentRect = contentRect; } ~PageRecord() { - mPicture->unref(); + delete mPictureRecorder; + if (NULL != mPicture) { + mPicture->unref(); + } } - SkPicture* const mPicture; + SkPictureRecorder* mPictureRecorder; + SkPicture* mPicture; const int mWidth; const int mHeight; SkRect mContentRect; @@ -62,8 +70,8 @@ public: mPages.push_back(page); mCurrentPage = page; - SkCanvas* canvas = page->mPicture->beginRecording( - contentRect.width(), contentRect.height(), 0); + SkCanvas* canvas = page->mPictureRecorder->beginRecording( + contentRect.width(), contentRect.height(), NULL, 0); // We pass this canvas to Java where it is used to construct // a Java Canvas object which dereferences the pointer when it @@ -75,7 +83,11 @@ public: void finishPage() { assert(mCurrentPage != NULL); - mCurrentPage->mPicture->endRecording(); + assert(mCurrentPage->mPictureRecorder != NULL); + assert(mCurrentPage->mPicture == NULL); + mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->endRecording(); + delete mCurrentPage->mPictureRecorder; + mCurrentPage->mPictureRecorder = NULL; mCurrentPage = NULL; } @@ -89,7 +101,7 @@ public: canvas->clipRect(page->mContentRect); canvas->translate(page->mContentRect.left(), page->mContentRect.top()); - canvas->drawPicture(*page->mPicture); + canvas->drawPicture(page->mPicture); document->endPage(); } @@ -97,11 +109,10 @@ public: } void close() { + assert(NULL == mCurrentPage); for (unsigned i = 0; i < mPages.size(); i++) { delete mPages[i]; } - delete mCurrentPage; - mCurrentPage = NULL; } private: diff --git a/core/jni/android_graphics_Picture.cpp b/core/jni/android_graphics_Picture.cpp new file mode 100644 index 0000000..f827907 --- /dev/null +++ b/core/jni/android_graphics_Picture.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" +#include "GraphicsJNI.h" +#include <android_runtime/AndroidRuntime.h> + +#include "Picture.h" + +#include "SkCanvas.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "CreateJavaOutputStreamAdaptor.h" + +namespace android { + +static jlong android_graphics_Picture_newPicture(JNIEnv* env, jobject, jlong srcHandle) { + const Picture* src = reinterpret_cast<Picture*>(srcHandle); + return reinterpret_cast<jlong>(new Picture(src)); +} + +static jlong android_graphics_Picture_deserialize(JNIEnv* env, jobject, jobject jstream, + jbyteArray jstorage) { + Picture* picture = NULL; + SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); + if (strm) { + picture = Picture::CreateFromStream(strm); + delete strm; + } + return reinterpret_cast<jlong>(picture); +} + +static void android_graphics_Picture_killPicture(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(picture); + delete picture; +} + +static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle, + jlong pictureHandle) { + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(canvas); + SkASSERT(picture); + picture->draw(canvas); +} + +static jboolean android_graphics_Picture_serialize(JNIEnv* env, jobject, jlong pictureHandle, + jobject jstream, jbyteArray jstorage) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); + + if (NULL != strm) { + picture->serialize(strm); + delete strm; + return JNI_TRUE; + } + return JNI_FALSE; +} + +static jint android_graphics_Picture_getWidth(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->width()); +} + +static jint android_graphics_Picture_getHeight(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->height()); +} + +static jlong android_graphics_Picture_beginRecording(JNIEnv* env, jobject, jlong pictHandle, + jint w, jint h) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + // beginRecording does not ref its return value, it just returns it. + SkCanvas* canvas = pict->beginRecording(w, h); + // the java side will wrap this guy in a Canvas.java, which will call + // unref in its finalizer, so we have to ref it here, so that both that + // Canvas.java and our picture can both be owners + canvas->ref(); + return reinterpret_cast<jlong>(canvas); +} + +static void android_graphics_Picture_endRecording(JNIEnv* env, jobject, jlong pictHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + pict->endRecording(); +} + +static JNINativeMethod gMethods[] = { + {"nativeGetWidth", "(J)I", (void*) android_graphics_Picture_getWidth}, + {"nativeGetHeight", "(J)I", (void*) android_graphics_Picture_getHeight}, + {"nativeConstructor", "(J)J", (void*) android_graphics_Picture_newPicture}, + {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)android_graphics_Picture_deserialize}, + {"nativeBeginRecording", "(JII)J", (void*) android_graphics_Picture_beginRecording}, + {"nativeEndRecording", "(J)V", (void*) android_graphics_Picture_endRecording}, + {"nativeDraw", "(JJ)V", (void*) android_graphics_Picture_draw}, + {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)android_graphics_Picture_serialize}, + {"nativeDestructor","(J)V", (void*) android_graphics_Picture_killPicture} +}; + +int register_android_graphics_Picture(JNIEnv* env) { + return AndroidRuntime::registerNativeMethods(env, "android/graphics/Picture", gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 3a53331..f8bab24 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -36,6 +36,11 @@ using namespace android; +enum { + // Keep up to date with Camera.java + CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2, +}; + struct fields_t { jfieldID context; jfieldID facing; @@ -466,7 +471,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, // connect to camera service static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, - jobject weak_this, jint cameraId, jstring clientPackageName) + jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName) { // Convert jstring to String16 const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL); @@ -474,8 +479,18 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, String16 clientName(rawClientName, rawClientNameLen); env->ReleaseStringChars(clientPackageName, rawClientName); - sp<Camera> camera = Camera::connect(cameraId, clientName, - Camera::USE_CALLING_UID); + sp<Camera> camera; + if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) { + // Default path: hal version is don't care, do normal camera connect. + camera = Camera::connect(cameraId, clientName, + Camera::USE_CALLING_UID); + } else { + jint status = Camera::connectLegacy(cameraId, halVersion, clientName, + Camera::USE_CALLING_UID, camera); + if (status != NO_ERROR) { + return status; + } + } if (camera == NULL) { return -EACCES; @@ -510,7 +525,6 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, // finalizer is invoked later. static void android_hardware_Camera_release(JNIEnv *env, jobject thiz) { - // TODO: Change to ALOGV ALOGV("release camera"); JNICameraContext* context = NULL; sp<Camera> camera; @@ -891,7 +905,7 @@ static JNINativeMethod camMethods[] = { "(ILandroid/hardware/Camera$CameraInfo;)V", (void*)android_hardware_Camera_getCameraInfo }, { "native_setup", - "(Ljava/lang/Object;ILjava/lang/String;)I", + "(Ljava/lang/Object;IILjava/lang/String;)I", (void*)android_hardware_Camera_native_setup }, { "native_release", "()V", diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index fcf8f83..c588942 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -51,6 +51,8 @@ struct SensorOffsets jfieldID fifoMaxEventCount; jfieldID stringType; jfieldID requiredPermission; + jfieldID maxDelay; + jfieldID flags; } gSensorOffsets; @@ -78,6 +80,8 @@ nativeClassInit (JNIEnv *_env, jclass _this) sensorOffsets.stringType = _env->GetFieldID(sensorClass, "mStringType", "Ljava/lang/String;"); sensorOffsets.requiredPermission = _env->GetFieldID(sensorClass, "mRequiredPermission", "Ljava/lang/String;"); + sensorOffsets.maxDelay = _env->GetFieldID(sensorClass, "mMaxDelay", "I"); + sensorOffsets.flags = _env->GetFieldID(sensorClass, "mFlags", "I"); } static jint @@ -112,6 +116,8 @@ nativeGetNextSensor(JNIEnv *env, jclass clazz, jobject sensor, jint next) env->SetObjectField(sensor, sensorOffsets.stringType, stringType); env->SetObjectField(sensor, sensorOffsets.requiredPermission, requiredPermission); + env->SetIntField(sensor, sensorOffsets.maxDelay, list->getMaxDelay()); + env->SetIntField(sensor, sensorOffsets.flags, list->getFlags()); next++; return size_t(next) < count ? next : 0; } @@ -160,7 +166,8 @@ private: ASensorEvent buffer[16]; while ((n = q->read(buffer, 16)) > 0) { for (int i=0 ; i<n ; i++) { - if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER) { + if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER || + buffer[i].type == SENSOR_TYPE_WAKE_UP_STEP_COUNTER) { // step-counter returns a uint64, but the java API only deals with floats float value = float(buffer[i].u64.step_counter); env->SetFloatArrayRegion(mScratch, 0, 1, &value); @@ -175,11 +182,26 @@ private: gBaseEventQueueClassInfo.dispatchFlushCompleteEvent, buffer[i].meta_data.sensor); } else { + int8_t status; + switch (buffer[i].type) { + case SENSOR_TYPE_ORIENTATION: + case SENSOR_TYPE_MAGNETIC_FIELD: + case SENSOR_TYPE_ACCELEROMETER: + case SENSOR_TYPE_GYROSCOPE: + status = buffer[i].vector.status; + break; + case SENSOR_TYPE_HEART_RATE: + status = buffer[i].heart_rate.status; + break; + default: + status = SENSOR_STATUS_ACCURACY_HIGH; + break; + } env->CallVoidMethod(mReceiverObject, gBaseEventQueueClassInfo.dispatchSensorEvent, buffer[i].sensor, mScratch, - buffer[i].vector.status, + status, buffer[i].timestamp); } if (env->ExceptionCheck()) { diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp new file mode 100644 index 0000000..69e991d --- /dev/null +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -0,0 +1,661 @@ +/* +** +** 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. +*/ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoundTrigger-JNI" +#include <utils/Log.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include <system/sound_trigger.h> +#include <soundtrigger/SoundTriggerCallback.h> +#include <soundtrigger/SoundTrigger.h> +#include <utils/RefBase.h> +#include <utils/Vector.h> +#include <binder/IMemory.h> +#include <binder/MemoryDealer.h> + +using namespace android; + +static jclass gArrayListClass; +static struct { + jmethodID add; +} gArrayListMethods; + +static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger"; +static jclass gSoundTriggerClass; + +static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule"; +static jclass gModuleClass; +static struct { + jfieldID mNativeContext; + jfieldID mId; +} gModuleFields; +static jmethodID gPostEventFromNative; + +static const char* const kModulePropertiesClassPathName = + "android/hardware/soundtrigger/SoundTrigger$ModuleProperties"; +static jclass gModulePropertiesClass; +static jmethodID gModulePropertiesCstor; + +static const char* const kSoundModelClassPathName = + "android/hardware/soundtrigger/SoundTrigger$SoundModel"; +static jclass gSoundModelClass; +static struct { + jfieldID data; +} gSoundModelFields; + +static const char* const kKeyPhraseClassPathName = + "android/hardware/soundtrigger/SoundTrigger$KeyPhrase"; +static jclass gKeyPhraseClass; +static struct { + jfieldID recognitionModes; + jfieldID locale; + jfieldID text; + jfieldID numUsers; +} gKeyPhraseFields; + +static const char* const kKeyPhraseSoundModelClassPathName = + "android/hardware/soundtrigger/SoundTrigger$KeyPhraseSoundModel"; +static jclass gKeyPhraseSoundModelClass; +static struct { + jfieldID keyPhrases; +} gKeyPhraseSoundModelFields; + + +static const char* const kRecognitionEventClassPathName = + "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent"; +static jclass gRecognitionEventClass; +static jmethodID gRecognitionEventCstor; + +static const char* const kKeyPhraseRecognitionEventClassPathName = + "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionEvent"; +static jclass gKeyPhraseRecognitionEventClass; +static jmethodID gKeyPhraseRecognitionEventCstor; + +static const char* const kKeyPhraseRecognitionExtraClassPathName = + "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra"; +static jclass gKeyPhraseRecognitionExtraClass; +static jmethodID gKeyPhraseRecognitionExtraCstor; + +static Mutex gLock; + +enum { + SOUNDTRIGGER_STATUS_OK = 0, + SOUNDTRIGGER_STATUS_ERROR = INT_MIN, + SOUNDTRIGGER_PERMISSION_DENIED = -1, + SOUNDTRIGGER_STATUS_NO_INIT = -19, + SOUNDTRIGGER_STATUS_BAD_VALUE = -22, + SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32, + SOUNDTRIGGER_INVALID_OPERATION = -38, +}; + +enum { + SOUNDTRIGGER_EVENT_RECOGNITION = 1, + SOUNDTRIGGER_EVENT_SERVICE_DIED = 2, +}; + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNISoundTriggerCallback: public SoundTriggerCallback +{ +public: + JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNISoundTriggerCallback(); + + virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event); + virtual void onServiceDied(); + +private: + jclass mClass; // Reference to SoundTrigger class + jobject mObject; // Weak ref to SoundTrigger Java object to call on +}; + +JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the SoundTriggerModule class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + ALOGE("Can't find class %s", kModuleClassPathName); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the SoundTriggerModule object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNISoundTriggerCallback::~JNISoundTriggerCallback() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + jobject jEvent; + + jbyteArray jData = NULL; + if (event->data_size) { + jData = env->NewByteArray(event->data_size); + jbyte *nData = env->GetByteArrayElements(jData, NULL); + memcpy(nData, (char *)event + event->data_offset, event->data_size); + env->ReleaseByteArrayElements(jData, nData, 0); + } + + if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) { + struct sound_trigger_phrase_recognition_event *phraseEvent = + (struct sound_trigger_phrase_recognition_event *)event; + + jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases, + gKeyPhraseRecognitionExtraClass, NULL); + if (jExtras == NULL) { + return; + } + + for (size_t i = 0; i < phraseEvent->num_phrases; i++) { + jintArray jConfidenceLevels = env->NewIntArray(phraseEvent->phrase_extras[i].num_users); + if (jConfidenceLevels == NULL) { + return; + } + jint *nConfidenceLevels = env->GetIntArrayElements(jConfidenceLevels, NULL); + memcpy(nConfidenceLevels, + phraseEvent->phrase_extras[i].confidence_levels, + phraseEvent->phrase_extras[i].num_users * sizeof(int)); + env->ReleaseIntArrayElements(jConfidenceLevels, nConfidenceLevels, 0); + jobject jNewExtra = env->NewObject(gKeyPhraseRecognitionExtraClass, + gKeyPhraseRecognitionExtraCstor, + jConfidenceLevels, + phraseEvent->phrase_extras[i].recognition_modes); + + if (jNewExtra == NULL) { + return; + } + env->SetObjectArrayElement(jExtras, i, jNewExtra); + + } + jEvent = env->NewObject(gKeyPhraseRecognitionEventClass, gKeyPhraseRecognitionEventCstor, + event->status, event->model, event->capture_available, + event->capture_session, event->capture_delay_ms, jData, + phraseEvent->key_phrase_in_capture, jExtras); + } else { + jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor, + event->status, event->model, event->capture_available, + event->capture_session, event->capture_delay_ms, jData); + } + + + env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, + SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + +void JNISoundTriggerCallback::onServiceDied() +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, + SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + +// ---------------------------------------------------------------------------- + +static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(gLock); + SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz, + gModuleFields.mNativeContext); + return sp<SoundTrigger>(st); +} + +static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module) +{ + Mutex::Autolock l(gLock); + sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz, + gModuleFields.mNativeContext); + if (module.get()) { + module->incStrong((void*)setSoundTrigger); + } + if (old != 0) { + old->decStrong((void*)setSoundTrigger); + } + env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get()); + return old; +} + + +static jint +android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz, + jobject jModules) +{ + ALOGV("listModules"); + + if (jModules == NULL) { + ALOGE("listModules NULL AudioPatch ArrayList"); + return SOUNDTRIGGER_STATUS_BAD_VALUE; + } + if (!env->IsInstanceOf(jModules, gArrayListClass)) { + ALOGE("listModules not an arraylist"); + return SOUNDTRIGGER_STATUS_BAD_VALUE; + } + + unsigned int numModules = 0; + struct sound_trigger_module_descriptor *nModules = NULL; + + status_t status = SoundTrigger::listModules(nModules, &numModules); + if (status != NO_ERROR || numModules == 0) { + return (jint)status; + } + + nModules = (struct sound_trigger_module_descriptor *) + calloc(numModules, sizeof(struct sound_trigger_module_descriptor)); + + status = SoundTrigger::listModules(nModules, &numModules); + ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules); + + if (status != NO_ERROR) { + numModules = 0; + } + + for (size_t i = 0; i < numModules; i++) { + char str[SOUND_TRIGGER_MAX_STRING_LEN]; + + jstring implementor = env->NewStringUTF(nModules[i].properties.implementor); + jstring description = env->NewStringUTF(nModules[i].properties.description); + SoundTrigger::guidToString(&nModules[i].properties.uuid, + str, + SOUND_TRIGGER_MAX_STRING_LEN); + jstring uuid = env->NewStringUTF(str); + + ALOGV("listModules module %d id %d description %s maxSoundModels %d", + i, nModules[i].handle, nModules[i].properties.description, + nModules[i].properties.max_sound_models); + + jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor, + nModules[i].handle, + implementor, description, uuid, + nModules[i].properties.version, + nModules[i].properties.max_sound_models, + nModules[i].properties.max_key_phrases, + nModules[i].properties.max_users, + nModules[i].properties.recognition_modes, + nModules[i].properties.capture_transition, + nModules[i].properties.max_buffer_ms, + nModules[i].properties.concurrent_capture, + nModules[i].properties.power_consumption_mw); + + env->DeleteLocalRef(implementor); + env->DeleteLocalRef(description); + env->DeleteLocalRef(uuid); + if (newModuleDesc == NULL) { + status = SOUNDTRIGGER_STATUS_ERROR; + goto exit; + } + env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc); + } + +exit: + free(nModules); + return (jint) status; +} + +static void +android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + ALOGV("setup"); + + sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this); + + sound_trigger_module_handle_t handle = + (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId); + + sp<SoundTrigger> module = SoundTrigger::attach(handle, callback); + if (module == 0) { + return; + } + + setSoundTrigger(env, thiz, module); +} + +static void +android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz) +{ + ALOGV("detach"); + sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0); + ALOGV("detach module %p", module.get()); + if (module != 0) { + ALOGV("detach module->detach()"); + module->detach(); + } +} + +static void +android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz) +{ + ALOGV("finalize"); + sp<SoundTrigger> module = getSoundTrigger(env, thiz); + if (module != 0) { + ALOGW("SoundTrigger finalized without being detached"); + } + android_hardware_SoundTrigger_detach(env, thiz); +} + +static jint +android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz, + jobject jSoundModel, jintArray jHandle) +{ + jint status = SOUNDTRIGGER_STATUS_OK; + char *nData = NULL; + struct sound_trigger_sound_model *nSoundModel; + jbyteArray jData; + sp<MemoryDealer> memoryDealer; + sp<IMemory> memory; + size_t size; + sound_model_handle_t handle; + + ALOGV("loadSoundModel"); + sp<SoundTrigger> module = getSoundTrigger(env, thiz); + if (module == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + if (jHandle == NULL) { + return SOUNDTRIGGER_STATUS_BAD_VALUE; + } + jsize jHandleLen = env->GetArrayLength(jHandle); + if (jHandleLen == 0) { + return SOUNDTRIGGER_STATUS_BAD_VALUE; + } + jint *nHandle = env->GetIntArrayElements(jHandle, NULL); + if (nHandle == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) { + status = SOUNDTRIGGER_STATUS_BAD_VALUE; + goto exit; + } + size_t offset; + sound_trigger_sound_model_type_t type; + if (env->IsInstanceOf(jSoundModel, gKeyPhraseSoundModelClass)) { + offset = sizeof(struct sound_trigger_phrase_sound_model); + type = SOUND_MODEL_TYPE_KEYPHRASE; + } else { + offset = sizeof(struct sound_trigger_sound_model); + type = SOUND_MODEL_TYPE_UNKNOWN; + } + jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data); + if (jData == NULL) { + status = SOUNDTRIGGER_STATUS_BAD_VALUE; + goto exit; + } + size = env->GetArrayLength(jData); + + nData = (char *)env->GetByteArrayElements(jData, NULL); + if (jData == NULL) { + status = SOUNDTRIGGER_STATUS_ERROR; + goto exit; + } + + memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel"); + if (memoryDealer == 0) { + status = SOUNDTRIGGER_STATUS_ERROR; + goto exit; + } + memory = memoryDealer->allocate(offset + size); + if (memory == 0 || memory->pointer() == NULL) { + status = SOUNDTRIGGER_STATUS_ERROR; + goto exit; + } + + nSoundModel = (struct sound_trigger_sound_model *)memory->pointer(); + + nSoundModel->type = type; + nSoundModel->data_size = size; + nSoundModel->data_offset = offset; + memcpy((char *)nSoundModel + offset, nData, size); + if (type == SOUND_MODEL_TYPE_KEYPHRASE) { + struct sound_trigger_phrase_sound_model *phraseModel = + (struct sound_trigger_phrase_sound_model *)nSoundModel; + + jobjectArray jPhrases = + (jobjectArray)env->GetObjectField(jSoundModel, gKeyPhraseSoundModelFields.keyPhrases); + if (jPhrases == NULL) { + status = SOUNDTRIGGER_STATUS_BAD_VALUE; + goto exit; + } + + size_t numPhrases = env->GetArrayLength(jPhrases); + phraseModel->num_phrases = numPhrases; + ALOGV("loadSoundModel numPhrases %d", numPhrases); + for (size_t i = 0; i < numPhrases; i++) { + jobject jPhrase = env->GetObjectArrayElement(jPhrases, i); + phraseModel->phrases[i].recognition_mode = + env->GetIntField(jPhrase,gKeyPhraseFields.recognitionModes); + phraseModel->phrases[i].num_users = + env->GetIntField(jPhrase, gKeyPhraseFields.numUsers); + jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.locale); + const char *nLocale = env->GetStringUTFChars(jLocale, NULL); + strncpy(phraseModel->phrases[i].locale, + nLocale, + SOUND_TRIGGER_MAX_LOCALE_LEN); + jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.text); + const char *nText = env->GetStringUTFChars(jText, NULL); + strncpy(phraseModel->phrases[i].text, + nText, + SOUND_TRIGGER_MAX_STRING_LEN); + + env->ReleaseStringUTFChars(jLocale, nLocale); + env->DeleteLocalRef(jLocale); + env->ReleaseStringUTFChars(jText, nText); + env->DeleteLocalRef(jText); + ALOGV("loadSoundModel phrases %d text %s locale %s", + i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale); + } + env->DeleteLocalRef(jPhrases); + } + status = module->loadSoundModel(memory, &handle); + ALOGV("loadSoundModel status %d handle %d", status, handle); + +exit: + if (nHandle != NULL) { + nHandle[0] = (jint)handle; + env->ReleaseIntArrayElements(jHandle, nHandle, NULL); + } + if (nData != NULL) { + env->ReleaseByteArrayElements(jData, (jbyte *)nData, NULL); + } + return status; +} + +static jint +android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz, + jint jHandle) +{ + jint status = SOUNDTRIGGER_STATUS_OK; + ALOGV("unloadSoundModel"); + sp<SoundTrigger> module = getSoundTrigger(env, thiz); + if (module == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + status = module->unloadSoundModel((sound_model_handle_t)jHandle); + + return status; +} + +static jint +android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz, + jint jHandle, jbyteArray jData) +{ + jint status = SOUNDTRIGGER_STATUS_OK; + ALOGV("startRecognition"); + sp<SoundTrigger> module = getSoundTrigger(env, thiz); + if (module == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + jsize dataSize = 0; + char *nData = NULL; + sp<IMemory> memory; + if (jData != NULL) { + dataSize = env->GetArrayLength(jData); + if (dataSize == 0) { + return SOUNDTRIGGER_STATUS_BAD_VALUE; + } + nData = (char *)env->GetByteArrayElements(jData, NULL); + if (nData == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + sp<MemoryDealer> memoryDealer = + new MemoryDealer(dataSize, "SoundTrigge-JNI::StartRecognition"); + if (memoryDealer == 0) { + return SOUNDTRIGGER_STATUS_ERROR; + } + memory = memoryDealer->allocate(dataSize); + if (memory == 0 || memory->pointer() == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + memcpy(memory->pointer(), nData, dataSize); + } + + status = module->startRecognition(jHandle, memory); + return status; +} + +static jint +android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz, + jint jHandle) +{ + jint status = SOUNDTRIGGER_STATUS_OK; + ALOGV("stopRecognition"); + sp<SoundTrigger> module = getSoundTrigger(env, thiz); + if (module == NULL) { + return SOUNDTRIGGER_STATUS_ERROR; + } + status = module->stopRecognition(jHandle); + return status; +} + +static JNINativeMethod gMethods[] = { + {"listModules", + "(Ljava/util/ArrayList;)I", + (void *)android_hardware_SoundTrigger_listModules}, +}; + + +static JNINativeMethod gModuleMethods[] = { + {"native_setup", + "(Ljava/lang/Object;)V", + (void *)android_hardware_SoundTrigger_setup}, + {"native_finalize", + "()V", + (void *)android_hardware_SoundTrigger_finalize}, + {"detach", + "()V", + (void *)android_hardware_SoundTrigger_detach}, + {"loadSoundModel", + "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I", + (void *)android_hardware_SoundTrigger_loadSoundModel}, + {"unloadSoundModel", + "(I)I", + (void *)android_hardware_SoundTrigger_unloadSoundModel}, + {"startRecognition", + "(I[B)I", + (void *)android_hardware_SoundTrigger_startRecognition}, + {"stopRecognition", + "(I)I", + (void *)android_hardware_SoundTrigger_stopRecognition}, +}; + +int register_android_hardware_SoundTrigger(JNIEnv *env) +{ + jclass arrayListClass = env->FindClass("java/util/ArrayList"); + gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass); + gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); + + jclass lClass = env->FindClass(kSoundTriggerClassPathName); + gSoundTriggerClass = (jclass) env->NewGlobalRef(lClass); + + jclass moduleClass = env->FindClass(kModuleClassPathName); + gModuleClass = (jclass) env->NewGlobalRef(moduleClass); + gPostEventFromNative = env->GetStaticMethodID(moduleClass, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + gModuleFields.mNativeContext = env->GetFieldID(moduleClass, "mNativeContext", "J"); + gModuleFields.mId = env->GetFieldID(moduleClass, "mId", "I"); + + + jclass modulePropertiesClass = env->FindClass(kModulePropertiesClassPathName); + gModulePropertiesClass = (jclass) env->NewGlobalRef(modulePropertiesClass); + gModulePropertiesCstor = env->GetMethodID(modulePropertiesClass, "<init>", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZI)V"); + + jclass soundModelClass = env->FindClass(kSoundModelClassPathName); + gSoundModelClass = (jclass) env->NewGlobalRef(soundModelClass); + gSoundModelFields.data = env->GetFieldID(soundModelClass, "data", "[B"); + + jclass keyPhraseClass = env->FindClass(kKeyPhraseClassPathName); + gKeyPhraseClass = (jclass) env->NewGlobalRef(keyPhraseClass); + gKeyPhraseFields.recognitionModes = env->GetFieldID(keyPhraseClass, "recognitionModes", "I"); + gKeyPhraseFields.locale = env->GetFieldID(keyPhraseClass, "locale", "Ljava/lang/String;"); + gKeyPhraseFields.text = env->GetFieldID(keyPhraseClass, "text", "Ljava/lang/String;"); + gKeyPhraseFields.numUsers = env->GetFieldID(keyPhraseClass, "numUsers", "I"); + + jclass keyPhraseSoundModelClass = env->FindClass(kKeyPhraseSoundModelClassPathName); + gKeyPhraseSoundModelClass = (jclass) env->NewGlobalRef(keyPhraseSoundModelClass); + gKeyPhraseSoundModelFields.keyPhrases = env->GetFieldID(keyPhraseSoundModelClass, + "keyPhrases", + "[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhrase;"); + + + jclass recognitionEventClass = env->FindClass(kRecognitionEventClassPathName); + gRecognitionEventClass = (jclass) env->NewGlobalRef(recognitionEventClass); + gRecognitionEventCstor = env->GetMethodID(recognitionEventClass, "<init>", + "(IIZII[B)V"); + + jclass keyPhraseRecognitionEventClass = env->FindClass(kKeyPhraseRecognitionEventClassPathName); + gKeyPhraseRecognitionEventClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionEventClass); + gKeyPhraseRecognitionEventCstor = env->GetMethodID(keyPhraseRecognitionEventClass, "<init>", + "(IIZII[BZ[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra;)V"); + + + jclass keyPhraseRecognitionExtraClass = env->FindClass(kKeyPhraseRecognitionExtraClassPathName); + gKeyPhraseRecognitionExtraClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionExtraClass); + gKeyPhraseRecognitionExtraCstor = env->GetMethodID(keyPhraseRecognitionExtraClass, "<init>", + "([II)V"); + + int status = AndroidRuntime::registerNativeMethods(env, + kSoundTriggerClassPathName, gMethods, NELEM(gMethods)); + + if (status == 0) { + status = AndroidRuntime::registerNativeMethods(env, + kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods)); + } + + return status; +} diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp index 40e9544..57058a6 100644 --- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp +++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp @@ -15,6 +15,7 @@ */ #define LOG_TAG "Legacy-CameraDevice-JNI" +// #define LOG_NDEBUG 0 #include <utils/Log.h> #include <utils/Errors.h> #include <utils/Trace.h> @@ -26,6 +27,7 @@ #include <ui/GraphicBuffer.h> #include <system/window.h> +#include <hardware/camera3.h> using namespace android; @@ -35,6 +37,8 @@ using namespace android; #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a))) +#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) + /** * Convert from RGB 888 to Y'CbCr using the conversion specified in ITU-R BT.601 for * digital RGB with K_b = 0.114, and K_r = 0.299. @@ -116,8 +120,9 @@ static status_t configureSurface(const sp<ANativeWindow>& anw, return err; } - ALOGV("%s: Setting buffer count to %d", __FUNCTION__, - maxBufferSlack + 1 + minUndequeuedBuffers); + ALOGV("%s: Setting buffer count to %d, size to (%dx%d), fmt (0x%x)", __FUNCTION__, + maxBufferSlack + 1 + minUndequeuedBuffers, + width, height, pixelFmt); err = native_window_set_buffer_count(anw.get(), maxBufferSlack + 1 + minUndequeuedBuffers); if (err != NO_ERROR) { ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__, @@ -146,16 +151,41 @@ static status_t produceFrame(const sp<ANativeWindow>& anw, int32_t width, // Width of the pixelBuffer int32_t height, // Height of the pixelBuffer int32_t pixelFmt, // Format of the pixelBuffer - int64_t bufSize) { + int32_t bufSize) { ATRACE_CALL(); status_t err = NO_ERROR; ANativeWindowBuffer* anb; - ALOGV("%s: Dequeue buffer from %p",__FUNCTION__, anw.get()); + ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)", + __FUNCTION__, anw.get(), width, height, pixelFmt, bufSize); + + if (anw == 0) { + ALOGE("%s: anw must not be NULL", __FUNCTION__); + return BAD_VALUE; + } else if (pixelBuffer == NULL) { + ALOGE("%s: pixelBuffer must not be NULL", __FUNCTION__); + return BAD_VALUE; + } else if (width < 0) { + ALOGE("%s: width must be non-negative", __FUNCTION__); + return BAD_VALUE; + } else if (height < 0) { + ALOGE("%s: height must be non-negative", __FUNCTION__); + return BAD_VALUE; + } else if (bufSize < 0) { + ALOGE("%s: bufSize must be non-negative", __FUNCTION__); + return BAD_VALUE; + } + + if (width < 0 || height < 0 || bufSize < 0) { + ALOGE("%s: Illegal argument, negative dimension passed to produceFrame", __FUNCTION__); + return BAD_VALUE; + } // TODO: Switch to using Surface::lock and Surface::unlockAndPost err = native_window_dequeue_buffer_and_wait(anw.get(), &anb); if (err != NO_ERROR) return err; + // TODO: check anb is large enough to store the results + sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false)); switch(pixelFmt) { @@ -181,6 +211,41 @@ static status_t produceFrame(const sp<ANativeWindow>& anw, uPlane, vPlane, chromaStep, yStride, chromaStride); break; } + case HAL_PIXEL_FORMAT_YV12: { + if (bufSize < width * height * 4) { + ALOGE("%s: PixelBuffer size %lld to small for given dimensions", __FUNCTION__, + bufSize); + return BAD_VALUE; + } + + if ((width & 1) || (height & 1)) { + ALOGE("%s: Dimens %dx%d are not divisible by 2.", __FUNCTION__, width, height); + return BAD_VALUE; + } + + uint8_t* img = NULL; + ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get()); + err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + if (err != NO_ERROR) { + ALOGE("%s: Error %s (%d) while locking gralloc buffer for write.", __FUNCTION__, + strerror(-err), err); + return err; + } + + uint32_t stride = buf->getStride(); + LOG_ALWAYS_FATAL_IF(stride % 16, "Stride is not 16 pixel aligned %d", stride); + + uint32_t cStride = ALIGN(stride / 2, 16); + size_t chromaStep = 1; + + uint8_t* yPlane = img; + uint8_t* crPlane = img + static_cast<uint32_t>(height) * stride; + uint8_t* cbPlane = crPlane + cStride * static_cast<uint32_t>(height) / 2; + + rgbToYuv420(pixelBuffer, width, height, yPlane, + crPlane, cbPlane, chromaStep, stride, cStride); + break; + } case HAL_PIXEL_FORMAT_YCbCr_420_888: { // Software writes with YCbCr_420_888 format are unsupported // by the gralloc module for now @@ -215,7 +280,12 @@ static status_t produceFrame(const sp<ANativeWindow>& anw, err); return err; } + struct camera3_jpeg_blob footer = { + jpeg_blob_id: CAMERA3_JPEG_BLOB_ID, + jpeg_size: (uint32_t)width + }; memcpy(img, pixelBuffer, width); + memcpy(img + anb->width - sizeof(footer), &footer, sizeof(footer)); break; } default: { diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index a2b1ed9..807dd32 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -20,11 +20,15 @@ #include <system/audio.h> // keep these values in sync with AudioFormat.java -#define ENCODING_PCM_16BIT 2 -#define ENCODING_PCM_8BIT 3 -#define ENCODING_PCM_FLOAT 4 -#define ENCODING_INVALID 0 -#define ENCODING_DEFAULT 1 +#define ENCODING_PCM_16BIT 2 +#define ENCODING_PCM_8BIT 3 +#define ENCODING_PCM_FLOAT 4 +#define ENCODING_AC3 5 +#define ENCODING_E_AC3 6 +#define ENCODING_INVALID 0 +#define ENCODING_DEFAULT 1 + + #define CHANNEL_INVALID 0 #define CHANNEL_OUT_DEFAULT 1 @@ -38,6 +42,10 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_PCM_8_BIT; case ENCODING_PCM_FLOAT: return AUDIO_FORMAT_PCM_FLOAT; + case ENCODING_AC3: + return AUDIO_FORMAT_AC3; + case ENCODING_E_AC3: + return AUDIO_FORMAT_E_AC3; case ENCODING_DEFAULT: return AUDIO_FORMAT_DEFAULT; default: @@ -54,6 +62,10 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) return ENCODING_PCM_8BIT; case AUDIO_FORMAT_PCM_FLOAT: return ENCODING_PCM_FLOAT; + case AUDIO_FORMAT_AC3: + return ENCODING_AC3; + case AUDIO_FORMAT_E_AC3: + return ENCODING_E_AC3; case AUDIO_FORMAT_DEFAULT: return ENCODING_DEFAULT; default: diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index bf47dd3..849531c 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -244,6 +244,12 @@ android_media_AudioSystem_isSourceActive(JNIEnv *env, jobject thiz, jint source) } static jint +android_media_AudioSystem_newAudioSessionId(JNIEnv *env, jobject thiz) +{ + return AudioSystem::newAudioSessionId(); +} + +static jint android_media_AudioSystem_setParameters(JNIEnv *env, jobject thiz, jstring keyValuePairs) { const jchar* c_keyValuePairs = env->GetStringCritical(keyValuePairs, 0); @@ -281,6 +287,8 @@ android_media_AudioSystem_error_callback(status_t err) env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz, "errorCallbackFromNative","(I)V"), check_AudioSystem_Command(err)); + + env->DeleteLocalRef(clazz); } static jint @@ -1295,6 +1303,7 @@ static JNINativeMethod gMethods[] = { {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive}, {"isStreamActiveRemotely","(II)Z", (void *)android_media_AudioSystem_isStreamActiveRemotely}, {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive}, + {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId}, {"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState}, {"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState}, {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState}, diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index e548e91..264a9ae 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -220,8 +220,13 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, } // compute the frame count - const size_t bytesPerSample = audio_bytes_per_sample(format); - size_t frameCount = buffSizeInBytes / (channelCount * bytesPerSample); + size_t frameCount; + if (audio_is_linear_pcm(format)) { + const size_t bytesPerSample = audio_bytes_per_sample(format); + frameCount = buffSizeInBytes / (channelCount * bytesPerSample); + } else { + frameCount = buffSizeInBytes; + } jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { @@ -266,7 +271,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, format,// word length, PCM nativeChannelMask, frameCount, - AUDIO_OUTPUT_FLAG_NONE, + audio_is_linear_pcm(format) ? AUDIO_OUTPUT_FLAG_NONE : AUDIO_OUTPUT_FLAG_DIRECT, audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user) 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 0,// shared mem @@ -478,14 +483,6 @@ jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const jbyte* da switch (format) { default: - // TODO Currently the only possible values for format are AUDIO_FORMAT_PCM_16_BIT, - // AUDIO_FORMAT_PCM_8_BIT, and AUDIO_FORMAT_PCM_FLOAT, - // due to the limited set of values for audioFormat. - // The next section of the switch will probably work for more formats, but it has only - // been tested for AUDIO_FORMAT_PCM_16_BIT and AUDIO_FORMAT_PCM_FLOAT, - // so that's why the "default" case fails. - break; - case AUDIO_FORMAT_PCM_FLOAT: case AUDIO_FORMAT_PCM_16_BIT: { // writing to shared memory, check for capacity @@ -904,8 +901,12 @@ static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thi return -1; } const audio_format_t format = audioFormatToNative(audioFormat); - const size_t bytesPerSample = audio_bytes_per_sample(format); - return frameCount * channelCount * bytesPerSample; + if (audio_is_linear_pcm(format)) { + const size_t bytesPerSample = audio_bytes_per_sample(format); + return frameCount * channelCount * bytesPerSample; + } else { + return frameCount; + } } // ---------------------------------------------------------------------------- diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 86207f0..87ee618 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -174,21 +174,21 @@ static int read_memtrack_memory(struct memtrack_proc* p, int pid, ssize_t pss = memtrack_proc_graphics_pss(p); if (pss < 0) { - ALOGW("failed to get graphics pss: %d", pss); + ALOGW("failed to get graphics pss: %zd", pss); return pss; } graphics_mem->graphics = pss / 1024; pss = memtrack_proc_gl_pss(p); if (pss < 0) { - ALOGW("failed to get gl pss: %d", pss); + ALOGW("failed to get gl pss: %zd", pss); return pss; } graphics_mem->gl = pss / 1024; pss = memtrack_proc_other_pss(p); if (pss < 0) { - ALOGW("failed to get other pss: %d", pss); + ALOGW("failed to get other pss: %zd", pss); return pss; } graphics_mem->other = pss / 1024; @@ -231,9 +231,9 @@ static void read_mapinfo(FILE *fp, stats_t* stats) unsigned referenced = 0; unsigned temp; - unsigned long int start; - unsigned long int end = 0; - unsigned long int prevEnd = 0; + uint64_t start; + uint64_t end = 0; + uint64_t prevEnd = 0; char* name; int name_pos; @@ -255,7 +255,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats) if (len < 1) return; line[--len] = 0; - if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) { + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) { skip = true; } else { while (isspace(line[name_pos])) { @@ -371,7 +371,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats) referenced = temp; } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) { swapped_out = temp; - } else if (strlen(line) > 30 && line[8] == '-' && line[17] == ' ') { + } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) { // looks like a new mapping // example: "10000000-10001000 ---p 10000000 00:00 0" break; diff --git a/core/jni/android_server_FingerprintManager.cpp b/core/jni/android_server_FingerprintManager.cpp index f8a1fd9..b174d1b 100644 --- a/core/jni/android_server_FingerprintManager.cpp +++ b/core/jni/android_server_FingerprintManager.cpp @@ -20,54 +20,185 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> +#include <hardware/hardware.h> +#include <hardware/fingerprint.h> #include <utils/Log.h> +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method" methodName); + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + namespace android { +static const uint16_t kVersion = HARDWARE_MODULE_API_VERSION(1, 0); + +static const char* FINGERPRINT_SERVICE = "com/android/server/fingerprint/FingerprintService"; static struct { jclass clazz; jmethodID notify; -} gFingerprintManagerClassInfo; + jobject callbackObject; +} gFingerprintServiceClassInfo; + +static struct { + fingerprint_module_t const* module; + fingerprint_device_t *device; +} gContext; + +// Called by the HAL to notify us of fingerprint events +static void hal_notify_callback(fingerprint_msg_t msg) { + uint32_t arg1 = 0; + uint32_t arg2 = 0; + uint32_t arg3 = 0; // TODO + switch (msg.type) { + case FINGERPRINT_ERROR: + arg1 = msg.data.error; + break; + case FINGERPRINT_ACQUIRED: + arg1 = msg.data.acquired.acquired_info; + break; + case FINGERPRINT_PROCESSED: + arg1 = msg.data.processed.id; + break; + case FINGERPRINT_TEMPLATE_ENROLLING: + arg1 = msg.data.enroll.id; + arg2 = msg.data.enroll.samples_remaining; + arg3 = msg.data.enroll.data_collected_bmp; + break; + case FINGERPRINT_TEMPLATE_REMOVED: + arg1 = msg.data.removed.id; + break; + default: + ALOGE("fingerprint: invalid msg: %d", msg.type); + return; + } + //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2); + + // TODO: fix gross hack to attach JNI to calling thread + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->AttachCurrentThread(&env, (void*) &args); + if (result != JNI_OK) { + ALOGE("Can't call JNI method: attach failed: %#x", result); + return; + } + } + env->CallVoidMethod(gFingerprintServiceClassInfo.callbackObject, + gFingerprintServiceClassInfo.notify, msg.type, arg1, arg2); +} + +static void nativeInit(JNIEnv *env, jobject clazz, jobject callbackObj) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeInit()\n"); + FIND_CLASS(gFingerprintServiceClassInfo.clazz, FINGERPRINT_SERVICE); + GET_METHOD_ID(gFingerprintServiceClassInfo.notify, gFingerprintServiceClassInfo.clazz, + "notify", "(III)V"); + gFingerprintServiceClassInfo.callbackObject = env->NewGlobalRef(callbackObj); +} static jint nativeEnroll(JNIEnv* env, jobject clazz, jint timeout) { - return -1; // TODO + ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll()\n"); + int ret = gContext.device->enroll(gContext.device, timeout); + return reinterpret_cast<jint>(ret); +} + +static jint nativeEnrollCancel(JNIEnv* env, jobject clazz) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnrollCancel()\n"); + int ret = gContext.device->enroll_cancel(gContext.device); + return reinterpret_cast<jint>(ret); } static jint nativeRemove(JNIEnv* env, jobject clazz, jint fingerprintId) { - return -1; // TODO + ALOG(LOG_VERBOSE, LOG_TAG, "nativeRemove(%d)\n", fingerprintId); + int ret = gContext.device->remove(gContext.device, fingerprintId); + return reinterpret_cast<jint>(ret); +} + +static jint nativeOpenHal(JNIEnv* env, jobject clazz) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeOpenHal()\n"); + int err; + const hw_module_t *hw_module = NULL; + if (0 != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) { + ALOGE("Can't open fingerprint HW Module, error: %d", err); + return 0; + } + if (NULL == hw_module) { + ALOGE("No valid fingerprint module"); + return 0; + } + + gContext.module = reinterpret_cast<const fingerprint_module_t*>(hw_module); + + if (gContext.module->common.methods->open == NULL) { + ALOGE("No valid open method"); + return 0; + } + + hw_device_t *device = NULL; + + if (0 != (err = gContext.module->common.methods->open(hw_module, NULL, &device))) { + ALOGE("Can't open fingerprint methods, error: %d", err); + return 0; + } + + if (kVersion != device->version) { + ALOGE("Wrong fp version. Expected %d, got %d", kVersion, device->version); + // return 0; // FIXME + } + + gContext.device = reinterpret_cast<fingerprint_device_t*>(device); + err = gContext.device->set_notify(gContext.device, hal_notify_callback); + if (err < 0) { + ALOGE("Failed in call to set_notify(), err=%d", err); + return 0; + } + + // Sanity check - remove + if (gContext.device->notify != hal_notify_callback) { + ALOGE("NOTIFY not set properly: %p != %p", gContext.device->notify, hal_notify_callback); + } + + ALOG(LOG_VERBOSE, LOG_TAG, "fingerprint HAL successfully initialized"); + return reinterpret_cast<jlong>(gContext.device); +} + +static jint nativeCloseHal(JNIEnv* env, jobject clazz) { + return -ENOSYS; // TODO } // ---------------------------------------------------------------------------- +// TODO: clean up void methods static const JNINativeMethod g_methods[] = { { "nativeEnroll", "(I)I", (void*)nativeEnroll }, + { "nativeEnrollCancel", "()I", (void*)nativeEnroll }, { "nativeRemove", "(I)I", (void*)nativeRemove }, + { "nativeOpenHal", "()I", (void*)nativeOpenHal }, + { "nativeCloseHal", "()I", (void*)nativeCloseHal }, + { "nativeInit", "(Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit } }; -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); - -#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ - var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find static method" methodName); - -#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ - var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find method" methodName); - -#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ - var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find field " fieldName); - -int register_android_server_FingerprintManager(JNIEnv* env) { - FIND_CLASS(gFingerprintManagerClassInfo.clazz, - "android/service/fingerprint/FingerprintManager"); - GET_METHOD_ID(gFingerprintManagerClassInfo.notify, gFingerprintManagerClassInfo.clazz, - "notify", "(III)V"); - return AndroidRuntime::registerNativeMethods( - env, "com/android/service/fingerprint/FingerprintManager", g_methods, NELEM(g_methods)); +int register_android_server_fingerprint_FingerprintService(JNIEnv* env) { + FIND_CLASS(gFingerprintServiceClassInfo.clazz, FINGERPRINT_SERVICE); + GET_METHOD_ID(gFingerprintServiceClassInfo.notify, gFingerprintServiceClassInfo.clazz, "notify", + "(III)V"); + int result = AndroidRuntime::registerNativeMethods( + env, FINGERPRINT_SERVICE, g_methods, NELEM(g_methods)); + ALOG(LOG_VERBOSE, LOG_TAG, "FingerprintManager JNI ready.\n"); + return result; } } // namespace android diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 01f4d3a..a6b65cc 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -45,6 +45,8 @@ #define POLICY_DEBUG 0 #define GUARD_THREAD_PRIORITY 0 +#define DEBUG_PROC(x) //x + using namespace android; #if GUARD_THREAD_PRIORITY @@ -725,6 +727,7 @@ jboolean android_os_Process_parseProcLineArray(JNIEnv* env, jobject clazz, const char term = (char)(mode&PROC_TERM_MASK); const jsize start = i; if (i >= endIndex) { + DEBUG_PROC(ALOGW("Ran off end of data @%d", i)); res = JNI_FALSE; break; } @@ -822,19 +825,20 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz, return JNI_FALSE; } int fd = open(file8, O_RDONLY); - env->ReleaseStringUTFChars(file, file8); if (fd < 0) { - //ALOGW("Unable to open process file: %s\n", file8); + DEBUG_PROC(ALOGW("Unable to open process file: %s\n", file8)); + env->ReleaseStringUTFChars(file, file8); return JNI_FALSE; } + env->ReleaseStringUTFChars(file, file8); char buffer[256]; const int len = read(fd, buffer, sizeof(buffer)-1); close(fd); if (len < 0) { - //ALOGW("Unable to open process file: %s fd=%d\n", file8, fd); + DEBUG_PROC(ALOGW("Unable to open process file: %s fd=%d\n", file8, fd)); return JNI_FALSE; } buffer[len] = 0; diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index d032cb6..2a1fd1b 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -20,15 +20,10 @@ #include "GraphicsJNI.h" #include <nativehelper/JNIHelp.h> -#include "android_view_GraphicBuffer.h" - #include <android_runtime/AndroidRuntime.h> -#include <android_runtime/android_graphics_SurfaceTexture.h> #include <androidfw/ResourceTypes.h> -#include <gui/GLConsumer.h> - #include <private/hwui/DrawGlInfo.h> #include <cutils/properties.h> @@ -37,15 +32,13 @@ #include <SkCanvas.h> #include <SkMatrix.h> #include <SkPaint.h> +#include <SkPorterDuff.h> #include <SkRegion.h> #include <SkScalerContext.h> #include <SkTemplates.h> #include <SkXfermode.h> #include <DisplayListRenderer.h> -#include <LayerRenderer.h> -#include <OpenGLRenderer.h> -#include <Stencil.h> #include <Rect.h> #include <RenderNode.h> #include <CanvasProperty.h> @@ -64,7 +57,7 @@ namespace android { using namespace uirenderer; /** - * Note: OpenGLRenderer JNI layer is generated and compiled only on supported + * Note: DisplayListRenderer JNI layer is generated and compiled only on supported * devices. This means all the logic must be compiled only when the * preprocessor variable USE_OPENGL_RENDERER is defined. */ @@ -91,57 +84,13 @@ static struct { } gRectClassInfo; // ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_flushCaches(JNIEnv* env, jobject clazz, - jint mode) { - if (Caches::hasInstance()) { - Caches::getInstance().flush(static_cast<Caches::FlushMode>(mode)); - } -} - -static jboolean android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - return Caches::getInstance().init() ? JNI_TRUE : JNI_FALSE; - } - return JNI_FALSE; -} - -static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - Caches::getInstance().terminate(); - } -} - -// ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz, - jobject graphicBuffer, jlongArray atlasMapArray, jint count) { - - sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); - jlong* jAtlasMap = env->GetLongArrayElements(atlasMapArray, NULL); - Caches::getInstance().assetAtlas.init(buffer, jAtlasMap, count); - env->ReleaseLongArrayElements(atlasMapArray, jAtlasMap, 0); -} - -// ---------------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------------- -static jlong android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) { - RENDERER_LOGD("Create OpenGLRenderer"); - OpenGLRenderer* renderer = new OpenGLRenderer(); - renderer->initProperties(); - return reinterpret_cast<jlong>(renderer); -} - static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz, jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - RENDERER_LOGD("Destroy OpenGLRenderer"); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); + RENDERER_LOGD("Destroy DisplayListRenderer"); delete renderer; } @@ -151,33 +100,29 @@ static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz, jlong rendererPtr, jint width, jint height) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->setViewport(width, height); } static int android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz, jlong rendererPtr, jboolean opaque) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); return renderer->prepare(opaque); } static int android_view_GLES20Canvas_prepareDirty(JNIEnv* env, jobject clazz, jlong rendererPtr, jint left, jint top, jint right, jint bottom, jboolean opaque) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); return renderer->prepareDirty(left, top, right, bottom, opaque); } static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz, jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->finish(); } -static jint android_view_GLES20Canvas_getStencilSize(JNIEnv* env, jobject clazz) { - return Stencil::getStencilSize(); -} - static void android_view_GLES20Canvas_setProperty(JNIEnv* env, jobject clazz, jstring name, jstring value) { if (!Caches::hasInstance()) { @@ -202,7 +147,7 @@ static void android_view_GLES20Canvas_setProperty(JNIEnv* env, static jint android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong functorPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); Functor* functor = reinterpret_cast<Functor*>(functorPtr); android::uirenderer::Rect dirty; return renderer->callDrawGLFunction(functor, dirty); @@ -226,25 +171,25 @@ static jint android_view_GLES20Canvas_getMaxTextureHeight(JNIEnv* env, jobject c static jint android_view_GLES20Canvas_save(JNIEnv* env, jobject clazz, jlong rendererPtr, jint flags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); return renderer->save(flags); } static jint android_view_GLES20Canvas_getSaveCount(JNIEnv* env, jobject clazz, jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); return renderer->getSaveCount(); } static void android_view_GLES20Canvas_restore(JNIEnv* env, jobject clazz, jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->restore(); } static void android_view_GLES20Canvas_restoreToCount(JNIEnv* env, jobject clazz, jlong rendererPtr, jint saveCount) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->restoreToCount(saveCount); } @@ -255,14 +200,14 @@ static void android_view_GLES20Canvas_restoreToCount(JNIEnv* env, jobject clazz, static jint android_view_GLES20Canvas_saveLayer(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintPtr, jint saveFlags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); return renderer->saveLayer(left, top, right, bottom, paint, saveFlags); } static jint android_view_GLES20Canvas_saveLayerClip(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong paintPtr, jint saveFlags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); return renderer->saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, @@ -272,13 +217,13 @@ static jint android_view_GLES20Canvas_saveLayerClip(JNIEnv* env, jobject clazz, static jint android_view_GLES20Canvas_saveLayerAlpha(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jint alpha, jint saveFlags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); return renderer->saveLayerAlpha(left, top, right, bottom, alpha, saveFlags); } static jint android_view_GLES20Canvas_saveLayerAlphaClip(JNIEnv* env, jobject clazz, jlong rendererPtr, jint alpha, jint saveFlags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); return renderer->saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, alpha, saveFlags); @@ -290,7 +235,7 @@ static jint android_view_GLES20Canvas_saveLayerAlphaClip(JNIEnv* env, jobject cl static jboolean android_view_GLES20Canvas_quickReject(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const bool result = renderer->quickRejectConservative(left, top, right, bottom); return result ? JNI_TRUE : JNI_FALSE; } @@ -298,7 +243,7 @@ static jboolean android_view_GLES20Canvas_quickReject(JNIEnv* env, jobject clazz static jboolean android_view_GLES20Canvas_clipRectF(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jint op) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const bool result = renderer->clipRect(left, top, right, bottom, static_cast<SkRegion::Op>(op)); return result ? JNI_TRUE : JNI_FALSE; @@ -307,7 +252,7 @@ static jboolean android_view_GLES20Canvas_clipRectF(JNIEnv* env, jobject clazz, static jboolean android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject clazz, jlong rendererPtr, jint left, jint top, jint right, jint bottom, jint op) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const bool result = renderer->clipRect(float(left), float(top), float(right), float(bottom), static_cast<SkRegion::Op>(op)); @@ -316,7 +261,7 @@ static jboolean android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject clazz, static jboolean android_view_GLES20Canvas_clipPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong pathPtr, jint op) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); const bool result = renderer->clipPath(path, static_cast<SkRegion::Op>(op)); return result ? JNI_TRUE : JNI_FALSE; @@ -324,7 +269,7 @@ static jboolean android_view_GLES20Canvas_clipPath(JNIEnv* env, jobject clazz, static jboolean android_view_GLES20Canvas_clipRegion(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong regionPtr, jint op) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr); const bool result = renderer->clipRegion(region, static_cast<SkRegion::Op>(op)); return result ? JNI_TRUE : JNI_FALSE; @@ -332,7 +277,7 @@ static jboolean android_view_GLES20Canvas_clipRegion(JNIEnv* env, jobject clazz, static jboolean android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject clazz, jlong rendererPtr, jobject rect) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); env->CallVoidMethod(rect, gRectClassInfo.set, @@ -347,47 +292,47 @@ static jboolean android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject cla static void android_view_GLES20Canvas_translate(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat dx, jfloat dy) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->translate(dx, dy); } static void android_view_GLES20Canvas_rotate(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat degrees) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->rotate(degrees); } static void android_view_GLES20Canvas_scale(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat sx, jfloat sy) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->scale(sx, sy); } static void android_view_GLES20Canvas_skew(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat sx, jfloat sy) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->skew(sx, sy); } static void android_view_GLES20Canvas_setMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->setMatrix(matrix); + renderer->setMatrix(matrix ? *matrix : SkMatrix::I()); } static void android_view_GLES20Canvas_getMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); renderer->getMatrix(matrix); } static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->concatMatrix(matrix); + renderer->concatMatrix(*matrix); } // ---------------------------------------------------------------------------- @@ -401,7 +346,7 @@ static void android_view_GLES20Canvas_drawBitmap(JNIEnv* env, jobject clazz, // This object allows the renderer to allocate a global JNI ref to the buffer object. JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawBitmap(bitmap, left, top, paint); } @@ -414,7 +359,7 @@ static void android_view_GLES20Canvas_drawBitmapRect(JNIEnv* env, jobject clazz, // This object allows the renderer to allocate a global JNI ref to the buffer object. JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint); @@ -427,20 +372,20 @@ static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject claz // This object allows the renderer to allocate a global JNI ref to the buffer object. JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawBitmap(bitmap, matrix, paint); + renderer->drawBitmap(bitmap, *matrix, paint); } static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, jlong rendererPtr, jintArray colors, jint offset, jint stride, jfloat left, jfloat top, jint width, jint height, jboolean hasAlpha, jlong paintPtr) { + const SkImageInfo info = SkImageInfo::Make(width, height, + hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType, + kPremul_SkAlphaType); SkBitmap* bitmap = new SkBitmap; - bitmap->setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config, - width, height); - - if (!bitmap->allocPixels()) { + if (!bitmap->allocPixels(info)) { delete bitmap; return; } @@ -450,7 +395,7 @@ static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, return; } - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawBitmapData(bitmap, left, top, paint); @@ -471,7 +416,7 @@ static void android_view_GLES20Canvas_drawBitmapMesh(JNIEnv* env, jobject clazz, jfloat* verticesArray = vertices ? env->GetFloatArrayElements(vertices, NULL) + offset : NULL; jint* colorsArray = colors ? env->GetIntArrayElements(colors, NULL) + colorOffset : NULL; - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawBitmapMesh(bitmap, meshWidth, meshHeight, verticesArray, colorsArray, paint); @@ -486,22 +431,23 @@ static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz, // This object allows the renderer to allocate a global JNI ref to the buffer object. JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(patchPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint); } static void android_view_GLES20Canvas_drawColor(JNIEnv* env, jobject clazz, - jlong rendererPtr, jint color, jint mode) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->drawColor(color, static_cast<SkXfermode::Mode>(mode)); + jlong rendererPtr, jint color, jint modeHandle) { + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); + SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle); + renderer->drawColor(color, SkPorterDuff::ToXfermodeMode(mode)); } static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawRect(left, top, right, bottom, paint); } @@ -509,21 +455,21 @@ static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawRoundRect(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawRoundRect(left, top, right, bottom, rx, ry, paint); } static void android_view_GLES20Canvas_drawCircle(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat x, jfloat y, jfloat radius, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawCircle(x, y, radius, paint); } static void android_view_GLES20Canvas_drawCircleProps(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); @@ -534,7 +480,7 @@ static void android_view_GLES20Canvas_drawCircleProps(JNIEnv* env, jobject clazz static void android_view_GLES20Canvas_drawOval(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawOval(left, top, right, bottom, paint); } @@ -542,14 +488,14 @@ static void android_view_GLES20Canvas_drawOval(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawArc(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, jboolean useCenter, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint); } static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong regionPtr, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); if (paint->getStyle() != SkPaint::kFill_Style || @@ -577,18 +523,9 @@ static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject cla } } -static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject clazz, - jlong rendererPtr, jfloatArray rects, jint count, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - jfloat* storage = env->GetFloatArrayElements(rects, NULL); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawRects(storage, count, paint); - env->ReleaseFloatArrayElements(rects, storage, 0); -} - static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); jfloat* storage = env->GetFloatArrayElements(points, NULL); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawPoints(storage + offset, count, paint); @@ -597,7 +534,7 @@ static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong pathPtr, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawPath(path, paint); @@ -605,7 +542,7 @@ static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); jfloat* storage = env->GetFloatArrayElements(points, NULL); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); renderer->drawLines(storage + offset, count, paint); @@ -618,13 +555,13 @@ static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_setupPaintFilter(JNIEnv* env, jobject clazz, jlong rendererPtr, jint clearBits, jint setBits) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->setupPaintFilter(clearBits, setBits); } static void android_view_GLES20Canvas_resetPaintFilter(JNIEnv* env, jobject clazz, jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); renderer->resetPaintFilter(); } @@ -651,7 +588,7 @@ static float xOffsetForTextAlign(SkPaint* paint, float totalAdvance) { class RenderTextFunctor { public: - RenderTextFunctor(const Layout& layout, OpenGLRenderer* renderer, jfloat x, jfloat y, + RenderTextFunctor(const Layout& layout, DisplayListRenderer* renderer, jfloat x, jfloat y, SkPaint* paint, uint16_t* glyphs, float* pos, float totalAdvance, uirenderer::Rect& bounds) : layout(layout), renderer(renderer), x(x), y(y), paint(paint), glyphs(glyphs), @@ -669,7 +606,7 @@ public: } private: const Layout& layout; - OpenGLRenderer* renderer; + DisplayListRenderer* renderer; jfloat x; jfloat y; SkPaint* paint; @@ -679,7 +616,7 @@ private: uirenderer::Rect& bounds; }; -static void renderTextLayout(OpenGLRenderer* renderer, Layout* layout, +static void renderTextLayout(DisplayListRenderer* renderer, Layout* layout, jfloat x, jfloat y, SkPaint* paint) { size_t nGlyphs = layout->nGlyphs(); float* pos = new float[nGlyphs * 2]; @@ -697,17 +634,17 @@ static void renderTextLayout(OpenGLRenderer* renderer, Layout* layout, } #endif -static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, - jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) { +static void renderText(DisplayListRenderer* renderer, const jchar* text, int count, + jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) { #ifdef USE_MINIKIN Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); layout.doLayout(text, 0, count, count, css); x += xOffsetForTextAlign(paint, layout.getAdvance()); renderTextLayout(renderer, &layout, x, y, paint); #else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count, flags); + text, 0, count, count, bidiFlags); if (value == NULL) { return; } @@ -726,10 +663,50 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, #endif } -static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count, - SkPath* path, jfloat hOffset, jfloat vOffset, int flags, SkPaint* paint) { +#ifdef USE_MINIKIN +class RenderTextOnPathFunctor { +public: + RenderTextOnPathFunctor(const Layout& layout, DisplayListRenderer* renderer, float hOffset, + float vOffset, SkPaint* paint, SkPath* path) + : layout(layout), renderer(renderer), hOffset(hOffset), vOffset(vOffset), + paint(paint), path(path) { + } + void operator()(size_t start, size_t end) { + uint16_t glyphs[1]; + for (size_t i = start; i < end; i++) { + glyphs[0] = layout.getGlyphId(i); + float x = hOffset + layout.getX(i); + float y = vOffset + layout.getY(i); + renderer->drawTextOnPath((const char*) glyphs, sizeof(glyphs), 1, path, x, y, paint); + } + } +private: + const Layout& layout; + DisplayListRenderer* renderer; + float hOffset; + float vOffset; + SkPaint* paint; + SkPath* path; +}; +#endif + +static void renderTextOnPath(DisplayListRenderer* renderer, const jchar* text, int count, + SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, SkPaint* paint, + TypefaceImpl* typeface) { +#ifdef USE_MINIKIN + Layout layout; + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); + hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path); + SkPaint::Align align = paint->getTextAlign(); + paint->setTextAlign(SkPaint::kLeft_Align); + + RenderTextOnPathFunctor f(layout, renderer, hOffset, vOffset, paint, path); + MinikinUtils::forFontRun(layout, paint, f); + paint->setTextAlign(align); +#else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count, flags); + text, 0, count, count, bidiFlags); if (value == NULL) { return; } @@ -738,20 +715,21 @@ static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int co int bytesCount = glyphsCount * sizeof(jchar); renderer->drawTextOnPath((const char*) glyphs, bytesCount, glyphsCount, path, hOffset, vOffset, paint); +#endif } -static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, +static void renderTextRun(DisplayListRenderer* renderer, const jchar* text, jint start, jint count, jint contextCount, jfloat x, jfloat y, - int flags, SkPaint* paint, TypefaceImpl* typeface) { + int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) { #ifdef USE_MINIKIN Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); layout.doLayout(text, start, count, contextCount, css); x += xOffsetForTextAlign(paint, layout.getAdvance()); renderTextLayout(renderer, &layout, x, y, paint); #else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, - text, start, count, contextCount, flags); + text, start, count, contextCount, bidiFlags); if (value == NULL) { return; } @@ -772,124 +750,87 @@ static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz, jlong rendererPtr, jcharArray text, jint index, jint count, - jfloat x, jfloat y, jint flags, jlong paintPtr, jlong typefacePtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + jfloat x, jfloat y, jint bidiFlags, jlong paintPtr, jlong typefacePtr) { + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); jchar* textArray = env->GetCharArrayElements(text, NULL); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); - renderText(renderer, textArray + index, count, x, y, flags, paint, typeface); + renderText(renderer, textArray + index, count, x, y, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz, jlong rendererPtr, jstring text, jint start, jint end, - jfloat x, jfloat y, jint flags, jlong paintPtr, jlong typefacePtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + jfloat x, jfloat y, jint bidiFlags, jlong paintPtr, jlong typefacePtr) { + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const jchar* textArray = env->GetStringChars(text, NULL); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); - renderText(renderer, textArray + start, end - start, x, y, flags, paint, typeface); + renderText(renderer, textArray + start, end - start, x, y, bidiFlags, paint, typeface); env->ReleaseStringChars(text, textArray); } static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jcharArray text, jint index, jint count, - jlong pathPtr, jfloat hOffset, jfloat vOffset, jint flags, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr, + jlong typefacePtr) { + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); jchar* textArray = env->GetCharArrayElements(text, NULL); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); renderTextOnPath(renderer, textArray + index, count, path, - hOffset, vOffset, flags, paint); + hOffset, vOffset, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jstring text, jint start, jint end, - jlong pathPtr, jfloat hOffset, jfloat vOffset, jint flags, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr, + jlong typefacePtr) { + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const jchar* textArray = env->GetStringChars(text, NULL); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); renderTextOnPath(renderer, textArray + start, end - start, path, - hOffset, vOffset, flags, paint); + hOffset, vOffset, bidiFlags, paint, typeface); env->ReleaseStringChars(text, textArray); } static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject clazz, jlong rendererPtr, jcharArray text, jint index, jint count, - jint contextIndex, jint contextCount, jfloat x, jfloat y, jint dirFlags, + jint contextIndex, jint contextCount, jfloat x, jfloat y, jboolean isRtl, jlong paintPtr, jlong typefacePtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); jchar* textArray = env->GetCharArrayElements(text, NULL); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); + int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; renderTextRun(renderer, textArray + contextIndex, index - contextIndex, - count, contextCount, x, y, dirFlags, paint, typeface); + count, contextCount, x, y, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz, jlong rendererPtr, jstring text, jint start, jint end, - jint contextStart, int contextEnd, jfloat x, jfloat y, jint dirFlags, + jint contextStart, int contextEnd, jfloat x, jfloat y, jboolean isRtl, jlong paintPtr, jlong typefacePtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); const jchar* textArray = env->GetStringChars(text, NULL); jint count = end - start; jint contextCount = contextEnd - contextStart; SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); + int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; renderTextRun(renderer, textArray + contextStart, start - contextStart, - count, contextCount, x, y, dirFlags, paint, typeface); - env->ReleaseStringChars(text, textArray); -} - -static void renderPosText(OpenGLRenderer* renderer, const jchar* text, int count, - const jfloat* positions, jint dirFlags, SkPaint* paint) { - sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count, dirFlags); - if (value == NULL) { - return; - } - const jchar* glyphs = value->getGlyphs(); - size_t glyphsCount = value->getGlyphsCount(); - if (count < int(glyphsCount)) glyphsCount = count; - int bytesCount = glyphsCount * sizeof(jchar); - - renderer->drawPosText((const char*) glyphs, bytesCount, glyphsCount, positions, paint); -} - -static void android_view_GLES20Canvas_drawPosTextArray(JNIEnv* env, jobject clazz, - jlong rendererPtr, jcharArray text, jint index, jint count, - jfloatArray pos, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - jchar* textArray = env->GetCharArrayElements(text, NULL); - jfloat* positions = env->GetFloatArrayElements(pos, NULL); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - - renderPosText(renderer, textArray + index, count, positions, kBidi_LTR, paint); - - env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT); - env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); -} - -static void android_view_GLES20Canvas_drawPosText(JNIEnv* env, jobject clazz, - jlong rendererPtr, jstring text, jint start, jint end, - jfloatArray pos, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - const jchar* textArray = env->GetStringChars(text, NULL); - jfloat* positions = env->GetFloatArrayElements(pos, NULL); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - - renderPosText(renderer, textArray + start, end - start, positions, kBidi_LTR, paint); - - env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT); + count, contextCount, x, y, bidiFlags, paint, typeface); env->ReleaseStringChars(text, textArray); } @@ -907,13 +848,13 @@ static jlong android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, jo return reinterpret_cast<jlong>(new DisplayListRenderer); } -static jint android_view_GLES20Canvas_drawDisplayList(JNIEnv* env, - jobject clazz, jlong rendererPtr, jlong displayListPtr, +static jint android_view_GLES20Canvas_drawRenderNode(JNIEnv* env, + jobject clazz, jlong rendererPtr, jlong renderNodePtr, jobject dirty, jint flags) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); android::uirenderer::Rect bounds; - status_t status = renderer->drawDisplayList(displayList, bounds, flags); + status_t status = renderer->drawRenderNode(renderNode, bounds, flags); if (status != DrawGlInfo::kStatusDone && dirty != NULL) { env->CallVoidMethod(dirty, gRectClassInfo.set, int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom)); @@ -927,44 +868,11 @@ static jint android_view_GLES20Canvas_drawDisplayList(JNIEnv* env, static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong layerPtr, jfloat x, jfloat y) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); + DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); Layer* layer = reinterpret_cast<Layer*>(layerPtr); renderer->drawLayer(layer, x, y); } -static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr, jlong bitmapPtr) { - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr); - return LayerRenderer::copyLayer(layer, bitmap); -} - -static void android_view_GLES20Canvas_pushLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->pushLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_cancelLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->cancelLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_clearLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->clearLayerUpdates(); -} - -static void android_view_GLES20Canvas_flushLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->flushLayerUpdates(); -} - #endif // USE_OPENGL_RENDERER // ---------------------------------------------------------------------------- @@ -1009,14 +917,7 @@ static JNINativeMethod gMethods[] = { { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable }, #ifdef USE_OPENGL_RENDERER - { "nFlushCaches", "(I)V", (void*) android_view_GLES20Canvas_flushCaches }, - { "nInitCaches", "()Z", (void*) android_view_GLES20Canvas_initCaches }, - { "nTerminateCaches", "()V", (void*) android_view_GLES20Canvas_terminateCaches }, - { "nInitAtlas", "(Landroid/view/GraphicBuffer;[JI)V", - (void*) android_view_GLES20Canvas_initAtlas }, - - { "nCreateRenderer", "()J", (void*) android_view_GLES20Canvas_createRenderer }, { "nDestroyRenderer", "(J)V", (void*) android_view_GLES20Canvas_destroyRenderer }, { "nSetViewport", "(JII)V", (void*) android_view_GLES20Canvas_setViewport }, { "nPrepare", "(JZ)I", (void*) android_view_GLES20Canvas_prepare }, @@ -1025,9 +926,6 @@ static JNINativeMethod gMethods[] = { { "nSetProperty", "(Ljava/lang/String;Ljava/lang/String;)V", (void*) android_view_GLES20Canvas_setProperty }, - - { "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize }, - { "nCallDrawGLFunction", "(JJ)I", (void*) android_view_GLES20Canvas_callDrawGLFunction }, { "nSave", "(JI)I", (void*) android_view_GLES20Canvas_save }, @@ -1067,7 +965,6 @@ static JNINativeMethod gMethods[] = { { "nDrawColor", "(JII)V", (void*) android_view_GLES20Canvas_drawColor }, { "nDrawRect", "(JFFFFJ)V", (void*) android_view_GLES20Canvas_drawRect }, { "nDrawRects", "(JJJ)V", (void*) android_view_GLES20Canvas_drawRegionAsRects }, - { "nDrawRects", "(J[FIJ)V", (void*) android_view_GLES20Canvas_drawRects }, { "nDrawRoundRect", "(JFFFFFFJ)V", (void*) android_view_GLES20Canvas_drawRoundRect }, { "nDrawCircle", "(JFFFJ)V", (void*) android_view_GLES20Canvas_drawCircle }, { "nDrawCircle", "(JJJJJ)V", (void*) android_view_GLES20Canvas_drawCircleProps }, @@ -1085,33 +982,22 @@ static JNINativeMethod gMethods[] = { { "nDrawText", "(JLjava/lang/String;IIFFIJJ)V", (void*) android_view_GLES20Canvas_drawText }, - { "nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, - { "nDrawTextOnPath", "(JLjava/lang/String;IIJFFIJ)V", + { "nDrawTextOnPath", "(J[CIIJFFIJJ)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, + { "nDrawTextOnPath", "(JLjava/lang/String;IIJFFIJJ)V", (void*) android_view_GLES20Canvas_drawTextOnPath }, - { "nDrawTextRun", "(J[CIIIIFFIJJ)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, - { "nDrawTextRun", "(JLjava/lang/String;IIIIFFIJJ)V", + { "nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, + { "nDrawTextRun", "(JLjava/lang/String;IIIIFFZJJ)V", (void*) android_view_GLES20Canvas_drawTextRun }, - { "nDrawPosText", "(J[CII[FJ)V", (void*) android_view_GLES20Canvas_drawPosTextArray }, - { "nDrawPosText", "(JLjava/lang/String;II[FJ)V", - (void*) android_view_GLES20Canvas_drawPosText }, - - { "nGetClipBounds", "(JLandroid/graphics/Rect;)Z", - (void*) android_view_GLES20Canvas_getClipBounds }, + { "nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_getClipBounds }, - { "nFinishRecording", "(J)J", (void*) android_view_GLES20Canvas_finishRecording }, - { "nDrawDisplayList", "(JJLandroid/graphics/Rect;I)I", - (void*) android_view_GLES20Canvas_drawDisplayList }, + { "nFinishRecording", "(J)J", (void*) android_view_GLES20Canvas_finishRecording }, + { "nDrawRenderNode", "(JJLandroid/graphics/Rect;I)I", (void*) android_view_GLES20Canvas_drawRenderNode }, { "nCreateDisplayListRenderer", "()J", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, { "nDrawLayer", "(JJFF)V", (void*) android_view_GLES20Canvas_drawLayer }, - { "nCopyLayer", "(JJ)Z", (void*) android_view_GLES20Canvas_copyLayer }, - { "nClearLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_clearLayerUpdates }, - { "nFlushLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_flushLayerUpdates }, - { "nPushLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_pushLayerUpdate }, - { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_cancelLayerUpdate }, { "nGetMaximumTextureWidth", "()I", (void*) android_view_GLES20Canvas_getMaxTextureWidth }, { "nGetMaximumTextureHeight", "()I", (void*) android_view_GLES20Canvas_getMaxTextureHeight }, diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp deleted file mode 100644 index d0269a3..0000000 --- a/core/jni/android_view_GLRenderer.cpp +++ /dev/null @@ -1,203 +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. - */ - -#define LOG_TAG "GLRenderer" - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <android_runtime/AndroidRuntime.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <EGL/egl_cache.h> - -#include <utils/Timers.h> - -#include <private/hwui/DrawGlInfo.h> - -#include <Caches.h> -#include <Extensions.h> -#include <LayerRenderer.h> -#include <RenderNode.h> - -#ifdef USE_OPENGL_RENDERER - EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); -#endif - -namespace android { - -/** - * Note: OpenGLRenderer JNI layer is generated and compiled only on supported - * devices. This means all the logic must be compiled only when the - * preprocessor variable USE_OPENGL_RENDERER is defined. - */ -#ifdef USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Defines -// ---------------------------------------------------------------------------- - -// Debug -#define DEBUG_RENDERER 0 - -// Debug -#if DEBUG_RENDERER - #define RENDERER_LOGD(...) ALOGD(__VA_ARGS__) -#else - #define RENDERER_LOGD(...) -#endif - -// ---------------------------------------------------------------------------- -// Surface and display management -// ---------------------------------------------------------------------------- - -static jboolean android_view_GLRenderer_preserveBackBuffer(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - eglGetError(); - eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not enable buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS; -} - -static jboolean android_view_GLRenderer_isBackBufferPreserved(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - EGLint value; - - eglGetError(); - eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &value); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not query buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED; -} - -// ---------------------------------------------------------------------------- -// Tracing and debugging -// ---------------------------------------------------------------------------- - -static bool android_view_GLRenderer_loadProperties(JNIEnv* env, jobject clazz) { - if (uirenderer::Caches::hasInstance()) { - return uirenderer::Caches::getInstance().initProperties(); - } - return false; -} - -static void android_view_GLRenderer_beginFrame(JNIEnv* env, jobject clazz, - jintArray size) { - - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - if (size) { - EGLint value; - jint* storage = env->GetIntArrayElements(size, NULL); - - eglQuerySurface(display, surface, EGL_WIDTH, &value); - storage[0] = value; - - eglQuerySurface(display, surface, EGL_HEIGHT, &value); - storage[1] = value; - - env->ReleaseIntArrayElements(size, storage, 0); - } - - eglBeginFrame(display, surface); -} - -static jlong android_view_GLRenderer_getSystemTime(JNIEnv* env, jobject clazz) { - if (uirenderer::Extensions::getInstance().hasNvSystemTime()) { - return eglGetSystemTimeNV(); - } - return systemTime(SYSTEM_TIME_MONOTONIC); -} - -static void android_view_GLRenderer_destroyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr) { - using namespace android::uirenderer; - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - LayerRenderer::destroyLayer(layer); -} - -static void android_view_GLRenderer_prepareTree(JNIEnv* env, jobject clazz, - jlong renderNodePtr) { - using namespace android::uirenderer; - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - TreeInfo ignoredInfo; - renderNode->prepareTree(ignoredInfo); -} - -static void android_view_GLRenderer_invokeFunctor(JNIEnv* env, jobject clazz, - jlong functorPtr, jboolean hasContext) { - using namespace android::uirenderer; - Functor* functor = reinterpret_cast<Functor*>(functorPtr); - DrawGlInfo::Mode mode = hasContext ? DrawGlInfo::kModeProcess : DrawGlInfo::kModeProcessNoContext; - (*functor)(mode, NULL); -} - -#endif // USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Shaders -// ---------------------------------------------------------------------------- - -static void android_view_GLRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, - jstring diskCachePath) { - - const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); - egl_cache_t::get()->setCacheFilename(cacheArray); - env->ReleaseStringUTFChars(diskCachePath, cacheArray); -} - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -const char* const kClassPathName = "android/view/GLRenderer"; - -static JNINativeMethod gMethods[] = { -#ifdef USE_OPENGL_RENDERER - { "isBackBufferPreserved", "()Z", (void*) android_view_GLRenderer_isBackBufferPreserved }, - { "preserveBackBuffer", "()Z", (void*) android_view_GLRenderer_preserveBackBuffer }, - { "loadProperties", "()Z", (void*) android_view_GLRenderer_loadProperties }, - - { "beginFrame", "([I)V", (void*) android_view_GLRenderer_beginFrame }, - - { "getSystemTime", "()J", (void*) android_view_GLRenderer_getSystemTime }, - { "nDestroyLayer", "(J)V", (void*) android_view_GLRenderer_destroyLayer }, - { "nPrepareTree", "(J)V", (void*) android_view_GLRenderer_prepareTree }, - { "nInvokeFunctor", "(JZ)V", (void*) android_view_GLRenderer_invokeFunctor }, -#endif - - { "setupShadersDiskCache", "(Ljava/lang/String;)V", - (void*) android_view_GLRenderer_setupShadersDiskCache }, -}; - -int register_android_view_GLRenderer(JNIEnv* env) { - return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -}; diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index 8a426ac..5ebed9c 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -21,6 +21,7 @@ #include "android_os_Parcel.h" #include "android_view_GraphicBuffer.h" +#include "android/graphics/GraphicsJNI.h" #include <android_runtime/AndroidRuntime.h> @@ -75,7 +76,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; #define GET_INT(object, field) \ @@ -141,16 +142,16 @@ static void android_view_GraphiceBuffer_destroy(JNIEnv* env, jobject clazz, // Canvas management // ---------------------------------------------------------------------------- -static inline SkBitmap::Config convertPixelFormat(int32_t format) { +static inline SkColorType convertPixelFormat(int32_t format) { switch (format) { case PIXEL_FORMAT_RGBA_8888: - return SkBitmap::kARGB_8888_Config; + return kN32_SkColorType; case PIXEL_FORMAT_RGBX_8888: - return SkBitmap::kARGB_8888_Config; + return kN32_SkColorType; case PIXEL_FORMAT_RGB_565: - return SkBitmap::kRGB_565_Config; + return kRGB_565_SkColorType; default: - return SkBitmap::kNo_Config; + return kUnknown_SkColorType; } } @@ -187,8 +188,10 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); SkBitmap bitmap; - bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()), - buffer->getWidth(), buffer->getHeight(), bytesCount); + bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(), + convertPixelFormat(buffer->getPixelFormat()), + kPremul_SkAlphaType), + bytesCount); if (buffer->getWidth() > 0 && buffer->getHeight() > 0) { bitmap.setPixels(bits); @@ -197,12 +200,11 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat()); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -218,8 +220,7 @@ static jboolean android_view_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobj GraphicBufferWrapper* wrapper = reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle); - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (wrapper) { status_t status = wrapper->buffer->unlock(); @@ -319,7 +320,7 @@ int register_android_view_GraphicBuffer(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp index 33a2705..ace17ec 100644 --- a/core/jni/android_view_HardwareLayer.cpp +++ b/core/jni/android_view_HardwareLayer.cpp @@ -43,21 +43,6 @@ using namespace uirenderer; #ifdef USE_OPENGL_RENDERER -static jlong android_view_HardwareLayer_createTextureLayer(JNIEnv* env, jobject clazz) { - Layer* layer = LayerRenderer::createTextureLayer(); - if (!layer) return 0; - - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) ); -} - -static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject clazz, - jint width, jint height) { - Layer* layer = LayerRenderer::createRenderLayer(width, height); - if (!layer) return 0; - - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) ); -} - static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); @@ -102,21 +87,6 @@ static void android_view_HardwareLayer_updateSurfaceTexture(JNIEnv* env, jobject layer->updateTexImage(); } -static void android_view_HardwareLayer_updateRenderLayer(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr, jlong displayListPtr, - jint left, jint top, jint right, jint bottom) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr); - layer->setDisplayList(displayList, left, top, right, bottom); -} - -static jboolean android_view_HardwareLayer_flushChanges(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - TreeInfo ignoredInfo; - return layer->apply(ignoredInfo); -} - static jlong android_view_HardwareLayer_getLayer(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); @@ -140,8 +110,6 @@ const char* const kClassPathName = "android/view/HardwareLayer"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nCreateTextureLayer", "()J", (void*) android_view_HardwareLayer_createTextureLayer }, - { "nCreateRenderLayer", "(II)J", (void*) android_view_HardwareLayer_createRenderLayer }, { "nOnTextureDestroyed", "(J)V", (void*) android_view_HardwareLayer_onTextureDestroyed }, { "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare }, @@ -150,9 +118,6 @@ static JNINativeMethod gMethods[] = { { "nSetSurfaceTexture", "(JLandroid/graphics/SurfaceTexture;Z)V", (void*) android_view_HardwareLayer_setSurfaceTexture }, { "nUpdateSurfaceTexture", "(J)V", (void*) android_view_HardwareLayer_updateSurfaceTexture }, - { "nUpdateRenderLayer", "(JJIIII)V", (void*) android_view_HardwareLayer_updateRenderLayer }, - - { "nFlushChanges", "(J)Z", (void*) android_view_HardwareLayer_flushChanges }, { "nGetLayer", "(J)J", (void*) android_view_HardwareLayer_getLayer }, { "nGetTexName", "(J)I", (void*) android_view_HardwareLayer_getTexName }, diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 26022e0..3ffde2d 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -38,6 +38,11 @@ using namespace uirenderer; */ #ifdef USE_OPENGL_RENDERER +#define SET_AND_DIRTY(prop, val, dirtyFlag) \ + (reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val) \ + ? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \ + : false) + // ---------------------------------------------------------------------------- // DisplayList view properties // ---------------------------------------------------------------------------- @@ -82,235 +87,199 @@ static void android_view_RenderNode_setDisplayListData(JNIEnv* env, // RenderProperties - setters // ---------------------------------------------------------------------------- -static void android_view_RenderNode_setCaching(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean caching) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setCaching(caching); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); +static jboolean android_view_RenderNode_setLayerType(JNIEnv* env, + jobject clazz, jlong renderNodePtr, jint jlayerType) { + LayerType layerType = static_cast<LayerType>(jlayerType); + return SET_AND_DIRTY(mutateLayerProperties().setType, layerType, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setLayerPaint(JNIEnv* env, + jobject clazz, jlong renderNodePtr, jlong paintPtr) { + SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); + return SET_AND_DIRTY(mutateLayerProperties().setFromPaint, paint, RenderNode::GENERIC); } -static void android_view_RenderNode_setStaticMatrix(JNIEnv* env, +static jboolean android_view_RenderNode_setStaticMatrix(JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong matrixPtr) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderNode->mutateStagingProperties().setStaticMatrix(matrix); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setStaticMatrix, matrix, RenderNode::GENERIC); } -static void android_view_RenderNode_setAnimationMatrix(JNIEnv* env, +static jboolean android_view_RenderNode_setAnimationMatrix(JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong matrixPtr) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderNode->mutateStagingProperties().setAnimationMatrix(matrix); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setAnimationMatrix, matrix, RenderNode::GENERIC); } -static void android_view_RenderNode_setClipToBounds(JNIEnv* env, +static jboolean android_view_RenderNode_setClipToBounds(JNIEnv* env, jobject clazz, jlong renderNodePtr, jboolean clipToBounds) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setClipToBounds(clipToBounds); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setClipToBounds, clipToBounds, RenderNode::GENERIC); } -static void android_view_RenderNode_setProjectBackwards(JNIEnv* env, +static jboolean android_view_RenderNode_setProjectBackwards(JNIEnv* env, jobject clazz, jlong renderNodePtr, jboolean shouldProject) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setProjectBackwards(shouldProject); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setProjectBackwards, shouldProject, RenderNode::GENERIC); } -static void android_view_RenderNode_setProjectionReceiver(JNIEnv* env, +static jboolean android_view_RenderNode_setProjectionReceiver(JNIEnv* env, jobject clazz, jlong renderNodePtr, jboolean shouldRecieve) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setProjectionReceiver(shouldRecieve); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setProjectionReceiver, shouldRecieve, RenderNode::GENERIC); } -static void android_view_RenderNode_setOutlineRoundRect(JNIEnv* env, +static jboolean android_view_RenderNode_setOutlineRoundRect(JNIEnv* env, jobject clazz, jlong renderNodePtr, jint left, jint top, jint right, jint bottom, jfloat radius) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setRoundRect(left, top, right, bottom, radius); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; } -static void android_view_RenderNode_setOutlineConvexPath(JNIEnv* env, +static jboolean android_view_RenderNode_setOutlineConvexPath(JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong outlinePathPtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); SkPath* outlinePath = reinterpret_cast<SkPath*>(outlinePathPtr); renderNode->mutateStagingProperties().mutableOutline().setConvexPath(outlinePath); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; } -static void android_view_RenderNode_setOutlineEmpty(JNIEnv* env, +static jboolean android_view_RenderNode_setOutlineEmpty(JNIEnv* env, jobject clazz, jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setEmpty(); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; } -static void android_view_RenderNode_setClipToOutline(JNIEnv* env, +static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env, jobject clazz, jlong renderNodePtr, jboolean clipToOutline) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setShouldClip(clipToOutline); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; } -static void android_view_RenderNode_setRevealClip(JNIEnv* env, +static jboolean android_view_RenderNode_setRevealClip(JNIEnv* env, jobject clazz, jlong renderNodePtr, jboolean shouldClip, jboolean inverseClip, jfloat x, jfloat y, jfloat radius) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableRevealClip().set( shouldClip, inverseClip, x, y, radius); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; } -static void android_view_RenderNode_setAlpha(JNIEnv* env, +static jboolean android_view_RenderNode_setAlpha(JNIEnv* env, jobject clazz, jlong renderNodePtr, float alpha) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setAlpha(alpha); - renderNode->setPropertyFieldsDirty(RenderNode::ALPHA); + return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA); } -static void android_view_RenderNode_setHasOverlappingRendering(JNIEnv* env, +static jboolean android_view_RenderNode_setHasOverlappingRendering(JNIEnv* env, jobject clazz, jlong renderNodePtr, bool hasOverlappingRendering) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setHasOverlappingRendering(hasOverlappingRendering); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, + RenderNode::GENERIC); } -static void android_view_RenderNode_setElevation(JNIEnv* env, +static jboolean android_view_RenderNode_setElevation(JNIEnv* env, jobject clazz, jlong renderNodePtr, float elevation) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setElevation(elevation); - renderNode->setPropertyFieldsDirty(RenderNode::Z); + return SET_AND_DIRTY(setElevation, elevation, RenderNode::Z); } -static void android_view_RenderNode_setTranslationX(JNIEnv* env, +static jboolean android_view_RenderNode_setTranslationX(JNIEnv* env, jobject clazz, jlong renderNodePtr, float tx) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setTranslationX(tx); - renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_X | RenderNode::X); + return SET_AND_DIRTY(setTranslationX, tx, RenderNode::TRANSLATION_X | RenderNode::X); } -static void android_view_RenderNode_setTranslationY(JNIEnv* env, +static jboolean android_view_RenderNode_setTranslationY(JNIEnv* env, jobject clazz, jlong renderNodePtr, float ty) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setTranslationY(ty); - renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_Y | RenderNode::Y); + return SET_AND_DIRTY(setTranslationY, ty, RenderNode::TRANSLATION_Y | RenderNode::Y); } -static void android_view_RenderNode_setTranslationZ(JNIEnv* env, +static jboolean android_view_RenderNode_setTranslationZ(JNIEnv* env, jobject clazz, jlong renderNodePtr, float tz) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setTranslationZ(tz); - renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z | RenderNode::Z); + return SET_AND_DIRTY(setTranslationZ, tz, RenderNode::TRANSLATION_Z | RenderNode::Z); } -static void android_view_RenderNode_setRotation(JNIEnv* env, +static jboolean android_view_RenderNode_setRotation(JNIEnv* env, jobject clazz, jlong renderNodePtr, float rotation) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setRotation(rotation); - renderNode->setPropertyFieldsDirty(RenderNode::ROTATION); + return SET_AND_DIRTY(setRotation, rotation, RenderNode::ROTATION); } -static void android_view_RenderNode_setRotationX(JNIEnv* env, +static jboolean android_view_RenderNode_setRotationX(JNIEnv* env, jobject clazz, jlong renderNodePtr, float rx) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setRotationX(rx); - renderNode->setPropertyFieldsDirty(RenderNode::ROTATION_X); + return SET_AND_DIRTY(setRotationX, rx, RenderNode::ROTATION_X); } -static void android_view_RenderNode_setRotationY(JNIEnv* env, +static jboolean android_view_RenderNode_setRotationY(JNIEnv* env, jobject clazz, jlong renderNodePtr, float ry) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setRotationY(ry); - renderNode->setPropertyFieldsDirty(RenderNode::ROTATION_Y); + return SET_AND_DIRTY(setRotationY, ry, RenderNode::ROTATION_Y); } -static void android_view_RenderNode_setScaleX(JNIEnv* env, +static jboolean android_view_RenderNode_setScaleX(JNIEnv* env, jobject clazz, jlong renderNodePtr, float sx) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setScaleX(sx); - renderNode->setPropertyFieldsDirty(RenderNode::SCALE_X); + return SET_AND_DIRTY(setScaleX, sx, RenderNode::SCALE_X); } -static void android_view_RenderNode_setScaleY(JNIEnv* env, +static jboolean android_view_RenderNode_setScaleY(JNIEnv* env, jobject clazz, jlong renderNodePtr, float sy) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setScaleY(sy); - renderNode->setPropertyFieldsDirty(RenderNode::SCALE_Y); + return SET_AND_DIRTY(setScaleY, sy, RenderNode::SCALE_Y); } -static void android_view_RenderNode_setPivotX(JNIEnv* env, +static jboolean android_view_RenderNode_setPivotX(JNIEnv* env, jobject clazz, jlong renderNodePtr, float px) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setPivotX(px); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setPivotX, px, RenderNode::GENERIC); } -static void android_view_RenderNode_setPivotY(JNIEnv* env, +static jboolean android_view_RenderNode_setPivotY(JNIEnv* env, jobject clazz, jlong renderNodePtr, float py) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setPivotY(py); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setPivotY, py, RenderNode::GENERIC); } -static void android_view_RenderNode_setCameraDistance(JNIEnv* env, +static jboolean android_view_RenderNode_setCameraDistance(JNIEnv* env, jobject clazz, jlong renderNodePtr, float distance) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setCameraDistance(distance); - renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return SET_AND_DIRTY(setCameraDistance, distance, RenderNode::GENERIC); } -static void android_view_RenderNode_setLeft(JNIEnv* env, +static jboolean android_view_RenderNode_setLeft(JNIEnv* env, jobject clazz, jlong renderNodePtr, int left) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setLeft(left); - renderNode->setPropertyFieldsDirty(RenderNode::X); + return SET_AND_DIRTY(setLeft, left, RenderNode::X); } -static void android_view_RenderNode_setTop(JNIEnv* env, +static jboolean android_view_RenderNode_setTop(JNIEnv* env, jobject clazz, jlong renderNodePtr, int top) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setTop(top); - renderNode->setPropertyFieldsDirty(RenderNode::Y); + return SET_AND_DIRTY(setTop, top, RenderNode::Y); } -static void android_view_RenderNode_setRight(JNIEnv* env, +static jboolean android_view_RenderNode_setRight(JNIEnv* env, jobject clazz, jlong renderNodePtr, int right) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setRight(right); - renderNode->setPropertyFieldsDirty(RenderNode::X); + return SET_AND_DIRTY(setRight, right, RenderNode::X); } -static void android_view_RenderNode_setBottom(JNIEnv* env, +static jboolean android_view_RenderNode_setBottom(JNIEnv* env, jobject clazz, jlong renderNodePtr, int bottom) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setBottom(bottom); - renderNode->setPropertyFieldsDirty(RenderNode::Y); + return SET_AND_DIRTY(setBottom, bottom, RenderNode::Y); } -static void android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env, +static jboolean android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env, jobject clazz, jlong renderNodePtr, int left, int top, int right, int bottom) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom); - renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) { + renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + return true; + } + return false; } -static void android_view_RenderNode_offsetLeftAndRight(JNIEnv* env, +static jboolean android_view_RenderNode_offsetLeftAndRight(JNIEnv* env, jobject clazz, jlong renderNodePtr, float offset) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().offsetLeftRight(offset); - renderNode->setPropertyFieldsDirty(RenderNode::X); + return SET_AND_DIRTY(offsetLeftRight, offset, RenderNode::X); } -static void android_view_RenderNode_offsetTopAndBottom(JNIEnv* env, +static jboolean android_view_RenderNode_offsetTopAndBottom(JNIEnv* env, jobject clazz, jlong renderNodePtr, float offset) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - renderNode->mutateStagingProperties().offsetTopBottom(offset); - renderNode->setPropertyFieldsDirty(RenderNode::Y); + return SET_AND_DIRTY(offsetTopBottom, offset, RenderNode::Y); } // ---------------------------------------------------------------------------- @@ -513,41 +482,42 @@ static JNINativeMethod gMethods[] = { { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize }, - { "nSetCaching", "(JZ)V", (void*) android_view_RenderNode_setCaching }, - { "nSetStaticMatrix", "(JJ)V", (void*) android_view_RenderNode_setStaticMatrix }, - { "nSetAnimationMatrix", "(JJ)V", (void*) android_view_RenderNode_setAnimationMatrix }, - { "nSetClipToBounds", "(JZ)V", (void*) android_view_RenderNode_setClipToBounds }, - { "nSetProjectBackwards", "(JZ)V", (void*) android_view_RenderNode_setProjectBackwards }, - { "nSetProjectionReceiver","(JZ)V", (void*) android_view_RenderNode_setProjectionReceiver }, - - { "nSetOutlineRoundRect", "(JIIIIF)V", (void*) android_view_RenderNode_setOutlineRoundRect }, - { "nSetOutlineConvexPath", "(JJ)V", (void*) android_view_RenderNode_setOutlineConvexPath }, - { "nSetOutlineEmpty", "(J)V", (void*) android_view_RenderNode_setOutlineEmpty }, - { "nSetClipToOutline", "(JZ)V", (void*) android_view_RenderNode_setClipToOutline }, - { "nSetRevealClip", "(JZZFFF)V", (void*) android_view_RenderNode_setRevealClip }, - - { "nSetAlpha", "(JF)V", (void*) android_view_RenderNode_setAlpha }, - { "nSetHasOverlappingRendering", "(JZ)V", + { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, + { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, + { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix }, + { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix }, + { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds }, + { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards }, + { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver }, + + { "nSetOutlineRoundRect", "(JIIIIF)Z", (void*) android_view_RenderNode_setOutlineRoundRect }, + { "nSetOutlineConvexPath", "(JJ)Z", (void*) android_view_RenderNode_setOutlineConvexPath }, + { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty }, + { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline }, + { "nSetRevealClip", "(JZZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, + + { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, + { "nSetHasOverlappingRendering", "(JZ)Z", (void*) android_view_RenderNode_setHasOverlappingRendering }, - { "nSetElevation", "(JF)V", (void*) android_view_RenderNode_setElevation }, - { "nSetTranslationX", "(JF)V", (void*) android_view_RenderNode_setTranslationX }, - { "nSetTranslationY", "(JF)V", (void*) android_view_RenderNode_setTranslationY }, - { "nSetTranslationZ", "(JF)V", (void*) android_view_RenderNode_setTranslationZ }, - { "nSetRotation", "(JF)V", (void*) android_view_RenderNode_setRotation }, - { "nSetRotationX", "(JF)V", (void*) android_view_RenderNode_setRotationX }, - { "nSetRotationY", "(JF)V", (void*) android_view_RenderNode_setRotationY }, - { "nSetScaleX", "(JF)V", (void*) android_view_RenderNode_setScaleX }, - { "nSetScaleY", "(JF)V", (void*) android_view_RenderNode_setScaleY }, - { "nSetPivotX", "(JF)V", (void*) android_view_RenderNode_setPivotX }, - { "nSetPivotY", "(JF)V", (void*) android_view_RenderNode_setPivotY }, - { "nSetCameraDistance", "(JF)V", (void*) android_view_RenderNode_setCameraDistance }, - { "nSetLeft", "(JI)V", (void*) android_view_RenderNode_setLeft }, - { "nSetTop", "(JI)V", (void*) android_view_RenderNode_setTop }, - { "nSetRight", "(JI)V", (void*) android_view_RenderNode_setRight }, - { "nSetBottom", "(JI)V", (void*) android_view_RenderNode_setBottom }, - { "nSetLeftTopRightBottom","(JIIII)V", (void*) android_view_RenderNode_setLeftTopRightBottom }, - { "nOffsetLeftAndRight", "(JF)V", (void*) android_view_RenderNode_offsetLeftAndRight }, - { "nOffsetTopAndBottom", "(JF)V", (void*) android_view_RenderNode_offsetTopAndBottom }, + { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation }, + { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX }, + { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY }, + { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ }, + { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation }, + { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX }, + { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY }, + { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX }, + { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY }, + { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX }, + { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY }, + { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance }, + { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft }, + { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop }, + { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight }, + { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom }, + { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom }, + { "nOffsetLeftAndRight", "(JF)Z", (void*) android_view_RenderNode_offsetLeftAndRight }, + { "nOffsetTopAndBottom", "(JF)Z", (void*) android_view_RenderNode_offsetTopAndBottom }, { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering }, { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline }, diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 6c9d060..7018751 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -70,7 +70,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; // ---------------------------------------------------------------------------- @@ -95,6 +95,7 @@ sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj) { env->GetLongField(surfaceObj, gSurfaceClassInfo.mNativeObject)); env->MonitorExit(lock); } + env->DeleteLocalRef(lock); return sur; } @@ -172,17 +173,17 @@ static jboolean nativeIsConsumerRunningBehind(JNIEnv* env, jclass clazz, jlong n return value; } -static inline SkBitmap::Config convertPixelFormat(PixelFormat format) { +static inline SkColorType convertPixelFormat(PixelFormat format) { /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then - we can map to SkBitmap::kARGB_8888_Config, and optionally call + we can map to kN32_SkColorType, and optionally call bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap (as an accelerator) */ switch (format) { - case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config; - case PIXEL_FORMAT_RGBA_8888: return SkBitmap::kARGB_8888_Config; - case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config; - default: return SkBitmap::kNo_Config; + case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGBA_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; + default: return kUnknown_SkColorType; } } @@ -219,12 +220,16 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, // Associate a SkCanvas object to this surface env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format); - SkBitmap bitmap; - ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); - bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr); + SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height, + convertPixelFormat(outBuffer.format), + kPremul_SkAlphaType); if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) { - bitmap.setAlphaType(kOpaque_SkAlphaType); + info.fAlphaType = kOpaque_SkAlphaType; } + + SkBitmap bitmap; + ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); + bitmap.setInfo(info, bpr); if (outBuffer.width > 0 && outBuffer.height > 0) { bitmap.setPixels(outBuffer.bits); } else { @@ -232,10 +237,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, bitmap.setPixels(NULL); } - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, + reinterpret_cast<jlong>(&bitmap)); if (dirtyRectPtr) { + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); nativeCanvas->clipRect( SkRect::Make(reinterpret_cast<const SkIRect&>(dirtyRect)) ); } @@ -262,8 +268,7 @@ static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, } // detach the canvas from the surface - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, (jlong)0); // unlock surface status_t err = surface->unlockAndPost(); @@ -375,7 +380,7 @@ int register_android_view_Surface(JNIEnv* env) clazz = env->FindClass("android/graphics/Canvas"); gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I"); - gCanvasClassInfo.safeCanvasSwap = env->GetMethodID(clazz, "safeCanvasSwap", "(JZ)V"); + gCanvasClassInfo.setNativeBitmap = env->GetMethodID(clazz, "setNativeBitmap", "(J)V"); clazz = env->FindClass("android/graphics/Rect"); gRectClassInfo.left = env->GetFieldID(clazz, "left", "I"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5a935a9..9783e91 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -42,6 +42,8 @@ #include <ScopedUtfChars.h> +#include "SkTemplates.h" + // ---------------------------------------------------------------------------- namespace android { @@ -59,8 +61,17 @@ static struct { jfieldID xDpi; jfieldID yDpi; jfieldID secure; + jfieldID appVsyncOffsetNanos; + jfieldID presentationDeadlineNanos; } gPhysicalDisplayInfoClassInfo; +static struct { + jfieldID bottom; + jfieldID left; + jfieldID right; + jfieldID top; +} gRectClassInfo; + // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref. void DeleteScreenshot(void* addr, void* context) { SkASSERT(addr == ((ScreenshotClient*) context)->getPixels()); @@ -104,28 +115,34 @@ static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) { ctrl->decStrong((void *)nativeCreate); } -static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj, - jint width, jint height, jint minLayer, jint maxLayer, bool allLayers, - bool useIdentityTransform) { +static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, + jobject displayTokenObj, jobject sourceCropObj, jint width, jint height, + jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); if (displayToken == NULL) { return NULL; } - ScreenshotClient* screenshot = new ScreenshotClient(); + int left = env->GetIntField(sourceCropObj, gRectClassInfo.left); + int top = env->GetIntField(sourceCropObj, gRectClassInfo.top); + int right = env->GetIntField(sourceCropObj, gRectClassInfo.right); + int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom); + Rect sourceCrop(left, top, right, bottom); + + SkAutoTDelete<ScreenshotClient> screenshot(new ScreenshotClient()); status_t res; if (width > 0 && height > 0) { if (allLayers) { - res = screenshot->update(displayToken, width, height, useIdentityTransform); - } else { - res = screenshot->update(displayToken, width, height, minLayer, maxLayer, + res = screenshot->update(displayToken, sourceCrop, width, height, useIdentityTransform); + } else { + res = screenshot->update(displayToken, sourceCrop, width, height, + minLayer, maxLayer, useIdentityTransform); } } else { - res = screenshot->update(displayToken, useIdentityTransform); + res = screenshot->update(displayToken, sourceCrop, useIdentityTransform); } if (res != NO_ERROR) { - delete screenshot; return NULL; } @@ -150,7 +167,6 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display break; } default: { - delete screenshot; return NULL; } } @@ -159,12 +175,12 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); SkBitmap* bitmap = new SkBitmap(); - bitmap->setConfig(screenshotInfo, (size_t)rowBytes); + bitmap->setInfo(screenshotInfo, (size_t)rowBytes); if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) { // takes ownership of ScreenshotClient SkMallocPixelRef* pixels = SkMallocPixelRef::NewWithProc(screenshotInfo, (size_t) rowBytes, NULL, (void*) screenshot->getPixels(), &DeleteScreenshot, - (void*) screenshot); + (void*) (screenshot.detach())); pixels->setImmutable(); bitmap->setPixelRef(pixels)->unref(); bitmap->lockPixels(); @@ -174,20 +190,25 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); } -static void nativeScreenshot(JNIEnv* env, jclass clazz, - jobject displayTokenObj, jobject surfaceObj, - jint width, jint height, jint minLayer, jint maxLayer, bool allLayers, - bool useIdentityTransform) { +static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj, + jobject surfaceObj, jobject sourceCropObj, jint width, jint height, + jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); if (displayToken != NULL) { sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj); if (consumer != NULL) { + int left = env->GetIntField(sourceCropObj, gRectClassInfo.left); + int top = env->GetIntField(sourceCropObj, gRectClassInfo.top); + int right = env->GetIntField(sourceCropObj, gRectClassInfo.right); + int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom); + Rect sourceCrop(left, top, right, bottom); + if (allLayers) { minLayer = 0; maxLayer = -1; } - ScreenshotClient::capture( - displayToken, consumer->getIGraphicBufferProducer(), + ScreenshotClient::capture(displayToken, + consumer->getIGraphicBufferProducer(), sourceCrop, width, height, uint32_t(minLayer), uint32_t(maxLayer), useIdentityTransform); } @@ -373,6 +394,10 @@ static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz, env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.xDpi, info.xdpi); env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.yDpi, info.ydpi); env->SetBooleanField(infoObj, gPhysicalDisplayInfoClassInfo.secure, info.secure); + env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos, + info.appVsyncOffset); + env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos, + info.presentationDeadline); env->SetObjectArrayElement(configArray, static_cast<jsize>(c), infoObj); env->DeleteLocalRef(infoObj); } @@ -393,20 +418,12 @@ static jboolean nativeSetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenOb return err == NO_ERROR ? JNI_TRUE : JNI_FALSE; } -static void nativeBlankDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { - sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); - if (token == NULL) return; - - ALOGD_IF_SLOW(100, "Excessive delay in blankDisplay() while turning screen off"); - SurfaceComposerClient::blankDisplay(token); -} - -static void nativeUnblankDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { +static void nativeSetDisplayPowerMode(JNIEnv* env, jclass clazz, jobject tokenObj, jint mode) { sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); if (token == NULL) return; - ALOGD_IF_SLOW(100, "Excessive delay in unblankDisplay() while turning screen on"); - SurfaceComposerClient::unblankDisplay(token); + ALOGD_IF_SLOW(100, "Excessive delay in setPowerMode()"); + SurfaceComposerClient::setDisplayPowerMode(token, mode); } static jboolean nativeClearContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject) { @@ -563,9 +580,9 @@ static JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeRelease }, {"nativeDestroy", "(J)V", (void*)nativeDestroy }, - {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZZ)Landroid/graphics/Bitmap;", + {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZ)Landroid/graphics/Bitmap;", (void*)nativeScreenshotBitmap }, - {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZZ)V", + {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V", (void*)nativeScreenshot }, {"nativeOpenTransaction", "()V", (void*)nativeOpenTransaction }, @@ -609,10 +626,6 @@ static JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetActiveConfig }, {"nativeSetActiveConfig", "(Landroid/os/IBinder;I)Z", (void*)nativeSetActiveConfig }, - {"nativeBlankDisplay", "(Landroid/os/IBinder;)V", - (void*)nativeBlankDisplay }, - {"nativeUnblankDisplay", "(Landroid/os/IBinder;)V", - (void*)nativeUnblankDisplay }, {"nativeClearContentFrameStats", "(J)Z", (void*)nativeClearContentFrameStats }, {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z", @@ -621,6 +634,8 @@ static JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeClearAnimationFrameStats }, {"nativeGetAnimationFrameStats", "(Landroid/view/WindowAnimationFrameStats;)Z", (void*)nativeGetAnimationFrameStats }, + {"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V", + (void*)nativeSetDisplayPowerMode }, }; int register_android_view_SurfaceControl(JNIEnv* env) @@ -639,6 +654,16 @@ int register_android_view_SurfaceControl(JNIEnv* env) gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F"); gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F"); gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z"); + gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos = env->GetFieldID(clazz, + "appVsyncOffsetNanos", "J"); + gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos = env->GetFieldID(clazz, + "presentationDeadlineNanos", "J"); + + jclass rectClazz = env->FindClass("android/graphics/Rect"); + gRectClassInfo.bottom = env->GetFieldID(rectClazz, "bottom", "I"); + gRectClassInfo.left = env->GetFieldID(rectClazz, "left", "I"); + gRectClassInfo.right = env->GetFieldID(rectClazz, "right", "I"); + gRectClassInfo.top = env->GetFieldID(rectClazz, "top", "I"); jclass frameStatsClazz = env->FindClass("android/view/FrameStats"); jfieldID undefined_time_nano_field = env->GetStaticFieldID(frameStatsClazz, "UNDEFINED_TIME_NANO", "J"); diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 9258543..5c04a78 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -29,6 +29,8 @@ #include <SkCanvas.h> #include <SkImage.h> +#include "android/graphics/GraphicsJNI.h" + namespace android { // ---------------------------------------------------------------------------- @@ -45,7 +47,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; static struct { @@ -71,17 +73,28 @@ static struct { // Native layer // ---------------------------------------------------------------------------- -static inline SkBitmap::Config convertPixelFormat(int32_t format) { - switch (format) { +// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp) +static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) { + SkImageInfo info; + info.fWidth = buffer.width; + info.fHeight = buffer.height; + switch (buffer.format) { case WINDOW_FORMAT_RGBA_8888: - return SkBitmap::kARGB_8888_Config; + info.fColorType = kN32_SkColorType; + info.fAlphaType = kPremul_SkAlphaType; + break; case WINDOW_FORMAT_RGBX_8888: - return SkBitmap::kARGB_8888_Config; + info.fColorType = kN32_SkColorType; + info.fAlphaType = kOpaque_SkAlphaType; case WINDOW_FORMAT_RGB_565: - return SkBitmap::kRGB_565_Config; + info.fColorType = kRGB_565_SkColorType; + info.fAlphaType = kOpaque_SkAlphaType; default: - return SkBitmap::kNo_Config; + info.fColorType = kUnknown_SkColorType; + info.fAlphaType = kIgnore_SkAlphaType; + break; } + return info; } /** @@ -146,11 +159,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format); SkBitmap bitmap; - bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount); - - if (buffer.format == WINDOW_FORMAT_RGBX_8888) { - bitmap.setAlphaType(kOpaque_SkAlphaType); - } + bitmap.setInfo(convertPixelFormat(buffer), bytesCount); if (buffer.width > 0 && buffer.height > 0) { bitmap.setPixels(buffer.bits); @@ -159,12 +168,11 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer.format); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -178,8 +186,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject, jlong nativeWindow, jobject canvas) { - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (nativeWindow) { sp<ANativeWindow> window((ANativeWindow*) nativeWindow); @@ -228,7 +235,7 @@ int register_android_view_TextureView(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); FIND_CLASS(clazz, "android/view/TextureView"); GET_FIELD_ID(gTextureViewClassInfo.nativeWindow, clazz, "mNativeWindow", "J"); diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 1397131..3e62d0b 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -22,6 +22,10 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <EGL/egl_cache.h> + #include <utils/StrongPointer.h> #include <android_runtime/android_view_Surface.h> #include <system/window.h> @@ -45,6 +49,14 @@ using namespace android::uirenderer::renderthread; static jmethodID gRunnableMethod; +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + class JavaTask : public RenderTask { public: JavaTask(JNIEnv* env, jobject jrunnable) { @@ -53,44 +65,17 @@ public: } virtual void run() { - env()->CallVoidMethod(mRunnable, gRunnableMethod); - env()->DeleteGlobalRef(mRunnable); + JNIEnv* env = getenv(mVm); + env->CallVoidMethod(mRunnable, gRunnableMethod); + env->DeleteGlobalRef(mRunnable); delete this; }; private: - JNIEnv* env() { - JNIEnv* env; - if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - return 0; - } - return env; - } - JavaVM* mVm; jobject mRunnable; }; -class SetAtlasTask : public RenderTask { -public: - SetAtlasTask(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) - : mBuffer(buffer) - , mMap(map) - , mMapSize(size) { - } - - virtual void run() { - CanvasContext::setTextureAtlas(mBuffer, mMap, mMapSize); - mMap = 0; - delete this; - } - -private: - sp<GraphicBuffer> mBuffer; - int64_t* mMap; - size_t mMapSize; -}; - class OnFinishedEvent { public: OnFinishedEvent(BaseRenderNodeAnimator* animator, AnimationListener* listener) @@ -118,12 +103,34 @@ private: std::vector<OnFinishedEvent> mOnFinishedEvents; }; -class RootRenderNode : public RenderNode, public AnimationHook { +class RenderingException : public MessageHandler { +public: + RenderingException(JavaVM* vm, const std::string& message) + : mVm(vm) + , mMessage(message) { + } + + virtual void handleMessage(const Message&) { + throwException(mVm, mMessage); + } + + static void throwException(JavaVM* vm, const std::string& message) { + JNIEnv* env = getenv(vm); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } + +private: + JavaVM* mVm; + std::string mMessage; +}; + +class RootRenderNode : public RenderNode, AnimationHook, ErrorHandler { public: - RootRenderNode() : RenderNode() { + RootRenderNode(JNIEnv* env) : RenderNode() { mLooper = Looper::getForThread(); LOG_ALWAYS_FATAL_IF(!mLooper.get(), "Must create RootRenderNode on a thread with a looper!"); + env->GetJavaVM(&mVm); } virtual ~RootRenderNode() {} @@ -133,10 +140,16 @@ public: mOnFinishedEvents.push_back(event); } + virtual void onError(const std::string& message) { + mLooper->sendMessage(new RenderingException(mVm, message), 0); + } + virtual void prepareTree(TreeInfo& info) { info.animationHook = this; + info.errorHandler = this; RenderNode::prepareTree(info); info.animationHook = NULL; + info.errorHandler = NULL; // post all the finished stuff if (mOnFinishedEvents.size()) { @@ -146,13 +159,21 @@ public: } } +protected: + virtual void damageSelf(TreeInfo& info) { + // Intentionally a no-op. As RootRenderNode gets a new DisplayListData + // every frame this would result in every draw push being a full inval, + // which is wrong. Only RootRenderNode has this issue. + } + private: sp<Looper> mLooper; std::vector<OnFinishedEvent> mOnFinishedEvents; + JavaVM* mVm; }; static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, - jobject graphicBuffer, jlongArray atlasMapArray) { + jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) { sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); jsize len = env->GetArrayLength(atlasMapArray); if (len <= 0) { @@ -162,12 +183,12 @@ static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, int64_t* map = new int64_t[len]; env->GetLongArrayRegion(atlasMapArray, 0, len, map); - SetAtlasTask* task = new SetAtlasTask(buffer, map, len); - RenderThread::getInstance().queue(task); + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setTextureAtlas(buffer, map, len); } static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) { - RootRenderNode* node = new RootRenderNode(); + RootRenderNode* node = new RootRenderNode(env); node->incStrong(0); node->setName("RootRenderNode"); return reinterpret_cast<jlong>(node); @@ -238,11 +259,9 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, } static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, - jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density, - jint dirtyLeft, jint dirtyTop, jint dirtyRight, jint dirtyBottom) { + jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density, - dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); + return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density); } static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz, @@ -252,10 +271,9 @@ static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, j } static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz, - jlong proxyPtr, jlong functorPtr, jboolean waitForCompletion) { - RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + jlong functorPtr, jboolean waitForCompletion) { Functor* functor = reinterpret_cast<Functor*>(functorPtr); - proxy->invokeFunctor(functor, waitForCompletion); + RenderProxy::invokeFunctor(functor, waitForCompletion); } static void android_view_ThreadedRenderer_runWithGlContext(JNIEnv* env, jobject clazz, @@ -329,6 +347,18 @@ static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject c #endif // ---------------------------------------------------------------------------- +// Shaders +// ---------------------------------------------------------------------------- + +static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, + jstring diskCachePath) { + + const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); + egl_cache_t::get()->setCacheFilename(cacheArray); + env->ReleaseStringUTFChars(diskCachePath, cacheArray); +} + +// ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -336,7 +366,7 @@ const char* const kClassPathName = "android/view/ThreadedRenderer"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nSetAtlas", "(Landroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas }, + { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, @@ -347,9 +377,9 @@ static JNINativeMethod gMethods[] = { { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface }, { "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup }, { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, - { "nSyncAndDrawFrame", "(JJJFIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, + { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface }, - { "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, + { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext }, { "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer }, { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, @@ -361,6 +391,8 @@ static JNINativeMethod gMethods[] = { { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, #endif + { "setupShadersDiskCache", "(Ljava/lang/String;)V", + (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 381a5ee..226b764 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -46,6 +46,9 @@ #define LIB_SUFFIX ".so" #define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1) +#define RS_BITCODE_SUFFIX ".bc" +#define RS_BITCODE_SUFFIX_LEN (sizeof(RS_BITCODE_SUFFIX) -1) + #define GDBSERVER "gdbserver" #define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) @@ -487,6 +490,42 @@ com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, j return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); } +enum bitcode_scan_result_t { + APK_SCAN_ERROR = -1, + NO_BITCODE_PRESENT = 0, + BITCODE_PRESENT = 1, +}; + +static jint +com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz, + jlong apkHandle) { + ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); + void* cookie = NULL; + if (!zipFile->startIteration(&cookie)) { + return APK_SCAN_ERROR; + } + + char fileName[PATH_MAX]; + ZipEntryRO next = NULL; + while ((next = zipFile->nextEntry(cookie)) != NULL) { + if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) { + continue; + } + + const size_t fileNameLen = strlen(fileName); + const char* lastSlash = strrchr(fileName, '/'); + const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1; + if (!strncmp(fileName + fileNameLen - RS_BITCODE_SUFFIX_LEN, RS_BITCODE_SUFFIX, + RS_BITCODE_SUFFIX_LEN) && isFilenameSafe(baseName)) { + zipFile->endIteration(cookie); + return BITCODE_PRESENT; + } + } + + zipFile->endIteration(cookie); + return NO_BITCODE_PRESENT; +} + static jlong com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath) { @@ -518,6 +557,8 @@ static JNINativeMethod gMethods[] = { {"nativeFindSupportedAbi", "(J[Ljava/lang/String;)I", (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, + {"hasRenderscriptBitcode", "(J)I", + (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, }; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index c58bf04..0cdddba 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -14,46 +14,40 @@ * limitations under the License. */ -#include "android_runtime/AndroidRuntime.h" +#define LOG_TAG "Zygote" // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #include <linux/fs.h> #include <grp.h> +#include <fcntl.h> #include <paths.h> #include <signal.h> #include <stdlib.h> +#include <unistd.h> +#include <sys/capability.h> +#include <sys/personality.h> +#include <sys/prctl.h> #include <sys/resource.h> -#include <sys/types.h> #include <sys/stat.h> +#include <sys/types.h> +#include <sys/utsname.h> #include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> -#include "cutils/fs.h" -#include "cutils/multiuser.h" -#include "cutils/sched_policy.h" -#include "utils/String8.h" + +#include <cutils/fs.h> +#include <cutils/multiuser.h> +#include <cutils/sched_policy.h> +#include <utils/String8.h> +#include <selinux/android.h> + +#include "android_runtime/AndroidRuntime.h" #include "JNIHelp.h" #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" -#if defined(HAVE_PRCTL) -#include <sys/prctl.h> -#endif - -#include <selinux/android.h> - -#if defined(__linux__) -#include <sys/personality.h> -#include <sys/utsname.h> -#if defined(HAVE_ANDROID_OS) -#include <sys/capability.h> -#endif -#endif - namespace { using android::String8; @@ -97,11 +91,9 @@ static void SigChldHandler(int /*signal_number*/) { if (WTERMSIG(status) != SIGKILL) { ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status)); } -#ifdef WCOREDUMP if (WCOREDUMP(status)) { ALOGI("Process %d dumped core.", pid); } -#endif /* ifdef WCOREDUMP */ } // If the just-crashed process is the system_server, bring down zygote @@ -199,8 +191,6 @@ static void SetRLimits(JNIEnv* env, jobjectArray javaRlimits) { } } -#if defined(HAVE_ANDROID_OS) - // The debug malloc library needs to know whether it's the zygote or a child. extern "C" int gMallocLeakZygoteChild; @@ -254,17 +244,6 @@ static void SetSchedulerPolicy(JNIEnv* env) { } } -#else - -static int gMallocLeakZygoteChild = 0; - -static void EnableKeepCapabilities(JNIEnv*) {} -static void DropCapabilitiesBoundingSet(JNIEnv*) {} -static void SetCapabilities(JNIEnv*, int64_t, int64_t) {} -static void SetSchedulerPolicy(JNIEnv*) {} - -#endif - // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. static bool MountEmulatedStorage(uid_t uid, jint mount_mode) { @@ -337,7 +316,6 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode) { return true; } -#if defined(__linux__) static bool NeedsNoRandomizeWorkaround() { #if !defined(__arm__) return false; @@ -357,7 +335,6 @@ static bool NeedsNoRandomizeWorkaround() { return (major < 3) || ((major == 3) && (minor < 4)); #endif } -#endif // Utility to close down the Zygote socket file descriptors while // the child is still running as root with Zygote's privileges. Each @@ -474,7 +451,6 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra RuntimeAbort(env); } -#if defined(__linux__) if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). int old_personality = personality(0xffffffff); @@ -483,58 +459,49 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra ALOGW("personality(%d) failed", new_personality); } } -#endif SetCapabilities(env, permittedCapabilities, effectiveCapabilities); SetSchedulerPolicy(env); -#if defined(HAVE_ANDROID_OS) - { // NOLINT(whitespace/braces) - const char* se_info_c_str = NULL; - ScopedUtfChars* se_info = NULL; - if (java_se_info != NULL) { - se_info = new ScopedUtfChars(env, java_se_info); - se_info_c_str = se_info->c_str(); - if (se_info_c_str == NULL) { - ALOGE("se_info_c_str == NULL"); - RuntimeAbort(env); - } - } - const char* se_name_c_str = NULL; - ScopedUtfChars* se_name = NULL; - if (java_se_name != NULL) { - se_name = new ScopedUtfChars(env, java_se_name); - se_name_c_str = se_name->c_str(); - if (se_name_c_str == NULL) { - ALOGE("se_name_c_str == NULL"); - RuntimeAbort(env); - } - } - rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str); - if (rc == -1) { - ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, - is_system_server, se_info_c_str, se_name_c_str); - RuntimeAbort(env); - } - - // Make it easier to debug audit logs by setting the main thread's name to the - // nice name rather than "app_process". - if (se_info_c_str == NULL && is_system_server) { - se_name_c_str = "system_server"; - } - if (se_info_c_str != NULL) { - SetThreadName(se_name_c_str); - } + const char* se_info_c_str = NULL; + ScopedUtfChars* se_info = NULL; + if (java_se_info != NULL) { + se_info = new ScopedUtfChars(env, java_se_info); + se_info_c_str = se_info->c_str(); + if (se_info_c_str == NULL) { + ALOGE("se_info_c_str == NULL"); + RuntimeAbort(env); + } + } + const char* se_name_c_str = NULL; + ScopedUtfChars* se_name = NULL; + if (java_se_name != NULL) { + se_name = new ScopedUtfChars(env, java_se_name); + se_name_c_str = se_name->c_str(); + if (se_name_c_str == NULL) { + ALOGE("se_name_c_str == NULL"); + RuntimeAbort(env); + } + } + rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str); + if (rc == -1) { + ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, + is_system_server, se_info_c_str, se_name_c_str); + RuntimeAbort(env); + } - delete se_info; - delete se_name; + // Make it easier to debug audit logs by setting the main thread's name to the + // nice name rather than "app_process". + if (se_info_c_str == NULL && is_system_server) { + se_name_c_str = "system_server"; } -#else - UNUSED(is_system_server); - UNUSED(java_se_info); - UNUSED(java_se_name); -#endif + if (se_info_c_str != NULL) { + SetThreadName(se_name_c_str); + } + + delete se_info; + delete se_name; UnsetSigChldHandler(); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c5fd83d..fa1a563 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -123,10 +123,30 @@ <protected-broadcast android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" /> <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.AG_EVENT" /> + <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.RESULT" /> + <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" /> + <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" /> + <protected-broadcast + android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" /> @@ -260,6 +280,12 @@ <protected-broadcast android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast + android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> @@ -631,6 +657,13 @@ android:label="@string/permlab_addVoicemail" android:description="@string/permdesc_addVoicemail" /> + <!-- Allows an application to read all the voicemails in the system. --> + <permission android:name="com.android.voicemail.permission.READ_ALL_VOICEMAIL" + android:permissionGroup="android.permission-group.VOICEMAIL" + android:protectionLevel="dangerous" + android:label="@string/permlab_readAllVoicemail" + android:description="@string/permdesc_readAllVoicemail" /> + <!-- =============================================== --> <!-- Permissions for enabling accessibility features --> <!-- =============================================== --> @@ -1007,6 +1040,14 @@ android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="signature" /> + <!-- Allows an application to communicate with a SIM card using logical + channels. --> + <permission android:name="android.permission.SIM_COMMUNICATION" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:label="@string/permlab_sim_communication" + android:description="@string/permdesc_sim_communication" + android:protectionLevel="dangerous" /> + <!-- @SystemApi Allows TvInputService to access underlying TV input hardware such as built-in tuners and HDMI-in's. @hide This should only be used by OEM's TvInputService's. @@ -1746,10 +1787,8 @@ android:label="@string/permlab_manageCaCertificates" android:description="@string/permdesc_manageCaCertificates" /> - <!-- @SystemApi Allows an application to do certain operations - needed for interacting with the recovery (system update) - system. - @hide --> + <!-- @SystemApi Allows an application to do certain operations needed for + interacting with the recovery (system update) system. --> <permission android:name="android.permission.RECOVERY" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="signature|system" @@ -2059,6 +2098,14 @@ android:description="@string/permdesc_bindVoiceInteraction" android:protectionLevel="signature" /> + <!-- Must be required by hotword enrollment application, + to ensure that only the system can interact with it. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" + android:label="@string/permlab_manageVoiceKeyphrases" + android:description="@string/permdesc_manageVoiceKeyphrases" + android:protectionLevel="signature|system" /> + <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider}, to ensure that only the system can bind to it. @hide --> @@ -2623,10 +2670,16 @@ android:label="@string/permlab_provide_trust_agent" android:description="@string/permdesc_provide_trust_agent" /> + <!-- Allows an application to launch the trust agent settings activity. + @hide --> + <permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS" + android:protectionLevel="signatureOrSystem" + android:label="@string/permlab_launch_trust_agent_settings" + android:description="@string/permdesc_launch_trust_agent_settings" /> + <!-- Must be required by an {@link android.service.trust.TrustAgentService}, - to ensure that only the system can bind to it. - @hide --> + to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_TRUST_AGENT" android:protectionLevel="signature" android:label="@string/permlab_bind_trust_agent_service" @@ -2689,7 +2742,8 @@ android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" android:multiprocess="true" - android:documentLaunchMode="never"> + android:documentLaunchMode="never" + android:relinquishTaskIdentity="true"> <intent-filter> <action android:name="android.intent.action.CHOOSER" /> <category android:name="android.intent.category.DEFAULT" /> @@ -2795,7 +2849,7 @@ </activity> <activity android:name="com.android.internal.app.RestrictionsPinActivity" - android:theme="@style/Theme.Holo.Dialog.Alert" + android:theme="@style/Theme.Material.Light.Dialog.Alert" android:excludeFromRecents="true" android:windowSoftInputMode="adjustPan" android:process=":ui"> @@ -2882,6 +2936,12 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service + android:name="com.android.server.pm.BackgroundDexOptService" + android:exported="true" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + </application> </manifest> diff --git a/core/res/assets/images/android-logo-mask.png b/core/res/assets/images/android-logo-mask.png Binary files differindex 1e8ab95..ad40645 100644 --- a/core/res/assets/images/android-logo-mask.png +++ b/core/res/assets/images/android-logo-mask.png diff --git a/core/res/assets/images/android-logo-shine.png b/core/res/assets/images/android-logo-shine.png Binary files differindex 60144f1..cb65f22 100644 --- a/core/res/assets/images/android-logo-shine.png +++ b/core/res/assets/images/android-logo-shine.png diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml index 7e212be..ace17ae 100644 --- a/core/res/res/anim/lock_screen_behind_enter.xml +++ b/core/res/res/anim/lock_screen_behind_enter.xml @@ -18,7 +18,7 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60"> + android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" diff --git a/core/res/res/anim/slide_in_micro.xml b/core/res/res/anim/slide_in_micro.xml new file mode 100644 index 0000000..6320e80 --- /dev/null +++ b/core/res/res/anim/slide_in_micro.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/slide_in_micro.xml +** +** 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="100%p" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> +</set> diff --git a/core/res/res/anim/slide_out_micro.xml b/core/res/res/anim/slide_out_micro.xml new file mode 100644 index 0000000..4cb6df0 --- /dev/null +++ b/core/res/res/anim/slide_out_micro.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/slide_out_micro.xml +** +** 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="0" android:toXDelta="100%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> +</set> diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png Binary files differindex 17acfc5..c5c0e97 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png Binary files differindex 17acfc5..c5c0e97 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png Binary files differindex 9b8ca22..3b31225 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png Binary files differindex 9b8ca22..3b31225 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png Binary files differindex bc20f6c..b65009e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png Binary files differindex bc20f6c..b65009e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png Binary files differindex 571819b..a2dfcae 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png Binary files differindex 571819b..c3fda0e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png Binary files differindex 1f83b5a..bae60a7 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png Binary files differindex 1f83b5a..bae60a7 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png Binary files differindex 733cf45..a9653b0 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png Binary files differindex 733cf45..a9653b0 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png Binary files differindex 2265de4..394cb5e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png Binary files differindex 2265de4..394cb5e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png Binary files differindex f3ada58..aa23c6e 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png Binary files differindex f3ada58..028b3b8 100644 --- a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png +++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 0000000..4d97045 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 0000000..46fb463 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png Binary files differnew file mode 100644 index 0000000..8866539 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_recent.png diff --git a/core/res/res/drawable-hdpi/sim_dark_blue.9.png b/core/res/res/drawable-hdpi/sim_dark_blue.9.png Binary files differnew file mode 100755 index 0000000..b991535 --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_dark_blue.9.png diff --git a/core/res/res/drawable-hdpi/sim_dark_green.9.png b/core/res/res/drawable-hdpi/sim_dark_green.9.png Binary files differnew file mode 100755 index 0000000..c8de61d --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_dark_green.9.png diff --git a/core/res/res/drawable-hdpi/sim_dark_orange.9.png b/core/res/res/drawable-hdpi/sim_dark_orange.9.png Binary files differnew file mode 100755 index 0000000..10347e8 --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_dark_orange.9.png diff --git a/core/res/res/drawable-hdpi/sim_dark_purple.9.png b/core/res/res/drawable-hdpi/sim_dark_purple.9.png Binary files differnew file mode 100755 index 0000000..ac4ee01 --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_dark_purple.9.png diff --git a/core/res/res/drawable-hdpi/sim_light_blue.9.png b/core/res/res/drawable-hdpi/sim_light_blue.9.png Binary files differnew file mode 100755 index 0000000..b2c5581 --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_light_blue.9.png diff --git a/core/res/res/drawable-hdpi/sim_light_green.9.png b/core/res/res/drawable-hdpi/sim_light_green.9.png Binary files differnew file mode 100755 index 0000000..4d29c81 --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_light_green.9.png diff --git a/core/res/res/drawable-hdpi/sim_light_orange.9.png b/core/res/res/drawable-hdpi/sim_light_orange.9.png Binary files differnew file mode 100755 index 0000000..68c6c2f --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_light_orange.9.png diff --git a/core/res/res/drawable-hdpi/sim_light_purple.9.png b/core/res/res/drawable-hdpi/sim_light_purple.9.png Binary files differnew file mode 100755 index 0000000..4deb8dc --- /dev/null +++ b/core/res/res/drawable-hdpi/sim_light_purple.9.png diff --git a/core/res/res/drawable-hdpi/work_icon.png b/core/res/res/drawable-hdpi/work_icon.png Binary files differdeleted file mode 100644 index be8a36e..0000000 --- a/core/res/res/drawable-hdpi/work_icon.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png Binary files differindex 3fdd3bc..4e6d076 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png Binary files differindex 3fdd3bc..4e6d076 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png Binary files differindex eaa02b3..ca61cb2 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png Binary files differindex eaa02b3..ca61cb2 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png Binary files differindex 28c8b94..b5999be 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png Binary files differindex 28c8b94..b5999be 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png Binary files differindex 6090cce..8392ac3 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png Binary files differindex 6090cce..522bafd 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png Binary files differindex 3f2e982..ebb2f8b 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png Binary files differindex 3f2e982..ebb2f8b 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png Binary files differindex 14b958b..3fa20ca 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png Binary files differindex 14b958b..3fa20ca 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png Binary files differindex 4db22d4..6cc59ed 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png Binary files differindex 4db22d4..6cc59ed 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png Binary files differindex a11e1c7..a1fcd08 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png Binary files differindex a11e1c7..c6c0224 100644 --- a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png +++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 0000000..163f4a0 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 0000000..bbfb83c --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png Binary files differnew file mode 100644 index 0000000..2b607df --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_recent.png diff --git a/core/res/res/drawable-mdpi/sim_dark_blue.9.png b/core/res/res/drawable-mdpi/sim_dark_blue.9.png Binary files differnew file mode 100755 index 0000000..d646a7f --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_dark_blue.9.png diff --git a/core/res/res/drawable-mdpi/sim_dark_green.9.png b/core/res/res/drawable-mdpi/sim_dark_green.9.png Binary files differnew file mode 100755 index 0000000..ee4ea0d --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_dark_green.9.png diff --git a/core/res/res/drawable-mdpi/sim_dark_orange.9.png b/core/res/res/drawable-mdpi/sim_dark_orange.9.png Binary files differnew file mode 100755 index 0000000..b394999 --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_dark_orange.9.png diff --git a/core/res/res/drawable-mdpi/sim_dark_purple.9.png b/core/res/res/drawable-mdpi/sim_dark_purple.9.png Binary files differnew file mode 100755 index 0000000..459b5d6 --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_dark_purple.9.png diff --git a/core/res/res/drawable-mdpi/sim_light_blue.9.png b/core/res/res/drawable-mdpi/sim_light_blue.9.png Binary files differnew file mode 100755 index 0000000..396ad70 --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_light_blue.9.png diff --git a/core/res/res/drawable-mdpi/sim_light_green.9.png b/core/res/res/drawable-mdpi/sim_light_green.9.png Binary files differnew file mode 100755 index 0000000..a063174 --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_light_green.9.png diff --git a/core/res/res/drawable-mdpi/sim_light_orange.9.png b/core/res/res/drawable-mdpi/sim_light_orange.9.png Binary files differnew file mode 100755 index 0000000..95ea88e --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_light_orange.9.png diff --git a/core/res/res/drawable-mdpi/sim_light_purple.9.png b/core/res/res/drawable-mdpi/sim_light_purple.9.png Binary files differnew file mode 100755 index 0000000..b1bd35f --- /dev/null +++ b/core/res/res/drawable-mdpi/sim_light_purple.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png Binary files differindex c08deab..1e45530 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png Binary files differindex c08deab..1e45530 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png Binary files differindex 8b1a55c..2c63c5d 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png Binary files differindex 8b1a55c..2c63c5d 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png Binary files differindex 77cd1fa..dd5e26e 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png Binary files differindex 77cd1fa..dd5e26e 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png Binary files differindex e0e3540..aa9b3c5 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png Binary files differindex e0e3540..367c25a 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png Binary files differindex 324e490..df28ad0 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png Binary files differindex 324e490..df28ad0 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png Binary files differindex e126cc6..3a27831 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png Binary files differindex e126cc6..3a27831 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png Binary files differindex 4c1f1b9..d68bdf4 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png Binary files differindex 4c1f1b9..d68bdf4 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png Binary files differindex 219d37b..da03ec9 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png Binary files differindex 219d37b..482b249 100644 --- a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 0000000..21d4d53 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 0000000..2aeb9a2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png Binary files differnew file mode 100644 index 0000000..86316db --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_recent.png diff --git a/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 0000000..1b11b59 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 0000000..ae0d655 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png Binary files differnew file mode 100644 index 0000000..e6bd125 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_recent.png diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 0000000..8774412 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 0000000..1375acc --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml index 5325712..16c101b 100644 --- a/core/res/res/drawable/ic_corp_badge.xml +++ b/core/res/res/drawable/ic_corp_badge.xml @@ -15,20 +15,29 @@ Copyright (C) 2014 The Android Open Source Project --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="19.0dp" - android:height="19.0dp"/> + android:width="20.0dp" + android:height="20.0dp"/> <viewport - android:viewportWidth="19.0" - android:viewportHeight="19.0"/> + android:viewportWidth="20.0" + android:viewportHeight="20.0"/> <path - android:pathData="M9.5,9.5m-9.5,0.0a9.5,9.5 0.0,1.0 1.0,19.0 0.0a9.5,9.5 0.0,1.0 1.0,-19.0 0.0" + android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0" android:fill="#FF5722"/> <path - android:pathData="M12.667,7.125l-1.583,0.0L11.084,6.333l-0.792,-0.792L8.708,5.5410004L7.917,6.333l0.0,0.792L6.333,7.125c-0.438,0.0 -0.788,0.354 -0.788,0.792l-0.004,4.354c0.0,0.438 0.354,0.792 0.792,0.792l6.333,0.0c0.438,0.0 0.792,-0.354 0.792,-0.792L13.458,7.917C13.458,7.479 13.104,7.125 12.667,7.125zM10.094,10.687L8.906,10.687L8.906,9.5l1.188,0.0L10.094,10.687zM10.292,7.125L8.708,7.125L8.708,6.333l1.583,0.0L10.291,7.125z" + android:pathData="M11.139,12.149l-0.001,0.0L8.996,12.149l0.0,-0.571L4.738,11.578l-0.002,2.198c0.0,0.589 0.477,1.066 1.066,1.066l8.535,0.0c0.589,0.0 1.066,-0.477 1.066,-1.066l0.0,-2.198l-4.264,0.0L11.139,12.149z" android:fill="#FFFFFF"/> <path - android:pathData="M4.75,4.75 h9.5 v9.5 h-9.5z" + android:pathData="M8.996,10.006l2.143,0.0l0.0,0.52l4.442,0.0L15.580999,7.909c0.0,-0.589 -0.477,-1.066 -1.066,-1.066l-1.877,0.0L7.544,6.843L5.606,6.843c-0.589,0.0 -1.061,0.477 -1.061,1.066l-0.003,2.617l4.453,0.0L8.996,10.006L8.996,10.006z" + android:fill="#FFFFFF"/> + <path + android:pathData="M3.367,3.456 h13.016 v13.016 h-13.016z" + android:fill="#00000000"/> + <path + android:pathData="M7.368,5.263l5.263,0.0l0.0,1.053l-5.263,0.0z" + android:fill="#FFFFFF"/> + <path + android:pathData="M8.996,12.149l2.1419992,0.0 0.0010004044,0.0 0.0,-0.5699997 -2.1429996,0.0z" android:fill="#00000000"/> </vector> diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge.xml index 7bfab4c..c8e49e1 100644 --- a/core/res/res/drawable/ic_corp_icon_badge.xml +++ b/core/res/res/drawable/ic_corp_icon_badge.xml @@ -24,17 +24,31 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FF000000" - android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/> + android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0" + android:fillOpacity="0.2"/> <path android:fill="#FF000000" - android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/> + android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0" + android:fillOpacity="0.2"/> <path android:pathData="M49.0,49.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0" android:fill="#FF5722"/> <path - android:pathData="M53.667,45.5l-2.333,0.0l0.0,-1.167l-1.167,-1.167l-2.333,0.0l-1.167,1.167L46.667,45.5l-2.333,0.0c-0.645,0.0 -1.161,0.522 -1.161,1.167l-0.006,6.417c0.0,0.645 0.522,1.167 1.167,1.167l9.333,0.0c0.645,0.0 1.167,-0.522 1.167,-1.167l0.0,-6.417C54.833,46.022 54.311,45.5 53.667,45.5zM49.875,50.75l-1.75,0.0L48.125,49.0l1.75,0.0L49.875,50.75zM50.167,45.5l-2.333,0.0l0.0,-1.167l2.333,0.0L50.167,45.5z" + android:pathData="M50.594,52.009l-3.0,0.0L47.594,51.0l-5.961,0.0l-0.003,3.289c0.0,0.826 0.668,1.494 1.494,1.494l11.948,0.0c0.826,0.0 1.494,-0.668 1.494,-1.494L56.566006,51.0l-5.972,0.0C50.594,51.0 50.594,52.009 50.594,52.009z" android:fill="#FFFFFF"/> <path - android:pathData="M42.0,42.0 h14.0 v14.0 h-14.0z" + android:pathData="M47.594,49.009l3.0,0.0L50.594,50.0l6.22,0.0l0.0,-3.925c0.0,-0.826 -0.668,-1.494 -1.494,-1.494l-2.627,0.0l-7.131,-0.001l-2.713,0.0c-0.826,0.0 -1.486,0.668 -1.486,1.494L41.359,50.0l6.235,0.0L47.594,49.009z" + android:fill="#FFFFFF"/> + <path + android:pathData="M39.714,39.838 h18.221 v18.221 h-18.221z" + android:fill="#00000000"/> + <path + android:pathData="M47.594,49.009 h3.0 v0.991 h-3.0z" android:fill="#00000000"/> + <path + android:pathData="M47.594,51.0 h3.0 v1.009 h-3.0z" + android:fill="#00000000"/> + <path + android:pathData="M46.0,43.0l6.0,0.0l0.0,1.0l-6.0,0.0z" + android:fill="#FFFFFF"/> </vector> diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/lock_task_notify_bg.xml new file mode 100644 index 0000000..3a8fab5 --- /dev/null +++ b/core/res/res/drawable/lock_task_notify_bg.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <solid android:color="@android:color/black" /> + <corners + android:radius="8dip" /> +</shape> diff --git a/core/res/res/drawable/view_accessibility_focused.xml b/core/res/res/drawable/view_accessibility_focused.xml index 0da9a81..68e3f1e 100644 --- a/core/res/res/drawable/view_accessibility_focused.xml +++ b/core/res/res/drawable/view_accessibility_focused.xml @@ -17,9 +17,11 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" > <stroke - android:width="2dp" - android:color="@color/accessibility_focus_highlight" /> + android:width="4dp" + android:color="@color/accessibility_focus_highlight" + android:dashWidth="4dp" + android:dashGap="2dp" /> - <corners android:radius="2dp"/> + <corners android:radius="2dp" /> </shape> diff --git a/core/res/res/layout/alert_dialog_leanback.xml b/core/res/res/layout/alert_dialog_leanback.xml index 8655aea..848015c 100644 --- a/core/res/res/layout/alert_dialog_leanback.xml +++ b/core/res/res/layout/alert_dialog_leanback.xml @@ -1,20 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** 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. -*/ + 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. --> <LinearLayout @@ -22,11 +20,13 @@ android:id="@+id/parentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@drawable/dialog_background_material" + android:translationZ="@dimen/floating_window_z" android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin" - android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin" android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin" - android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin" - android:orientation="vertical"> + android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin" + android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" @@ -37,16 +37,17 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical|start" - android:minHeight="@dimen/alert_dialog_title_height" - android:layout_marginStart="16dip" - android:layout_marginEnd="16dip"> + android:paddingStart="16dip" + android:paddingEnd="16dip" + android:paddingTop="16dip"> <ImageView android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingEnd="8dip" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_marginEnd="8dip" + android:scaleType="fitCenter" android:src="@null" /> - <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" - style="?android:attr/windowTitleStyle" + <TextView android:id="@+id/alertTitle" + style="?attr/windowTitleStyle" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" @@ -67,13 +68,12 @@ android:layout_height="wrap_content" android:clipToPadding="false"> <TextView android:id="@+id/message" - style="?android:attr/textAppearanceMedium" + style="?attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dip" android:paddingEnd="16dip" - android:paddingTop="8dip" - android:paddingBottom="8dip"/> + android:paddingTop="16dip" /> </ScrollView> </LinearLayout> @@ -91,41 +91,32 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="@dimen/alert_dialog_button_bar_height" - android:orientation="vertical"> + android:orientation="vertical" + android:gravity="end" + android:padding="16dip"> <LinearLayout - style="?android:attr/buttonBarStyle" - android:layout_width="match_parent" + style="?attr/buttonBarStyle" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:layoutDirection="locale" - android:measureWithLargestChild="true"> - <Button android:id="@+id/button2" + android:layoutDirection="locale"> + <Button android:id="@+id/button3" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" - android:layout_gravity="start" - android:layout_weight="1" + android:layout_height="wrap_content" android:maxLines="2" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:minHeight="@dimen/alert_dialog_button_bar_height" - android:layout_height="wrap_content" /> - <Button android:id="@+id/button3" + android:minHeight="@dimen/alert_dialog_button_bar_height" /> + <Button android:id="@+id/button2" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_weight="1" + android:layout_height="wrap_content" android:maxLines="2" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:minHeight="@dimen/alert_dialog_button_bar_height" - android:layout_height="wrap_content" /> + android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button1" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" - android:layout_gravity="end" - android:layout_weight="1" + android:layout_height="wrap_content" android:maxLines="2" - android:minHeight="@dimen/alert_dialog_button_bar_height" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:layout_height="wrap_content" /> + android:minHeight="@dimen/alert_dialog_button_bar_height" /> </LinearLayout> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml b/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml index 096b015..829d5aa 100644 --- a/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml +++ b/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml @@ -1,20 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** 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. -*/ + 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. --> <LinearLayout @@ -22,18 +20,20 @@ android:id="@+id/parentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="horizontal" + android:background="@drawable/dialog_background_material" + android:translationZ="@dimen/floating_window_z" android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin" - android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin" android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin" - android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin" - android:orientation="horizontal"> + android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin" + android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin"> - <LinearLayout - android:id="@+id/leftPanel" - android:layout_width="0dp" - android:layout_weight="0.66" - android:layout_height="wrap_content" - android:orientation="vertical"> + <LinearLayout + android:id="@+id/leftPanel" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:orientation="vertical"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" @@ -44,16 +44,17 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical|start" - android:minHeight="@dimen/alert_dialog_title_height" - android:layout_marginStart="16dip" - android:layout_marginEnd="16dip"> + android:paddingStart="16dip" + android:paddingEnd="16dip" + android:paddingTop="16dip"> <ImageView android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingEnd="8dip" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_marginEnd="8dip" + android:scaleType="fitCenter" android:src="@null" /> - <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" - style="?android:attr/windowTitleStyle" + <TextView android:id="@+id/alertTitle" + style="?attr/windowTitleStyle" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" @@ -66,6 +67,7 @@ <LinearLayout android:id="@+id/contentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_weight="1" android:orientation="vertical" android:minHeight="64dp"> <ScrollView android:id="@+id/scrollView" @@ -73,13 +75,13 @@ android:layout_height="wrap_content" android:clipToPadding="false"> <TextView android:id="@+id/message" - style="?android:attr/textAppearanceMedium" + style="?attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dip" android:paddingEnd="16dip" - android:paddingTop="8dip" - android:paddingBottom="8dip"/> + android:paddingTop="16dip" + android:paddingBottom="16dip" /> </ScrollView> </LinearLayout> @@ -92,51 +94,41 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> </FrameLayout> - </LinearLayout> + </LinearLayout> <LinearLayout android:id="@+id/buttonPanel" - android:layout_width="0dp" - android:layout_weight="0.33" - android:layout_height="match_parent" + android:layout_width="wrap_content" +android:background="#ffffff" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" android:minHeight="@dimen/alert_dialog_button_bar_height" - android:paddingLeft="32dp" - android:paddingRight="32dp" - android:orientation="horizontal"> + android:orientation="vertical" + android:gravity="end" + android:padding="16dip"> <LinearLayout - style="?android:attr/buttonBarStyle" - android:layout_width="match_parent" + style="?attr/buttonBarStyle" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:orientation="vertical" android:layoutDirection="locale" - android:measureWithLargestChild="true"> - <Button android:id="@+id/button1" - android:layout_width="match_parent" - android:gravity="center_vertical" - android:layout_weight="1" - android:maxLines="2" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:minHeight="@dimen/alert_dialog_button_bar_height" - android:layout_height="wrap_content" /> + android:orientation="vertical"> <Button android:id="@+id/button3" + style="?attr/buttonBarButtonStyle" android:layout_width="match_parent" - android:gravity="center_vertical" - android:layout_weight="1" + android:layout_height="wrap_content" android:maxLines="2" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:minHeight="@dimen/alert_dialog_button_bar_height" - android:layout_height="wrap_content" /> + android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button2" + style="?attr/buttonBarButtonStyle" android:layout_width="match_parent" - android:gravity="center_vertical" - android:layout_weight="1" + android:layout_height="wrap_content" + android:maxLines="2" + android:minHeight="@dimen/alert_dialog_button_bar_height" /> + <Button android:id="@+id/button1" + style="?attr/buttonBarButtonStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" android:maxLines="2" - android:minHeight="@dimen/alert_dialog_button_bar_height" - style="?android:attr/buttonBarButtonStyle" - android:textSize="14sp" - android:layout_height="wrap_content" /> + android:minHeight="@dimen/alert_dialog_button_bar_height" /> </LinearLayout> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml new file mode 100644 index 0000000..c034536 --- /dev/null +++ b/core/res/res/layout/lock_to_app_enter.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:paddingBottom="12dp" + android:alpha=".8" + android:background="@drawable/lock_task_notify_bg" > + + <ImageView + android:id="@+id/lock_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:alpha=".8" + android:src="@drawable/ic_lock_outline_wht_24dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/lock_icon" + android:layout_centerHorizontal="true" + android:layout_marginTop="2dp" + android:textColor="@android:color/white" + android:alpha=".8" + android:text="@string/lock_to_app_start" /> +</RelativeLayout> diff --git a/core/res/res/layout/lock_to_app_exit.xml b/core/res/res/layout/lock_to_app_exit.xml new file mode 100644 index 0000000..4a60c80 --- /dev/null +++ b/core/res/res/layout/lock_to_app_exit.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:paddingBottom="12dp" + android:alpha=".8" + android:background="@drawable/lock_task_notify_bg" > + + <ImageView + android:id="@+id/lock_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:alpha=".8" + android:src="@drawable/ic_lock_open_wht_24dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/lock_icon" + android:layout_centerHorizontal="true" + android:layout_marginTop="2dp" + android:textColor="@android:color/white" + android:alpha=".8" + android:text="@string/lock_to_app_exit" /> +</RelativeLayout> diff --git a/core/res/res/layout/preference_widget_seekbar.xml b/core/res/res/layout/preference_widget_seekbar.xml index c427965..05daa1a 100644 --- a/core/res/res/layout/preference_widget_seekbar.xml +++ b/core/res/res/layout/preference_widget_seekbar.xml @@ -14,9 +14,7 @@ limitations under the License. --> -<!-- Layout for a Preference in a PreferenceActivity. The - Preference is able to place a specific widget for its particular - type in the "widget_frame" layout. --> +<!-- Layout used by SeekBarPreference for the seekbar widget style. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/core/res/res/layout/preference_widget_seekbar_material.xml b/core/res/res/layout/preference_widget_seekbar_material.xml new file mode 100644 index 0000000..f70a472 --- /dev/null +++ b/core/res/res/layout/preference_widget_seekbar_material.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- Layout used by SeekBarPreference for the seekbar widget style. --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:paddingStart="?attr/listPreferredItemPaddingStart" + android:paddingEnd="?attr/listPreferredItemPaddingEnd"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center" + android:minWidth="@dimen/preference_icon_minWidth" + android:orientation="horizontal"> + <ImageView + android:id="@+android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:minWidth="48dp" + /> + </LinearLayout> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dip" + android:layout_marginEnd="8dip" + android:layout_marginTop="6dip" + android:layout_marginBottom="6dip" + android:layout_weight="1"> + + <TextView android:id="@+android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> + + <TextView android:id="@+android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignStart="@android:id/title" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="4" /> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout android:id="@+android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_below="@android:id/summary" + android:layout_alignStart="@android:id/title" + android:minWidth="@dimen/preference_widget_width" + android:gravity="center" + android:orientation="vertical" /> + + <SeekBar android:id="@+android:id/seekbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/summary" + android:layout_toEndOf="@android:id/widget_frame" + android:layout_alignParentEnd="true" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/core/res/res/layout/progress_dialog_leanback.xml b/core/res/res/layout/progress_dialog_leanback.xml deleted file mode 100644 index 6bcad7a..0000000 --- a/core/res/res/layout/progress_dialog_leanback.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** 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. -*/ ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <View - android:layout_width="match_parent" - android:layout_height="2dip" - android:background="@android:color/leanback_dark_gray" /> - <LinearLayout android:id="@+id/body" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:baselineAligned="false" - android:padding="16dip"> - - <ProgressBar android:id="@android:id/progress" - style="?android:attr/progressBarStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:max="10000" - android:layout_marginEnd="16dip" /> - - <TextView android:id="@+id/message" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" /> - </LinearLayout> -</LinearLayout> diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml index 8bf8416..5acb588 100644 --- a/core/res/res/layout/screen_action_bar.xml +++ b/core/res/res/layout/screen_action_bar.xml @@ -34,7 +34,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?attr/actionBarStyle" - android:viewName="android:action_bar" + android:transitionName="android:action_bar" android:gravity="top"> <com.android.internal.widget.ActionBarView android:id="@+id/action_bar" diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml index c8952bf..b385bed 100644 --- a/core/res/res/layout/screen_custom_title.xml +++ b/core/res/res/layout/screen_custom_title.xml @@ -31,7 +31,7 @@ This is a custom layout for a screen. <FrameLayout android:id="@android:id/title_container" android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" - android:viewName="android:title" + android:transitionName="android:title" style="?android:attr/windowTitleBackgroundStyle"> </FrameLayout> <FrameLayout android:id="@android:id/content" diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml index 290c7da..56815f8 100644 --- a/core/res/res/layout/screen_toolbar.xml +++ b/core/res/res/layout/screen_toolbar.xml @@ -34,7 +34,7 @@ This is an optimized layout for a screen with a toolbar enabled. android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?attr/actionBarStyle" - android:viewName="android:action_bar" + android:transitionName="android:action_bar" android:gravity="top"> <Toolbar android:id="@+id/action_bar" diff --git a/core/res/res/layout/subscription_item_layout.xml b/core/res/res/layout/subscription_item_layout.xml new file mode 100755 index 0000000..9f8f2b3 --- /dev/null +++ b/core/res/res/layout/subscription_item_layout.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright (C) 2014 MediaTek Inc. +** +** 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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" > + <RelativeLayout + android:layout_width="48dip" + android:layout_height="32dip" + android:id="@+id/sub_color" + android:layout_marginEnd="6dip" + android:layout_centerVertical="true"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/sub_short_number" + android:layout_marginBottom="2dip" + android:layout_marginEnd="4dip" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:textSize="12sp" + android:singleLine="true" + android:textColor="@android:color/white" + android:includeFontPadding="false"/> + </RelativeLayout> + <RelativeLayout + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_centerVertical="true"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/sub_name" + android:singleLine="true" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:scrollHorizontally="true" + android:textAppearance="?android:attr/textAppearanceMedium"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/sub_number" + android:layout_below="@+id/sub_name" + android:layout_alignStart="@+id/sub_name" + android:singleLine="true" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorSecondary"/> + </RelativeLayout> +</LinearLayout> diff --git a/core/res/res/transition/explode.xml b/core/res/res/transition/explode.xml new file mode 100644 index 0000000..fe22284 --- /dev/null +++ b/core/res/res/transition/explode.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<explode xmlns:android="http://schemas.android.com/apk/res/android"/> diff --git a/core/res/res/transition/slide_bottom.xml b/core/res/res/transition/slide_bottom.xml new file mode 100644 index 0000000..46dc0d6 --- /dev/null +++ b/core/res/res/transition/slide_bottom.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="bottom"/> diff --git a/core/res/res/transition/slide_left.xml b/core/res/res/transition/slide_left.xml new file mode 100644 index 0000000..997bd97 --- /dev/null +++ b/core/res/res/transition/slide_left.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="left"/> diff --git a/core/res/res/transition/slide_right.xml b/core/res/res/transition/slide_right.xml new file mode 100644 index 0000000..98f8f6a --- /dev/null +++ b/core/res/res/transition/slide_right.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="right"/> diff --git a/core/res/res/transition/slide_top.xml b/core/res/res/transition/slide_top.xml new file mode 100644 index 0000000..07ab945 --- /dev/null +++ b/core/res/res/transition/slide_top.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="top"/> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 9e7a4d1..60fa084 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Dit laat die houer toe om aan die topvlak-koppelvlak van \'n muurpapier te bind. Dit moet nooit vir normale programme nodig wees nie."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"verbind met \'n steminteraksiediens"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Laat die houer toe om met die topvlak-koppelvlak van \'n steminteraksiediens te verbind. Behoort nooit vir normale programme nodig te wees nie."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"bestuur stemsleutelfrases"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Laat die houer toe om die sleutelfrases vir aktiveerwoordbespeuring met stem te bestuur. Behoort nooit vir normale programme nodig te wees nie."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"koppel aan \'n afstandskerm"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Dit laat die houer toe om aan die top-koppelvlak van \'n afstandskerm te koppel. Behoort nooit vir gewone programme nodig te wees nie."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind aan \'n legstukdiens"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"neem klank op"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Laat die program toe om klank met die mikrofoon op te neem. Hierdie toestemming laat die program toe om klank te eniger tyd, sonder jou bevestiging, op te neem."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-kommunikasie"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string> <string name="permlab_camera" msgid="3616391919559751192">"neem foto\'s en video\'s"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Laat die program toe om foto\'s en video\'s met die kamera te neem. Hierdie toestemming laat die program toe om die kamera te eniger tyd sonder jou bevestiging te gebruik."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiveer LED wat oordrag aandui wanneer kamera gebruik word"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Laat die program toe om \'n alarm in \'n geïnstalleerde wekkerprogram te stel. Sommige wekkerprogramme werk dalk nie met hierdie funksie nie."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"voeg stemboodskap by"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Laat die program toe om boodskappe by te voeg by jou stempos-inkassie."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lees alle stempos"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Laat die program toe om al jou stemposse te lees."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"verander blaaier se geoligging-toestemmings"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Laat die program toe om die blaaier se geoligging-toestemmings te verander. Kwaadwillige programme kan dit gebruik om hulle toe te laat om ligginginligting aan enige webwerf te stuur."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifieer pakkies"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Laat \'n program toe om vir veranderinge in vertrouenstaat te luister."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Voorsien \'n vertroude agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Laat \'n program toe om \'n vertroude agent te voorsien."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Begin vertrouensagente se instellingskieslys."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Laat \'n program toe om \'n aktiwiteit te begin wat die vertrouensagent se gedrag verander."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Verbind met \'n vertrouensagentdiens"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Laat \'n program toe om met \'n vertrouensagentdiens te verbind."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Tree in wisselwerking met opdatering- en terugstellingstelsel"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Kies jaar"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> gekies"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> uitgevee"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk-<xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Gebruik Sluit-na-program?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Sluit-na-program sluit die skerm in \'n enkele program.\n\nOm uit te gaan, druk en hou die knoppie vir onlangse programme $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"BEGIN"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Begin Sluit-na-program"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Gaan uit Sluit-na-program"</string> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 14d0af2..7a08236 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ያዡ ግቤት ስልቱን ወደ ከፍተኛ-ደረጃ ልጣፍ ለመጠረዝ ይፈቅዳሉ። ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ከአንድ የድምጽ በይነተገናኝ ጋር ይሰሩ"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"ያዢው የአንድ የድምጽ በይነግንኙነት አገልግሎት የከፍተኛ ደረጃ በይነገጽ እንዲያስር ያስችለዋል። ለመደበኛ መተግበሪያዎች በጭራሽ አያስፈልግም።"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ድምጽ የቁልፍ ሃረጎችን አስተዳድር"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"መያዣው የቁልፍ ሃረጎችን ለድምጽ ትኩስ ቃል ለይቶ ማወቂያ እንዲያስተዳድር ይፈቅድለታል። ለመደበኛ መተግበሪያዎች ማስፈለግ የለበትም።"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ከአንድ የርቀት ማሳያ ጋር ይጠርዛል"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"ያዢው ከአንድ የርቀት ማሳያ ከፍተኛ-ደረጃ በይነገጽ ጋር እንዲጠርዝ ይፈቅድለታል። ለመደበኛ መተግበሪያዎች በጭራሽ አያስፈልግም።"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ወደ ፍርግም አገልግሎት አያይዝ"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"መተግበሪያው አንደ የድምጽ መጠን እና ለውጽአት የትኛውን የድምጽ ማጉያ ጥቅም ላይ እንደዋለ የመሳሰሉ ሁለንተናዊ የድምጽ ቅንብሮችን እንዲያስተካክል ይፈቅድለታል።"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ኦዲዮ ይቅዱ"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"መተግበሪያው ድምጽን በማይክሮፎን እንዲቀዳ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው ያላንተ ማረጋገጫ በማንኛውም ጊዜ ድምጽ እንዲቀዳ ይፈቅድለታል።"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"የሲም ግንኙነት"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string> <string name="permlab_camera" msgid="3616391919559751192">"ፎቶዎች እና ቪዲዮዎች ያንሱ"</string> <string name="permdesc_camera" msgid="8497216524735535009">"መተግበሪያው በካሜራው ፎቶዎችንና ቪዲዮዎችን እንዲያነሳ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው ካሜራውን በማንኛውም ጊዜ ያላንተ ማረጋገጫ እንዲጠቀም ይፈቅድለታል።"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ካሜራው ስራ ላይ ሲሆን የማስተላለፍ አመልካች ኤል ኢ ዲን ያሰናክሉ"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"በተጫነው የማንቂያ ሰዓት መተግበሪያ ውስጥ ማንቅያን ለማደራጀት ለመተግበሪያው ይፈቅዳሉ፡፡አንዳንድ የማንቂያ ሰዓት መተግበሪያዎች ይሄንን ባህሪ ላይፈፅሙ ይችላሉ፡፡"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"የድምፅ መልዕክት አክል"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ወደ ድምፅ መልዕክት የገቢ መልዕክትህ መልዕክቶች ለማከል ለመተግበሪያው ይፈቅዳሉ።"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ሁሉንም የድምጽ መልዕክት ያነብባል"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"መተግበሪያው ሁሉንም የድምጽ መልዕክቶችዎ እንዲያነባቸው ያስችለዋል።"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"የአሳሽ ገፀ ሥፍራ ፍቃዶችን ቀይር"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"የአሳሹን የጂኦ-አካባቢ ፍቃዶችን እንዲለውጥ ለመተግበሪያው ይፈቅዳል፡፡ተንኮል አዘል መተግበሪያዎች የመላኪያ አከባቢን መረጃ ወደ አጠራጣሪ የድር ጣቢያዎች ለመፍቀድ ይሄንን ሊጠቀሙበት ይችላሉ፡፡"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ፓኬጆችን አረጋግጥ"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"መተግበሪያው በተአማኒነት ሁኔታ ውስጥ ለውጦችን እንዲያዳምጥ ይፈቅዳል።"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"የመታመን ወኪል ያቅርቡ።"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"አንድ መተግበሪያ የመታመን ወኪል እንዲያቀርብ ይፈቅድለታል።"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"የእምነት ወኪል ቅንብሮች ምናሌን አስጀምር።"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"አንድ መተግበሪያ የእምነት ወኪል ባህሪ የሚቀይር እንቅስቃሴ እንዲያስጀምር ያስችለዋል።"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ለተአማኒነት ወኪል አገልግሎት ተገዢ አድርግ"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"ለመተግበሪያን የተአማኒነት ወኪል አገልግሎትን እንዲያከብር ይፈቅዳል።"</string> <string name="permlab_recovery" msgid="3157024487744125846">"ከዝማኔዎች እና ከመልሶ ማግኛ ስርዓቶች ጋር ይገናኙ"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"ዓመት ይምረጡ"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ተመርጧል"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ተሰርዟል"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"ስራ <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 319d210..d53daba 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"للسماح للمالك بالالتزام بواجهة المستوى العلوي للخلفية. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"الربط بخدمة التفاعل الصوتي"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"للسماح للمالك بالربط بواجهة المستوى العلوي لخدمة التفاعل الصوتي. لن تكون هناك حاجة إلى هذا الإعداد مطلقًا مع التطبيقات العادية."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"إدارة العبارات الأساسية الصوتية"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"للسماح للمالك بإدارة العبارات الأساسية لاكتشاف الكلمات المهمة الصوتية. لا يجب استخدامه على الإطلاق للتطبيقات العادية."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"الربط بالشاشة عن بُعد"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"للسماح للمالك بالالتزام بواجهة المستوى العلوي للعرض عن بُعد. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"الالتزام بخدمة أداة"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"للسماح للتطبيق بتعديل إعدادات الصوت العامة مثل مستوى الصوت وأي السماعات يتم استخدامها للاستماع."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"تسجيل الصوت"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"للسماح للتطبيق بتسجيل الصوت باستخدام الميكروفون. ويتيح هذا الإذن للتطبيق تسجيل الصوت في أي وقت وبدون موافقة منك."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"اتصالات SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"السماح للتطبيق بإرسال أوامر إلى بطاقة SIM. وهذا أمر بالغ الخطورة."</string> <string name="permlab_camera" msgid="3616391919559751192">"التقاط صور ومقاطع فيديو"</string> <string name="permdesc_camera" msgid="8497216524735535009">"للسماح للتطبيق بالتقاط صور ومقاطع فيديو من خلال الكاميرا. ويتيح هذا الإذن للتطبيق استخدام الكاميرا في أي وقت وبدون موافقة منك."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"تعطيل مؤشر LED للإرسال عندما تكون الكاميرا قيد الاستخدام"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"للسماح للتطبيق بضبط المنبه في تطبيق المنبه المثبّت. ربما لا تنفذ بعض تطبيقات المنبه هذه الميزة."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"إضافة بريد صوتي"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"للسماح للتطبيق بإضافة رسائل إلى صندوق البريد الصوتي."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"قراءة جميع رسائل البريد الصوتي"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"للسماح للتطبيق بقراءة جميع رسائل البريد الصوتي."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"تعديل أذونات الموقع الجغرافي للمتصفح"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"للسماح لأحد التطبيقات بتعديل أذونات الموقع الجغرافي للمتصفح. يمكن أن تستخدم التطبيقات الضارة هذا للسماح بإرسال معلومات الموقع إلى مواقع ويب عشوائية."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"التحقق من الحزم"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"للسماح للتطبيق بالتعرف على التغييرات في حالة الاعتماد."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"توفير وكيل معتمد."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"للسماح لأحد التطبيقات بتوفير وكيل معتمد."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"تشغيل قائمة إعدادات الوكيل المعتمد."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"للسماح لأحد التطبيقات بتشغيل نشاط يؤدي إلى تغيير سلوك الوكيل المعتمد."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"الالتزام بخدمة الوكيل المعتمد"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"للسماح لأحد التطبيقات بالالتزام بخدمة الوكيل المعتمد."</string> <string name="permlab_recovery" msgid="3157024487744125846">"التفاعل مع نظام التحديث والاسترداد"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"تحديد العام"</string> <string name="item_is_selected" msgid="949687401682476608">"تم تحديد <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"تم حذف <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> المخصص للعمل"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"هل تريد استخدام قفل تشغيل الجهاز على تطبيق؟"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"تتيح ميزة \"قفل تشغيل الجهاز على تطبيق\" تأمين الشاشة وقصر استخدامها على تطبيق واحد.\n\nللخروج اضغط مع الاستمرار على زر التطبيقات الحديثة $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"لا"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"بدء"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"بدء قفل تشغيل الجهاز على تطبيق"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج من قفل تشغيل الجهاز على تطبيق"</string> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 21545aa..d77971c 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Разрешава на притежателя да се обвърже с интерфейса от най-високото ниво на тапет. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"свързване с услуга за гласово взаимодействие"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Разрешава на притежателя да се свърже с интерфейса от най-високото ниво на услуга за гласово взаимодействие. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"управление на гласовите ключови фрази"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Разрешава на притежателя да управлява ключовите фрази за функцията за откриване на произнесени активиращи думи. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"свързване с отдалечен екран"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Разрешава на притежателя да се свърже с интерфейса от първо ниво на отдалечен екран. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"обвързване с услуга за приспособления"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Разрешава на приложението да променя глобалните настройки за звука, като например силата и това, кой високоговорител се използва за изход."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"запис на звук"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Разрешава на приложението да записва звук с микрофона. Това разрешение му позволява да го прави по всяко време без потвърждение от ваша страна."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"комуникация със SIM картата"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string> <string name="permlab_camera" msgid="3616391919559751192">"правене на снимки и видеоклипове"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Разрешава на приложението да прави снимки и видеоклипове с камерата. Това разрешение му позволява да я използва по всяко време без потвърждение от ваша страна."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"деактивиране на светодиодния индикатор за предаване, когато камерата се използва"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Разрешава на приложението да навие инсталирано приложение будилник. Някои будилници може да не изпълнят тази функция."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"добавяне на гласова поща"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Разрешава на приложението да добавя съобщения към входящата ви гласова поща."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"четене на цялата гласова поща"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Разрешава на приложението да чете цялата ви гласова поща."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"промяна на разрешенията за местоположение в браузъра"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Разрешава на приложението да променя разрешенията на браузъра за местоположение. Злонамерените приложения могат да използват това, за да изпращат информация за местоположението до произволни уебсайтове."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"проверка на пакетите"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Разрешава на приложението да следи за промени в състоянието на надеждност."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Предоставяне на trust agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Разрешава на приложението да предоставя trust agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Стартиране на менюто за настройки за trust agent."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Разрешава на приложението да стартира активност, която променя поведението на trust agent."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Обвързване с услуга за trust agents"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Разрешава на приложението да се обвърже с услуга за trust agents."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Взаимодействие със системата за актуализации и възстановяване"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Избиране на година"</string> <string name="item_is_selected" msgid="949687401682476608">"Избрахте <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Изтрихте <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> за работа"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index ef5b180..46eeae8 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet que el titular vinculi a la interfície de nivell superior d\'un fons de pantalla. No s\'hauria de necessitar mai per a les aplicacions normals."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"enllaçar amb una eina d\'interacció de veu"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet enllaçar amb la interfície de nivell superior d\'un servei d\'interacció de veu. No ha de ser mai necessari per a aplicacions normals."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestiona les frases clau en veu alta"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permet que l\'usuari gestioni les frases clau per detectar paraules actives que es diguin en veu alta. Mai no hauria de ser necessari per a les aplicacions habituals."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vincula a una pantalla remota"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permet que el titular es vinculi a la interfície de nivell superior d\'una pantalla remota. No s\'hauria de necessitar mai per a les aplicacions normals."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincula a un servei de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrar àudio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet que l\'aplicació enregistri àudio amb el micròfon. Aquest permís permet que l\'aplicació enregistri àudio en qualsevol moment sense la teva confirmació."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicació SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string> <string name="permlab_camera" msgid="3616391919559751192">"fer fotos i vídeos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permet que l\'aplicació faci fotos i vídeos amb la càmera. Aquest permís permet que l\'aplicació utilitzi la càmera en qualsevol moment sense la teva confirmació."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desactiva la transmissió del LED indicador en fer servir la càmera"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet que l\'aplicació defineixi una alarma en una aplicació de despertador instal·lada. És possible que algunes aplicacions de despertador no incorporin aquesta funció."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"afegeix bústia de veu"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet que l\'aplicació afegeixi missatges a la safata d\'entrada de la bústia de veu."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accedir a tots els correus de veu"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet que l\'aplicació accedeixi a tots els teus correus de veu."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifica els permisos d\'ubicació geogràfica del navegador"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet que l\'aplicació modifiqui els permisos d\'ubicació geogràfica del navegador. Les aplicacions malicioses poden utilitzar-ho per enviar la informació d\'ubicació a llocs web arbitraris."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifica paquets"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permet que una aplicació escolti els canvis en l\'estat de confiança."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agent de confiança"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permet que una aplicació proporcioni un agent de confiança."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Inicia el menú de configuració de l\'agent de confiança."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet que una aplicació iniciï una activitat que canviï el comportament de l\'agent de confiança."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Enllaçar amb el servei d\'un agent de confiança"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permet que una aplicació es vinculi amb el servei d\'un agent de confiança."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interacciona amb el sistema de recuperació i amb les actualitzacions"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Selecciona un any"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionat"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> suprimit"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Vols fer servir la funció Bloqueja una aplicació?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"La funció Bloqueja una aplicació bloqueja la visualització a una sola aplicació.\n\nPer sortir, mantén premut el botó d\'aplicacions recents $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"INICI"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Inicia la funció Bloqueja una aplicació"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Surt de la funció Bloqueja una aplicació"</string> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 0526038..eb28a23 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Umožňuje držiteli navázat se na nejvyšší úroveň rozhraní tapety. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"navázání na hlasovou interakci"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Umožňuje držiteli navázat se na nejvyšší úroveň rozhraní služby hlasové interakce. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"správa klíčových hlasových frází"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Umožňuje držiteli spravovat klíčové hlasové fráze pro detekci hlasových klíčových slov. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"připojit se ke vzdálenému displeji"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Umožňuje držiteli připojit se k vysokoúrovňovému rozhraní vzdáleného displeje. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"navázat se na službu widgetu"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či reproduktor pro výstup zvuku."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrávání zvuku"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Umožňuje aplikaci zaznamenat zvuk pomocí mikrofonu. Toto oprávnění umožňuje aplikaci kdykoliv zaznamenat zvuk bez vašeho svolení."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikace s kartou SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string> <string name="permlab_camera" msgid="3616391919559751192">"pořizování fotografií a videí"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Umožňuje aplikaci pořizovat fotografie a videa pomocí fotoaparátu. Toto oprávnění umožňuje aplikaci používat fotoaparát kdykoliv i bez vašeho svolení."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"vypnutí indikátoru LED přenosu při použití fotoaparátu"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikaci nastavit budík v nainstalované aplikaci budík. Některé aplikace budík tuto funkci nemusí obsahovat."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"přidat hlasovou zprávu"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Umožňuje aplikaci přidávat zprávy do hlasové schránky."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"číst všechny hlasové zprávy"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Umožňuje aplikaci číst všechny vaše hlasové zprávy."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"změna oprávnění prohlížeče poskytovat informace o zeměpisné poloze"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Umožňuje aplikaci upravit oprávnění funkce geolokace v prohlížeči. Škodlivé aplikace toho mohou využít k odeslání údajů o poloze na libovolné webové stránky."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ověřit balíčky"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Umožňuje aplikaci naslouchat změnám ve stavu důvěryhodnosti."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Poskytování zástupce důvěryhodnosti"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Umožňuje aplikaci poskytnout zástupce důvěryhodnosti."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Spustit nabídku nastavení agenta důvěryhodnosti"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umožňuje aplikaci spustit aktivitu, která změní chování agenta důvěryhodnosti."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vázat se na službu zástupce důvěryhodnosti"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Umožňuje aplikaci vázat se na službu zástupce důvěryhodnosti."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interakce se systémem aktualizací a obnovení"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string> <string name="item_is_selected" msgid="949687401682476608">"Vybrána položka <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Číslice <xliff:g id="KEY">%1$s</xliff:g> byla smazána"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Pracovní <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Použít Uzamčení v aplikaci?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Funkce Uzamčení v aplikaci uzamkne obrazovku na jedinou aplikaci.\n\nChcete-li tento režim opustit, stiskněte a podržte tlačítko posledních aplikací $."</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"SPUSTIT"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Spustit Uzamčení v aplikaci"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Opustit Uzamčení v aplikaci"</string> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 895c98e..9ddbf87 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Tillader, at indehaveren kan binde en baggrunds grænseflade på øverste niveau. Dette bør aldrig være nødvendigt for almindelige apps."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"oprette binding til en tjeneste til stemmeinteraktion"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Tillader, at brugeren opretter en binding til det øverste niveau af grænsefladen i en tjeneste til stemmeinteraktion. Dette bør aldrig være nødvendigt for almindelige apps."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrer nøglesætninger til stemmeregistrering"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Tillader, at brugeren kan administrere nøglesætningerne til stemmeregistrering af kommandoord. Dette bør aldrig være nødvendigt for almindelige apps."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind til en ekstern skærm"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Tillader, at brugeren kan foretage en binding til grænsefladens øverste niveau på en ekstern skærm. Bør aldrig være nødvendigt til almindelige apps."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"forpligt til en widgettjeneste"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"optage lyd"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Tillader, at appen kan optage lyd med mikrofonen. Med denne tilladelse kan appen til enhver tid optage lyd uden din bekræftelse."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikation"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string> <string name="permlab_camera" msgid="3616391919559751192">"tage billeder og optage video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Tillader, at appen kan tage billeder og videoer med kameraet. Med denne tilladelse kan appen til enhver tid bruge kameraet uden din bekræftelse."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiver sendelysdioden, når kameraet er i brug"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillader, at appen kan indstille en alarm i en installeret alarmapp. Nogle alarmapps har muligvis ikke denne funktion."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"tilføje telefonsvarer"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Tillader, at appen kan tilføje beskeder på din telefonsvarer."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"læs alle talebeskeder"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Tillader, at appen kan læse alle dine talebeskeder"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"skifte tilladelser til geografisk placering i Browser"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Tillader, at appen kan ændre browserens tilladelser angående geografisk placering. Ondsindede apps kan benytte dette til at sende oplysninger om placering til vilkårlige websites."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekræft pakker"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Tillader, at en applikation registrerer ændringer i trust-tilstand."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Angiv en tillidsagent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillader, at en applikation angiver en tillidsagent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Åbn indstillingsmenuen for tillidsagenten"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Giver en applikation tilladelse til at starte en aktivitet, som ændrer adfærden for tillidsagenten."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Knytte sig til en trust agent-tjeneste"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Tillader, at en applikation knytter sig til en trust agent-tjeneste."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interager med opdaterings- og gendannelsessystemet"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Vælg år"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> – arbejde"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruge Lås-til-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Med Lås-til-app låses skærmen, så den kun viser én app.\n\nHvis du vil afslutte denne tilstand, skal du trykke på knappen for seneste apps og holde fingeren nede $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lås-til-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Afslut Lås-til-app"</string> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 35116c3..6f08ba2 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ermöglicht dem Halter, sich an die Oberfläche eines Hintergrunds auf oberster Ebene zu binden. Sollte nie für normale Apps benötigt werden."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"An einen Sprachinteraktionsdienst binden"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ermöglicht dem Inhaber, sich an die Oberfläche eines Sprachinteraktionsdienstes auf oberster Ebene zu binden. Für normale Apps sollte dies nie erforderlich sein."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Suchbegriffe für die Sprachsuche verwalten"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ermöglicht dem Inhaber die Verwaltung von Suchbegriffen für die Hotword-Erkennung bei der Sprachbedienung. Für normale Apps sollte dies nie erforderlich sein."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"An Remote-Display binden"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ermöglicht dem Halter, sich an die Oberfläche eines Remote-Displays auf oberster Ebene zu binden. Sollte für normale Apps nie benötigt werden."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"An einen Widget-Dienst binden"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ermöglicht der App, Ton mithilfe des Mikrofons aufzunehmen. Die Berechtigung erlaubt der App, Tonaufnahmen jederzeit und ohne Ihre Bestätigung durchzuführen."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-Kommunikation"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string> <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Ermöglicht der App, Bilder und Videos mit der Kamera aufzunehmen. Die Berechtigung erlaubt der App, die Kamera jederzeit und ohne Ihre Bestätigung zu nutzen."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"LED-Anzeige für Übertragung bei Kameranutzung deaktivieren"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Ermöglicht der App, einen Alarm in einer installierten Wecker-App einzurichten. Einige Wecker-Apps implementieren diese Funktion möglicherweise nicht."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"Mailbox-Nachrichten hinzufügen"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ermöglicht der App, Nachrichten zu Ihrem Mailbox-Posteingang hinzuzufügen"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Alle Mailboxnachrichten abrufen"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ermöglicht der App das Abrufen aller Ihrer Mailboxnachrichten"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Geolokalisierungsberechtigungen des Browsers ändern"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ermöglicht der App, die Geolokalisierungsberechtigungen des Browsers zu ändern. Schädliche Apps können so Standortinformationen an beliebige Websites senden."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Pakete überprüfen"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ermöglicht einer App die Überwachungen von Änderungen des Trust-Status"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust Agent bereitstellen"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ermöglich die Bereitstellung eines Trust Agents durch eine App"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Einstellungsmenü des Trust Agents starten"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ermöglicht einer App das Starten einer Aktivität, die das Verhalten des Trust Agents ändert"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"An Trust Agent-Service anbinden"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ermöglicht einer App die Anbindung an einen Trust Agent-Service"</string> <string name="permlab_recovery" msgid="3157024487744125846">"Mit Update- und Wiederherstellungssystem interagieren"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Jahr auswählen"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ausgewählt"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> gelöscht"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (geschäftlich)"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"\"Auf App einschränken\" verwenden?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Mit \"Auf App einschränken\" wird das Display in einer einzelnen App gesperrt.\n\nZum Beenden der Sperre berühren und halten Sie die Schaltfläche für die letzten Apps $."</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"Nein"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"Starten"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"\"Auf App einschränken\" starten"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"\"Auf App einschränken\" beenden"</string> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 0c50bb5..0539bc9 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας ταπετσαρίας. Δεν απαιτείται για συνήθεις εφαρμογές."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"σύνδεση σε παράγοντα φωνητικής αλληλεπίδρασης"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Επιτρέπει στον κάτοχο τη σύνδεση στη διεπαφή ανωτάτου επιπέδου μιας υπηρεσίας φωνητικής αλληλεπίδρασης. Δεν απαιτείται για κανονικές εφαρμογές."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"διαχείριση φωνητικών φράσεων-κλειδιών"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Δίνει στον κάτοχο τη δυνατότητα να διαχειριστεί τις φράσεις-κλειδιά για την ανίχνευση φωνητικών λέξεων-κλειδιών. Δεν απαιτείται για τις συνήθεις εφαρμογές."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"μεταφορά σε μια απομακρυσμένη οθόνη"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας απομακρυσμένης οθόνης. Δεν απαιτείται ποτέ για κανονικές εφαρμογές."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"δέσμευση σε υπηρεσία γραφικών στοιχείων"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Επιτρέπει στην εφαρμογή την εγγραφή ήχου με το μικρόφωνο. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να εγγράφει ήχο ανά πάσα στιγμή χωρίς την έγκρισή σας."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"επικοινωνία με κάρτα sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string> <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Επιτρέπει στην εφαρμογή τη λήψη φωτογραφιών και βίντεο με τη φωτογραφική μηχανή. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να χρησιμοποιεί τη φωτογραφική μηχανή ανά πάσα στιγμή χωρίς την έγκρισή σας."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"απενεργοποίηση ένδειξης LED μετάδοσης όταν χρησιμοποιείται η φωτογραφική μηχανή"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Επιτρέπει στην εφαρμογή τη ρύθμιση μιας ειδοποίησης σε μια εγκατεστημένη εφαρμογή ξυπνητηριού. Ορισμένες εφαρμογές ξυπνητηριού ενδέχεται να μην μπορούν να ενσωματώσουν αυτήν τη λειτουργία."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"προσθήκη τηλεφωνητή"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Επιτρέπει στην εφαρμογή να προσθέτει μηνύματα στα εισερχόμενα του αυτόματου τηλεφωνητή σας."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ανάγνωση όλων των μηνυμάτων του αυτόματου τηλεφωνητή"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Επιτρέπει στην εφαρμογή να διαβάσει όλα τα μηνύματα του αυτόματου τηλεφωνητή σας."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"τροποποίηση δικαιωμάτων γεωγραφικής θέσης του Προγράμματος περιήγησης"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Επιτρέπει στην εφαρμογή την τροποποίηση των αδειών γεωτοποθεσίας του Προγράμματος περιήγησης. Τυχόν κακόβουλες εφαρμογές ενδέχεται να το χρησιμοποιήσουν για να επιτρέψουν την αποστολή πληροφοριών τοποθεσίας σε αυθαίρετους ιστότοπους."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"επαλήθευση πακέτων"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Επιτρέπει σε μια εφαρμογή να αντιλαμβάνεται τις αλλαγές στην κατάσταση εμπιστοσύνης."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Παράσχετε έναν αξιόπιστο αντιπρόσωπο."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Επιτρέπει σε μια εφαρμογή να προσφέρει έναν αξιόπιστο αντιπρόσωπο."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Εκκίνηση μενού ρυθμίσεων αξιόπιστου αντιπροσώπου."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Επιτρέπει σε μια εφαρμογή να εκκινεί μια ενέργεια που αλλάζει τη συμπεριφορά του αξιόπιστου αντιπροσώπου."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Σύνδεση σε υπηρεσία trust agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Επιτρέπει σε μια εφαρμογή να συνδεθεί σε μια υπηρεσία trust agents."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Αλληλεπίδραση με το σύστημα ενημέρωσης και ανάκτησης"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Επιλογή έτους"</string> <string name="item_is_selected" msgid="949687401682476608">"Επιλέχτηκε το στοιχείο <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> διαγράφηκε"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Εργασία <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Χρήση λειτουργίας lock-to-app;"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Η λειτουργία Lock-to-app κλειδώνει την οθόνη σε μία μόνο εφαρμογή.\n\nΓια έξοδο πατήστε παρατεταμένα το κουμπί των πρόσφατων εφαρμογών $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"ΟΧΙ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ΕΝΑΡΞΗ"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Έναρξη Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Έξοδος από Lock-to-app"</string> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 32141c3..18e11df 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for normal applications."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind to a voice interactor"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Allows the holder to bind to the top-level interface of a voice interaction service. Should never be needed for normal apps."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"manage voice key phrases"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Allows the holder to manage the key phrases for voice hotword detection. Should never be needed for normal apps."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind to a remote display"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Allows the holder to bind to the top-level interface of a remote display. Should never be needed for normal apps."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind to a widget service"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"record audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Allows the app to record audio with the microphone. This permission allows the app to record audio at any time without your confirmation."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM communication"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_camera" msgid="3616391919559751192">"take pictures and videos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Allows the app to take pictures and videos with the camera. This permission allows the app to use the camera at any time without your confirmation."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disable transmit indicator LED when camera is in use"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"add voicemail"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Allows the app to add messages to your voicemail inbox."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"read all voicemail"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Allows the app to read all your voicemails."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modify Browser geo-location permissions"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verify packages"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Allows an application to listen for changes in trust state."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Provide a trust agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Allows an application to provide a trust agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lunch trust agent settings menu."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Allows an application to lunch an activity that changes the trust agent behaviour."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind to a trust agent service"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Allows an application to bind to a trust agent service."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interact with update and recovery system"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Select year"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string> </resources> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 32141c3..18e11df 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for normal applications."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind to a voice interactor"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Allows the holder to bind to the top-level interface of a voice interaction service. Should never be needed for normal apps."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"manage voice key phrases"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Allows the holder to manage the key phrases for voice hotword detection. Should never be needed for normal apps."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind to a remote display"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Allows the holder to bind to the top-level interface of a remote display. Should never be needed for normal apps."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind to a widget service"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"record audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Allows the app to record audio with the microphone. This permission allows the app to record audio at any time without your confirmation."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM communication"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_camera" msgid="3616391919559751192">"take pictures and videos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Allows the app to take pictures and videos with the camera. This permission allows the app to use the camera at any time without your confirmation."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disable transmit indicator LED when camera is in use"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"add voicemail"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Allows the app to add messages to your voicemail inbox."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"read all voicemail"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Allows the app to read all your voicemails."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modify Browser geo-location permissions"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verify packages"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Allows an application to listen for changes in trust state."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Provide a trust agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Allows an application to provide a trust agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lunch trust agent settings menu."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Allows an application to lunch an activity that changes the trust agent behaviour."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind to a trust agent service"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Allows an application to bind to a trust agent service."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interact with update and recovery system"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Select year"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 3eb0666..2451d62 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite al propietario vincularse a la interfaz de nivel superior de un fondo de pantalla. Las aplicaciones normales no deben utilizar este permiso."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"vincular con un servicio de interacción por voz"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite vincular con la interfaz de nivel superior de un servicio de interacción por voz. Las aplicaciones normales no deberían necesitar este permiso."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrar frases clave de voz"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite administrar las frases clave para detección de palabras activas de voz. Las aplicaciones normales no deberían necesitar este permiso."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vincular a una pantalla remota"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite al propietario vincularse a la interfaz de nivel superior de una pantalla remota. Las aplicaciones normales no deberían necesitar este permiso."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincular a un servicio de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que la aplicación grabe audio con el micrófono. La aplicación puede utilizar este permiso para grabar audio en cualquier momento sin tener tu confirmación."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"Comunicación con tarjeta SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string> <string name="permlab_camera" msgid="3616391919559751192">"tomar fotografías y grabar videos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permite que la aplicación saque fotos o grabe videos con la cámara. Este permiso autoriza a la aplicación a utilizar la cámara en cualquier momento sin tu confirmación."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Inhabilitar el indicador LED de transmisión mientras se utiliza la cámara"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Consultar todos los mensajes del buzón de voz"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que la aplicación consulte todos los mensajes del buzón de voz."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Verificar paquetes"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que una aplicación detecte cambios en el estado de confianza."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agente de confianza"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que una aplicación proporcione un agente de confianza."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Iniciar menú de configuración de agente de confianza"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que una aplicación inicie una actividad que modifica el comportamiento del agente de confianza."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vincular con un servicio de agente de confianza"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que una aplicación se vincule con un servicio de agente de confianza."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interaccionar con el sistema de recuperación y las actualizaciones"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> borrado"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 126d20d..0bc3a92 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite enlazar con la interfaz de nivel superior de un fondo de pantalla. Las aplicaciones normales no deberían necesitar este permiso."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"enlazar con un servicio de interacción de voz"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite enlazar con la interfaz de nivel superior de un servicio de interacción de voz. Las aplicaciones normales no deberían necesitar este permiso."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrar frases clave de voz"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite administrar las frases clave para la detección de palabras activas. Las aplicaciones normales no deberían necesitar este permiso."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"enlazar a una pantalla remota"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite enlazar con la interfaz de nivel superior de una pantalla remota. Las aplicaciones normales no deberían necesitar este permiso."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"enlazar con un servicio de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que la aplicación modifique la configuración de audio global (por ejemplo, el volumen y el altavoz de salida)."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar sonido"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que la aplicación grabe audio con el micrófono. La aplicación puede utilizar este permiso para grabar audio en cualquier momento sin tener la confirmación del usuario."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicación con la tarjeta SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string> <string name="permlab_camera" msgid="3616391919559751192">"realizar fotografías y vídeos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permite que la aplicación haga fotos o grabe vídeos con la cámara. Este permiso autoriza a la aplicación a utilizar la cámara en cualquier momento sin tu confirmación."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"inhabilitar el indicador LED de transmisión mientras se utiliza la cámara"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de reloj instalada. Es posible que algunas aplicaciones de reloj no incluyan esta función."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"añadir buzón de voz"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación añada mensajes a la bandeja de entrada del buzón de voz."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"consultar todos los mensajes de voz"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que la aplicación consulte todos tus mensajes de voz."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificar los permisos de ubicación geográfica del navegador"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones malintencionadas pueden usar este permiso para autorizar el envío de información sobre la ubicación a sitios web arbitrarios."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar paquetes"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que una aplicación detecte cambios en el estado de confianza."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agente de confianza."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que una aplicación proporcione un agente de confianza."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Abre el menú de ajustes del agente de confianza."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que una aplicación inicie una actividad que cambie el comportamiento del agente de confianza."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Enlazar con un servicio de agente de confianza"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite a una aplicación enlazar con un servicio de agente de confianza."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interactuar con el sistema de recuperación y las actualizaciones"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"¿Usar bloqueo de aplicación?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"El bloqueo de aplicación bloquea la pantalla en una sola aplicación.\n\nPara salir, mantén pulsado el botón de aplicaciones recientes $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar bloqueo de aplicación"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Salir de bloqueo de aplicación"</string> </resources> diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml index 71a19f0..4012f09 100644 --- a/core/res/res/values-et-rEE/strings.xml +++ b/core/res/res/values-et-rEE/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lubab omanikul siduda taustapildi ülataseme liidesega. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"seo häälinteraktsiooniga"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Lubab omanikul siduda häälinteraktsiooni teenuse ülataseme liidesega. Pole kunagi vajalik tavaliste rakenduste puhul."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"häältuvastuse võtmefraaside haldus"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Võimaldab omanikul hallata häältuvastuse otsetee sõna võtmefraase. Seda ei tohiks tavaliste rakenduste puhul kunagi vaja olla."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"kaugekraaniga sidumine"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lubab omanikul siduda rakenduse kaugekraani ülataseme liidesega. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vidinateenusega sidumine"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Võimaldab rakendusel muuta üldiseid heliseadeid, näiteks helitugevust ja seda, millist kõlarit kasutatakse väljundiks."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"salvesta heli"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Võimaldab rakendusel salvestada mikrofoniga heli. See luba võimaldab rakendusel salvestada heli igal ajal ilma teie kinnituseta."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"side SIM-kaardiga"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string> <string name="permlab_camera" msgid="3616391919559751192">"piltide ja videote tegemine"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Võimaldab rakendusel teha kaameraga pilte ja videoid. See luba võimaldab rakendusel kasutada kaamerat mis tahes ajal teie kinnituseta."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"keela kaamera kasutamisel näidikutule kasutamine"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Võimaldab rakendusel seada installitud äratuskellarakenduses äratuse. Mõned äratuskellarakendused ei pruugi seda funktsiooni juurutada."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"lisa kõneposti"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Võimaldab rakendusel lisada sõnumeid teie kõneposti postkasti."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"kogu kõneposti lugemine"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Võimaldab rakendusel kogu kõneposti lugeda."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Brauseri geolokatsiooniõiguste muutmine"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Võimaldab rakendusel muuta brauseri geolokatsiooniõigusi. Pahatahtlikud rakendused võivad seda kasutada asukohateabe saatmise lubamiseks suvalistele veebisaitidele."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakettide kinnitamine"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Lubab rakendusel tuvastada muudatusi usaldusväärses olekus."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Usaldusväärse agendi esitamine."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Võimaldab rakendusel esitada usaldusväärset agenti."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Käivita usaldusväärse agendi seadete menüü."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Võimaldab rakendusel käivitada tegevuse, mis muudab usaldusväärse agendi käitumist."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Usaldusväärse agendi teenusega sidumine"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Lubab rakendusel ennast siduda usaldusväärse agendi teenusega."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Suhtlemine värskenduse ja taastesüsteemiga"</string> @@ -1717,4 +1725,12 @@ <string name="select_year" msgid="7952052866994196170">"Aasta valimine"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valitud"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> on kustutatud"</string> + <!-- no translation found for managed_profile_label_badge (2355652472854327647) --> + <skip /> + <string name="lock_to_app_title" msgid="5895142291937470019">"Kas soovite kasutada rakendusele lukustamist?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Rakendusele lukustamise funktsioon lukustab kuva ühele rakendusele.\n\nVäljumiseks hoidke hiljutiste rakenduste nuppu $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"EI"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"KÄIVITA"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Käivita rakendusele lukustamine"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Välju rakendusele lukustamisest"</string> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 8cf01d9..30136e76 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"به دارنده اجازه میدهد تا به رابط سطح بالای تصویر زمینه متصل شود. برنامههای معمولی هرگز به این ویژگی نیاز ندارند."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"مقید بودن به سرویس تعامل صوتی"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"به دارنده امکان میدهد به واسط سطح بالای سرویس تعامل صوتی مقید باشد. برای برنامههای عادی هرگز نباید لازم باشد."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"مدیریت عبارات کلیدی صوتی"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"به دارنده امکان میدهد عبارات کلیدی برای شناسایی کلیدگفته صوتی را مدیریت کند. هرگز نباید برای برنامههای معمولی مورد نیاز باشد."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"اتصال به نمایشگر راه دور"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"به دارنده امکان میدهد تا به رابط سطح بالای نمایشگر راه دور وصل شود. نباید هرگز برای برنامههای عادی لازم باشد."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"اتصال به یک سرویس ابزارک"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"به برنامه امکان میدهد تنظیمات صوتی کلی مانند میزان صدا و بلندگوی مورد استفاده برای پخش صدا را اصلاح کند."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ضبط صدا"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"به برنامه اجازه میدهد صدا را با میکروفن ضبط کند. این مجوز به برنامه اجازه میدهد صدا را در هر زمان که بخواهید بدون تأیید شما ضبط کند."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"ارتباطات سیم کارت"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"به برنامه اجازه ارسال دستورات به سیم کارت را میدهد. این بسیار خطرناک است."</string> <string name="permlab_camera" msgid="3616391919559751192">"عکسبرداری و فیلمبرداری"</string> <string name="permdesc_camera" msgid="8497216524735535009">"به برنامه اجازه میدهد با دوربین به عکسبرداری و فیلمبرداری بپردازد. این مجوز به برنامه اجازه میدهد از دوربین در هر زمانی بدون تأیید شما استفاده کند."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"LED نشانگر انتقال داده، هنگام استفاده از دوربین غیرفعال شود"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"به برنامه اجازه میدهد تا هشداری را در برنامه ساعت زنگدار نصب شده تنظیم کند. برخی از برنامههای ساعت زنگدار نمیتوانند این ویژگی را اعمال کنند."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"افزودن پست صوتی"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"به برنامه اجازه میدهد تا پیامها را به صندوق دریافت پست صوتی شما اضافه کند."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"خواندن کل پست صوتی"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"به برنامه اجازه میدهد همه پستهای صوتیتان را بخواند."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"تغییر مجوزهای مکان جغرافیایی مرورگر"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"به برنامه اجازه میدهد تا مجوزهای جغرافیایی مرورگر را تغییر دهد. برنامههای مخرب میتوانند از آن استفاده کنند تا اطلاعات موقعیت مکانی را به سایتهای وب کتابخانه بفرستند."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"تأیید بستهها"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"به یک برنامه کاربردی برای گوش دادن به تغییرات در trust اجازه میدهد."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"یک عامل مورد اعتماد فراهم میآورد."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"به برنامه امکان میدهد یک عامل مورد اعتماد فراهم آورد."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"منوی تنظیمات نماینده امانی را راهاندازی کنید."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"به برنامه اجازه میدهد تا فعالیتی را راهاندازی کند که رفتار نماینده امانی را تغییر میدهد."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"اتصال به یک سرویس trust agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"به یک برنامه کاربردی برای اتصال به یک سرویس trust agent اجازه میدهد."</string> <string name="permlab_recovery" msgid="3157024487744125846">"تعامل با سیستم بهروزرسانی و بازیابی"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"انتخاب سال"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> انتخاب شد"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> حذف شد"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> محل کار"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"از «قفل برنامه» استفاده شود؟"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"«قفل برنامه» نمایشگر را در یک برنامه قفل میکند.\n\nبرای خروج، دکمه برنامههای جدید $ را فشار داده و نگهدارید"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"خیر"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"شروع"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"راهاندازی «قفل برنامه»"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج از «قفل برنامه»"</string> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 2852844..f833733 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Antaa sovelluksen sitoutua taustakuvan ylätason käyttöliittymään. Ei tavallisten sovellusten käyttöön."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"puheohjauspalveluun sitominen"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Antaa sovelluksen luoda sidoksen puheohjauspalvelun ylätason rajapintaan. Ei tavallisten sovelluksien käyttöön."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"puhuttujen ilmausten hallinta"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Sallii ilmausten hallinnan puhuttujen toimintosanojen tunnistusta varten. Ei tavallisten sovellusten käyttöön."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"etänäyttöön sitoutuminen"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Antaa sovelluksen sitoutua etänäytön ylemmän tason käyttöliittymään. Ei tavallisten sovelluksien käyttöön."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"sitoudu widget-palveluun"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"tallentaa ääntä"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Antaa sovelluksen tallentaa ääntä mikrofonin avulla. Sovellus voi tallentaa ääntä milloin tahansa pyytämättä sinulta lupaa."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-viestintä"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string> <string name="permlab_camera" msgid="3616391919559751192">"ota kuvia ja videoita"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Antaa sovelluksen ottaa kuvia ja kuvata videoita kameralla. Sovellus voi käyttää kameraa milloin tahansa ilman lupaasi."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"poista lähetyksen merkkivalo käytöstä, kun kameraa käytetään"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Antaa sovelluksen asettaa hälytyksen sisäiseen herätyskellosovellukseen. Jotkin herätyskellosovellukset eivät välttämättä käytä tätä ominaisuutta."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"lisää vastaajaviesti"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Antaa sovelluksen lisätä viestejä saapuneisiin vastaajaviesteihin."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"kaikkien vastaajaviestien luku"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Antaa sovelluksen lukea kaikki vastaajaviestisi."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"selaimen maantieteellisen sijainnin lupien muokkaaminen"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Antaa sovelluksen muokata Selaimen maantieteellisen sijainnin lupia. Haitalliset sovellukset voivat sallia tällä sijaintitietojen lähettämisen mielivaltaisiin sivustoihin."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vahvista paketteja"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Antaa sovelluksen seurata luottamuksen tilamuutoksia."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Luotettavan tahon tarjoaminen"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Antaa sovelluksen tarjota luotettavan tahon."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Käynnistä luotettavan tahon asetusvalikko."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Sallii sovelluksen käynnistää toiminnon, joka muuttaa luotettavan tahon toimintaa."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Luotettavaan tahoon sitoutuminen"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Antaa sovelluksen sitoutua luotettavaan tahoon."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Vuorovaikutus päivitys- ja palautusjärjestelmän kanssa"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Valitse vuosi"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valittu"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> poistettiin"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (työ)"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index f3e6322..5ffa2e7 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un fond d\'écran. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"s\'associer à un service d\'interaction vocale"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet à l\'application de s\'associer à l\'interface de niveau supérieur d\'un service d\'interaction vocale. Ne devrait pas être nécessaire pour les applications standards."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gérer les phrases clés vocales"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permet à l\'utilisateur de gérer les phrases clés pour la détection de mots clés vocaux. Cette option ne devrait jamais être nécessaire pour les applications standards."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"lier à un écran distant"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un écran distant. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"s\'associer à un service de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrer fichier audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet à l\'application d\'enregistrer des contenus audio à l\'aide du microphone. Cette autorisation lui donne la possibilité d\'enregistrer du contenu audio à tout moment sans votre consentement."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"Communication avec la carte SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string> <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et filmer des vidéos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permet à l\'application de prendre des photos et de filmer des vidéos avec l\'appareil photo. Cette autorisation lui permet d\'utiliser l\'appareil photo à tout moment sans votre consentement."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"désactiver l\'indicateur d\'émission LED lorsque la caméra est en cours d\'utilisation"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet à l\'application de régler la sonnerie d\'une fonction de réveil installée sur votre appareil. Cette fonctionnalité n\'est pas compatible avec toutes les applications de réveils."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ajouter des messages vocaux"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet à l\'application d\'ajouter des messages à votre messagerie vocale."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accéder à tous les messages vocaux"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet à l\'application d\'accéder à tous vos messages vocaux."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifier les autorisations de géolocalisation du navigateur"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet à l\'application de modifier les autorisations de géolocalisation du navigateur. Des applications malveillantes peuvent exploiter cette fonctionnalité pour permettre l\'envoi de données de localisation à des sites Web arbitraires."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vérifier les paquets"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permet à une application de détecter les modifications de l\'état de confiance."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fournir un agent de confiance."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permet à une application de fournir un agent de confiance."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancer le menu des paramètres de l\'agent de confiance."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet à une application de lancer une activité qui modifie le comportement de l\'agent de confiance."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Lier à un service d\'agent de confiance"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permet à une application de se lier à un service d\'agent de confiance."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interagir avec le système de récupération et de mise à jour"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Sélectionnez une année"</string> <string name="item_is_selected" msgid="949687401682476608">"« <xliff:g id="ITEM">%1$s</xliff:g> » a été sélectionné"</string> <string name="deleted_key" msgid="7659477886625566590">"« <xliff:g id="KEY">%1$s</xliff:g> » a été supprimé"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index cfb37c2..e2c7196 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un fond d\'écran. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"s\'associer à un service d\'interaction vocale"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet à l\'application de s\'associer à l\'interface de niveau supérieur d\'un service d\'interaction vocale. Ne devrait pas être nécessaire pour les applications standards."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gérer les expressions clés vocales"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permettre à l\'application autorisée de gérer les expressions clés pour la détection de mots clés vocaux. Ne devrait jamais être nécessaire pour les applications standards."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"s\'associer à un écran à distance"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permettre à l\'application autorisée de s\'associer à l\'interface de niveau supérieur d\'un écran à distance. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"associer à un service widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrer des fichiers audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet à l\'application d\'enregistrer des contenus audio à l\'aide du microphone. Cette autorisation lui donne la possibilité d\'enregistrer du contenu audio à tout moment sans votre consentement."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"Communication avec la carte SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string> <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et enregistrer des vidéos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permet à l\'application de prendre des photos et de filmer des vidéos avec l\'appareil photo. Cette autorisation lui permet d\'utiliser l\'appareil photo à tout moment sans votre consentement."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"désactiver l\'indicateur d\'émission LED lorsque la caméra est en cours d\'utilisation"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet à l\'application de régler la sonnerie d\'un réveil installé. Cette fonctionnalité n\'est pas disponible sur tous les réveils."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ajouter un message vocal"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet à l\'application d\'ajouter des messages à votre messagerie vocale."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accéder à tous les messages vocaux"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet à l\'application d\'accéder à tous vos messages vocaux."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifier les autorisations de géolocalisation du navigateur"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet à l\'application de modifier les autorisations de géolocalisation du navigateur. Des applications malveillantes peuvent exploiter cette fonctionnalité pour permettre l\'envoi de données de localisation à des sites Web arbitraires."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vérifier les packages"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permettre à une application de détecter les modifications de l\'état de confiance."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fournir un agent de confiance"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permettre à une application de fournir un agent de confiance"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancer le menu des paramètres de l\'agent de confiance"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet à une application de lancer une activité qui modifie le comportement de l\'agent de confiance."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"S\'associer à un service d\'agent de confiance"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permettre à une application de s\'associer à un service d\'agent de confiance."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interagir avec le système de récupération et de mise à jour"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Sélectionner une année"</string> <string name="item_is_selected" msgid="949687401682476608">"\"<xliff:g id="ITEM">%1$s</xliff:g>\" sélectionné"</string> <string name="deleted_key" msgid="7659477886625566590">"\"<xliff:g id="KEY">%1$s</xliff:g>\" supprimé"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index bf22360..8cbb095 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"धारक को किसी वॉलपेपर के शीर्ष-स्तर इंटरफ़ेस से आबद्ध होने देता है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"किसी ध्वनि सहभागिताकर्ता से आबद्ध हों"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"धारक को किसी ध्वनि सहभागिता सेवा के शीर्ष-स्तर के इंटरफ़ेस से आबद्ध होने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ध्वनि कीफ़्रेज़ प्रबंधित करें"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"धारक को ध्वनि हॉटवर्ड पहचान के लिए कीफ़्रेज़ प्रबंधित करने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"रिमोट डिस्प्ले से आबद्ध करें"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"धारक को किसी रिमोट डिस्प्ले के शीर्ष-स्तरीय इंटरफ़ेस से आबद्ध होने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"किसी विजेट सेवा से आबद्ध करें"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ऐप्स को वैश्विक ऑडियो सेटिंग, जैसे वॉल्यूम और कौन-सा स्पीकर आउटपुट के लिए उपयोग किया गया, संशोधित करने देता है."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ऑडियो रिकॉर्ड करें"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"ऐप्स को माइक्रोफ़ोन द्वारा ऑडियो रिकार्ड करने देता है. यह अनुमति ऐप्स को आपकी पुष्टि के बिना किसी भी समय ऑडियो रिकार्ड करने देती है."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"सिम संचार"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"ऐप्स को सिम में आदेश भेजने देती है. यह बहुत ही खतरनाक है."</string> <string name="permlab_camera" msgid="3616391919559751192">"चित्र और वीडियो लें"</string> <string name="permdesc_camera" msgid="8497216524735535009">"ऐप्स को कैमरे से चित्र और वीडियो लेने देता है. यह अनुमति ऐप्स को किसी भी समय आपकी पुष्टि के बिना कैमरे का उपयोग करने देती है."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"कैमरा उपयोग में होने पर संचारण संकेतक LED अक्षम करें"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"ऐप्स को इंस्टॉल किए गए अलार्म घड़ी ऐप्स में अलार्म सेट करने देता है. हो सकता है कुछ अलार्म घड़ी ऐप्स में यह सुविधा न हो."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ध्वनिमेल जोड़ें"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ऐप्स को आपके ध्वनिमेल इनबॉक्स में संदेश जोड़ने देता है."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"सभी ध्वनिमेल पढ़ें"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ऐप्स को आपके सभी ध्वनिमेल पढ़ने देती है."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउज़र भौगोलिक-स्थान अनुमतियों को बदलें"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ऐप्स को ब्राउज़र के भौगोलिक-स्थान की अनुमतियां संशोधित करने देता है. दुर्भावनापूर्ण ऐप्स इसका उपयोग एकपक्षीय वेबसाइट को स्थान जानकारी भेजने में कर सकते हैं."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"पैकेज सत्यापित करें"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"किसी एप्लिकेशन को ट्रस्ट स्थिति के बदलावों को सुनने की अनुमति देती है."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"विश्वसनीय एजेंट प्रदान करें."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"एप्लिकेशन को विश्वसनीय एजेंट प्रदान करने देती है."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"विश्वस्त एजेंट सेटिंग मेनू लॉन्च करें."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"एप्लिकेशन को ऐसी गतिविधि लॉन्च करने की अनुमति मिलती है जो विश्वस्त एजेंट के व्यवहार में बदलाव लाती है."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ट्रस्ट एजेंट सेवा से आबद्ध करना"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"किसी एप्लिकेशन को ट्रस्ट एजेंट सेवा से आबद्ध करने की अनुमति देती है."</string> <string name="permlab_recovery" msgid="3157024487744125846">"अपडेट और पुनर्प्राप्ति सिस्टम के साथ सहभागिता करें"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"वर्ष चुनें"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> चयनित"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> को हटा दिया गया"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"कार्यस्थल का <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"ऐप्स-पर-लॉक करें का उपयोग करें?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"ऐप्स-पर-लॉक करें, डिस्प्ले को किसी एकल ऐप्स में लॉक कर देता है.\n\nबाहर निकलने के लिए हाल ही के ऐप्स बटन $ को दबाए रखें"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"नहीं"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"प्रारंभ करें"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"ऐप्स-पर-लॉक करें प्रारंभ करें"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"ऐप्स-पर-लॉक करें से बाहर निकलें"</string> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index c5da5d8..a427eb3 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Nositelju omogućuje povezivanje sa sučeljem pozadinske slike najviše razine. Ne bi smjelo biti potrebno za normalne aplikacije."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"povezivanje s uslugom glasovne interakcije"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Vlasniku omogućuje povezivanje sa sučeljem najviše razine usluge glasovne interakcije. Nije potrebno za normalne aplikacije."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"upravljanje glasovnim ključnim frazama"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Omogućuje korisniku upravljanje ključnim frazama za otkrivanje glasovnih pokretača značajke. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vezanje uz udaljeni zaslon"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Nositelju omogućuje vezanje uza sučelje najviše razine udaljenog zaslona. Ne bi smjelo biti potrebno za normalne aplikacije."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vezanje na uslugu widgeta"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Aplikaciji omogućuje izmjenu globalnih postavki zvuka, primjerice glasnoće i zvučnika koji se upotrebljava za izlaz."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"snimanje zvuka"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Aplikaciji omogućuje snimanje zvuka mikrofonom. Ta dozvola aplikaciji omogućuje snimanje zvuka u bilo kojem trenutku bez vašeg odobrenja."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikacija sa SIM-om"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string> <string name="permlab_camera" msgid="3616391919559751192">"snimi fotografije i videozapise"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Aplikaciji omogućuje snimanje slika i videozapisa fotoaparatom. Ta dozvola aplikaciji omogućuje upotrebu fotoaparata u bilo kojem trenutku bez vašeg odobrenja."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"onemogućavanje lampice pokazivača prijenosa kada je fotoaparat u upotrebi"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Omogućuje aplikaciji postavljanje alarma na instaliranoj aplikaciji budilici. Neke aplikacije budilice možda neće primijeniti tu značajku."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodaj govornu poštu"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Omogućuje aplikaciji da doda poruke u vašu govornu poštu."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"čitanje svih poruka u govornoj pošti"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Aplikaciji omogućuje čitanje svih vaših poruka u govornoj pošti."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"izmjena dozvola za geolociranje u pregledniku"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Omogućuje aplikaciji promjenu geolokacijskih dozvola preglednika. Zlonamjerne aplikacije mogu to upotrijebiti da bi dopustile slanje podataka o lokaciji nasumičnim web-lokacijama."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"provjeri pakete"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Omogućuje aplikaciji praćenje promjena pouzdanog stanja."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Pružanje agenta za pouzdanost."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Omogućuje aplikaciji pružanje agenta za pouzdanost."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Pokretanje izbornika postavki agenta za pouzdanost."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Aplikaciji omogućuje pokretanje aktivnosti koja mijenja ponašanje agenta za pouzdanost."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Povezivanje s uslugom pouzdanog predstavnika"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Omogućuje aplikaciji povezivanje s uslugom pouzdanog predstavnika."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interakcija s ažuriranjem i sustavom za oporavak"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Odaberite godinu"</string> <string name="item_is_selected" msgid="949687401682476608">"Odabrana je stavka <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Izbrisan je broj <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za posao"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 414941f..1fbfdef 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lehetővé teszi, hogy a tulajdonos kötelezővé tegye egy háttérkép legfelső szintű felületét. A normál alkalmazásoknak erre soha nincs szüksége."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"csatlakozás egy hangvezérlőhöz"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Lehetővé teszi a használó számára, hogy csatlakozzon egy hangvezérlő szolgáltatás legfelső szintű kezelőfelületéhez. A normál alkalmazásoknak erre soha nincs szükségük."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"hangalapú kulcskifejezések kezelése"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Az engedély birtokosa kezelheti a hangalapú hotwordök felismerésére szolgáló kulcskifejezéseket. Az átlagos alkalmazásoknak nem lehet rá szükségük."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"csatlakozás egy távoli kijelzőhöz"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lehetővé teszi a használó számára, hogy csatlakozzon egy távoli kijelző legfelső szintű kezelőfelületéhez. A normál alkalmazásoknak erre soha nincs szükségük."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"csatlakozás modulszolgáltatáshoz"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Lehetővé teszi az alkalmazás számára az általános hangbeállítások, például a hangerő és a használni kívánt kimeneti hangszóró módosítását."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"hanganyag rögzítése"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Lehetővé teszi az alkalmazás számára a mikrofonnal való hangfelvételt.Az engedéllyel rendelkező alkalmazás az Ön jóváhagyása nélkül, bármikor rögzíthet hanganyagot."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikáció"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string> <string name="permlab_camera" msgid="3616391919559751192">"fotók és videók készítése"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Lehetővé teszi az alkalmazás számára, hogy a fényképezőgéppel fotókat és videókat készítsen. Az engedéllyel rendelkező alkalmazás bármikor, az Ön jóváhagyása nélkül használhatja a fényképezőgépet."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"átviteljelző LED letiltása, ha a kamera használatban van"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Lehetővé teszi az alkalmazás számára, hogy ébresztőt állítson be egy telepített ébresztőóra alkalmazásban. Egyes ilyen alkalmazásokban lehet, hogy nem működik ez a funkció."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"hangposta hozzáadása"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Lehetővé teszi az alkalmazás számára, hogy üzeneteket adjon hozzá bejövő hangpostájához."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"az összes hangüzenet olvasása"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Engedélyezi az alkalmazásnak az összes hangüzenet olvasását."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"a böngésző helymeghatározási engedélyeinek módosítása"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a böngésző helymeghatározási engedélyeit. Rosszindulatú alkalmazások ezt arra használhatják, hogy a helyére vonatkozó információkat küldjenek tetszőleges webhelyeknek."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"csomagok ellenőrzése"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Lehetővé teszi, hogy az alkalmazás figyelje a trust-állapot változásait."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust agent szoftver megadása"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Lehetővé teszi, hogy az alkalmazás megadjon egy trust agent szoftvert."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Trust agent szoftver beállításmenüjének indítása."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Lehetővé teszi az alkalmazások számára olyan tevékenységek indítását, amelyek megváltoztatják a trust agent szoftver viselkedését."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Csatlakozás egy trust agent szolgáltatáshoz"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Lehetővé teszi, hogy az alkalmazás egy trust agent szolgáltatáshoz csatlakozzon."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Kapcsolatfelvétel a frissítési és helyreállítási rendszerrel"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Válassza ki az évet"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kiválasztva"</string> <string name="deleted_key" msgid="7659477886625566590">"A(z) <xliff:g id="KEY">%1$s</xliff:g> érték törölve"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Munkahelyi <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Használni szeretné az alkalmazászárolást?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Az alkalmazászárolás lezárja a kijelzőt az adott alkalmazásban.\n\nA kilépéshez tartsa lenyomva a legutóbbi alkalmazások gombot ($)."</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEM"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"INDÍT"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Az alkalmazászárolás indítása"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Kilépés az alkalmazászárolásból"</string> </resources> diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml index 60b55a1..27a65a3 100644 --- a/core/res/res/values-hy-rAM/strings.xml +++ b/core/res/res/values-hy-rAM/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Թույլ է տալիս սեփականատիրոջը միանալ պաստառի վերին մակարդակի ինտերֆեյսին: Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի:"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"կապվել ձայնային փոխազդիչին"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Թույլ է տալիս սեփականատիրոջը միանալ ձայնային փոխազդիչի բազային միջերեսին: Սովորական ծրագրերի համար երբևէ չպետք է անհրաժեշտ լինի:"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"կառավարել ձայնային բանալի բառակապակցությունները"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Սեփականատիրոջը թույլ է տալիս կառավարել բանալի բառակապակցությունները՝ ձայնային թեժ բառերի հայտնաբերման համար: Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի:"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"միանալ հեռակա էկրանին"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Թույլ է տալիս սեփականատիրոջը միանալ հեռակա էկրանի վերին մակարդակի ինտերֆեյսին: Սովորական ծրագրերի համար երբևէ չպետք է անհրաժեշտ լինի:"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"միանալ վիջեթ ծառայությանը"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ` ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ձայնագրել ձայնանյութ"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Թույլ է տալիս հավելվածին բարձրախոսով ձայնագրել ձայնանյութ: Այս թույլտվությունը հնարավորություն է տալիս հավելվածին ձայնանյութ ձայնագրել ցանկացած ժամանակ` առանց ձեր հաստատման:"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM հաղորդակցում"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string> <string name="permlab_camera" msgid="3616391919559751192">"լուսանկարել և տեսանկարել"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Թույլ է տալիս հավելվածին ֆոտոխցիկով լուսանկարել և տեսանկարել: Այս թույլտվությունը հնարավորություն է տալիս հավելվածին օգտագործել ֆոտոխցիկը ցանկացած ժամանակ` առանց ձեր հաստատման:"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"անջատել փոխանցող LED ցուցիչը, երբ ֆոտոխցիկը օգտագործվում է"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Թույլ է տալիս հավելվածին սահմանել զարթուցիչի ծրագրում տեղադրված ազդանշանը: Զարթուցիչի որոշ հավելվածներ չեն կարող կիրառել այս հատկությունը:"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ավելացնել ձայնային փոստ"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Թույլ է տալիս հավելվածին ավելացնել հաղորդագրություններ ձեր ձայնային փոստի արկղում:"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"կարդալ ձայնային հաղորդագրությունները"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Թույլ է տալիս հավելվածին կարդալ ձեր բոլոր ձայնային հաղորդագրությունները:"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"փոփոխել դիտարկչի աշխարհագրական տեղանքի թույլտվությունները"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Թույլ է տալիս հավելվածին փոփոխել զննարկչի աշխարհագրական դիրքի թույլտվությունները: Վնասարար հավելվածները կարող են օգտագործել սա` թույլատրելու ուղարկել տեղադրության վերաբերյալ տեղեկությունները կամայական վեբ կայքերին:"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"հաստատել փաթեթները"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ծրագրին թույլ է տալիս լսել վստահության կարգավիճակի փոփոխությունները:"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Տրամադրել վստահելի գործակալ:"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ծրագրին թույլ է տալիս տրամադրել վստահելի գործակալ:"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Գործարկել վստահելի գործակալի կարգավորումների ցանկը:"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ծրագրին թույլ է տալիս իրականացնել այնպիսի գործունեություն, որը փոխում է վստահելի գործակալի վարքագիծը:"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Կապվել վստահելի գործակալի ծառայությանը"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ծրագրին թույլ է տալիս կապվել վստահելի գործակալի ծառայությանը:"</string> <string name="permlab_recovery" msgid="3157024487744125846">"Փոխազդել թարմացման և վերականգնման համակարգի հետ"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Ընտրեք տարին"</string> <string name="item_is_selected" msgid="949687401682476608">"Ընտրված է <xliff:g id="ITEM">%1$s</xliff:g> տարրը"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> թիվը ջնջված է"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Աշխատանքային <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 2033596..666d585 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Mengizinkan pemegang mengikat antarmuka tingkat tinggi dari suatu wallpaper. Tidak pernah diperlukan oleh apl normal."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"mengikat ke pemicu interaksi suara"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Memungkinkan pemegang mengikat antarmuka tingkat tinggi dari layanan interaksi suara. Tidak pernah diperlukan oleh aplikasi normal."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"mengelola frasa kunci suara"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Memungkinkan pemegang mengelola frasa kunci untuk deteksi kata cepat suara. Tidak pernah diperlukan oleh aplikasi normal."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"mengikat ke layar jarak jauh"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Mengizinkan pemegang mengikat ke antarmuka tingkat atas dari layar jarak jauh. Tidak pernah diperlukan untuk aplikasi normal."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"mengikat ke layanan widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"rekam audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Memungkinkan aplikasi merekam audio dengan mikrofon. Izin ini memungkinkan aplikasi merekam audio kapan saja tanpa konfirmasi Anda."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikasi sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string> <string name="permlab_camera" msgid="3616391919559751192">"ambil gambar dan video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Memungkinkan aplikasi mengambil gambar dan video dengan kamera. Izin ini memungkinkan aplikasi menggunakan kamera kapan saja tanpa konfirmasi Anda."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"nonaktifkan LED indikator transmisi saat kamera digunakan"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Mengizinkan apl menyetel alarm di apl jam alarm yang terpasang. Beberapa apl jam alarm mungkin tidak menerapkan fitur ini."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"tambahkan kotak pesan"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Mengizinkan apl menambahkan pesan ke kotak masuk untuk pesan suara Anda."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"baca semua kotak pesan"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Mengizinkan aplikasi membaca semua kotak pesan Anda."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"memodifikasi izin geolokasi Browser"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Mengizinkan apl memodifikasi izin geolokasi Browser. Apl berbahaya dapat menggunakan izin ini untuk memungkinkan pengiriman informasi lokasi ke sembarang situs web."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifikasi paket"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Mengizinkan aplikasi mendengarkan perubahan dalam status kepercayaan."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Berikan agen tepercaya."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Memungkinkan aplikasi memberikan agen tepercaya."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Luncurkan menu setelan agen tepercaya."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Memungkinkan aplikasi meluncurkan aktivitas yang mengubah perilaku agen tepercaya."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Ikat ke layanan agen kepercayaan"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Mengizinkan aplikasi mengikat ke layanan agen kepercayaan."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Berinteraksi dengan sistem pemulihan dan pembaruan"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dihapus"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kantor <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Gunakan lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app mengunci layar dalam satu aplikasi tunggal.\n\nUntuk keluar, tekan lama tombol aplikasi terbaru $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"TIDAK"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"MULAI"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Mulai Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Keluar dari Lock-to-app"</string> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 3fd984b..3397834 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Consente l\'associazione di uno sfondo all\'interfaccia principale. Non dovrebbe mai essere necessaria per le normali applicazioni."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"collegamento a un servizio di interazione vocale"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Consente al titolare di collegarsi all\'interfaccia di primo livello di un servizio di interazione vocale. Non dovrebbe essere mai necessaria per le normali app."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestione delle frasi chiave vocali"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Consente al titolare di gestire le frasi chiave per il rilevamento hotword vocale. Non dovrebbe mai essere necessario per le normali applicazioni."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"collega a un display remoto"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Consente al titolare di collegarsi all\'interfaccia di primo livello di un display remoto. Non dovrebbe essere mai necessaria per le normali applicazioni."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"associazione a un servizio widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Consente all\'applicazione di modificare le impostazioni audio globali, come il volume e quale altoparlante viene utilizzato per l\'uscita."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"registrazione audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Consente all\'applicazione di registrare audio con il microfono. Questa autorizzazione consente all\'applicazione di registrare audio in qualsiasi momento senza la tua conferma."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicazione SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string> <string name="permlab_camera" msgid="3616391919559751192">"acquisizione di foto e video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Consente all\'applicazione di scattare foto e riprendere video con la fotocamera. Questa autorizzazione consente all\'applicazione di utilizzare la fotocamera in qualsiasi momento senza la tua conferma."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disabilitazione del LED di indicazione della trasmissione quando la fotocamera è in uso"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Consente all\'applicazione di impostare un allarme in un\'applicazione sveglia installata. È possibile che alcune applicazioni sveglia non possano implementare questa funzione."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"aggiunta di un messaggio vocale"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Consente all\'applicazione di aggiungere messaggi alla casella della segreteria."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accesso alla segreteria"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Consente all\'app di accedere alla segreteria."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifica delle autorizzazioni di localizzazione geografica del browser"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Consente all\'applicazione di modificare le autorizzazioni di geolocalizzazione del Browser. Le applicazioni dannose potrebbero farne uso per consentire l\'invio di informazioni sulla posizione a siti web arbitrari."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifica dei pacchetti"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Consente a un\'applicazione di rilevare le modifiche nello stato trust."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Indica un trust agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Consente a un\'applicazione di indicare un trust agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Avvio del menu di impostazioni del trust agent."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Consente a un\'applicazione di avviare un\'attività che modifica il comportamento del trust agent."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Associazione a un servizio trust agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Consente a un\'applicazione di associarsi a un servizio trust agent."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interazione con il sistema di ripristino e aggiornamento"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Seleziona anno"</string> <string name="item_is_selected" msgid="949687401682476608">"Elemento selezionato: <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminato"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> lavoro"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Utilizzare Lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"La funzione Lock-to-app consente di bloccare la visualizzazione in un\'unica app.\n\nPer uscire, premi e tieni premuto il pulsante delle app recenti $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"AVVIA"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Avvia Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Esci da Lock-to-app"</string> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 1f2b75c..f67ae0c 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"מאפשר למשתמש לבצע איגוד לממשק הרמה העליונה של טפט. הרשאה זו לעולם אינה נחוצה לאפליקציות רגילים."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"איגוד לשירות אינטראקציה קולית"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"מאפשרת לבעלים לאגד לממשק ברמה העליונה של שירות אינטראקציה קולית. לעולם לא אמורה להיות נחוצה עבור אפליקציות רגילות."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ניהול של ביטויי מפתח קוליים"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"מאפשרת לבעלים לנהל את ביטויי המפתח עבור זיהוי של מילת הפעלה. לעולם לא אמורה להיות נחוצה עבור אפליקציות רגילות."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"איגוד לצג מרוחק"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"הרשאה זו מאפשרת למשתמש לבצע איגוד לממשק הרמה העליונה של צג רחוק. לעולם אינה אמורה להיות נחוצה לאפליקציות רגילות."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"הכפפה לשירות Widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"מאפשר לאפליקציה לשנות הגדרות אודיו גלובליות כמו עוצמת קול ובחירת הרמקול המשמש לפלט."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"הקלט אודיו"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"מאפשר לאפליקציה להקליט אודיו באמצעות המיקרופון. אישור זה מתיר לאפליקציה להקליט אודיו בכל עת ללא אישורך."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"תקשורת SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"מאפשרת ליישום לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string> <string name="permlab_camera" msgid="3616391919559751192">"צלם תמונות וסרטונים"</string> <string name="permdesc_camera" msgid="8497216524735535009">"מאפשר לאפליקציה לצלם תמונות וסרטונים באמצעות המצלמה. אישור זה מאפשר לאפליקציה להשתמש במצלמה בכל עת ללא אישורך."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"השבת את נורית מצב השידור כשהמצלמה בשימוש"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"מאפשר לאפליקציה להגדיר התראה באפליקציה מותקנת של שעון מעורר. אפליקציות מסוימות של שעון מעורר אינן מיישמות תכונה זו."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"הוסף דואר קולי"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"מאפשר לאפליקציה להוסיף הודעות לתיבת הדואר הקולי."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"קריאת כל הדואר הקולי"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"מאפשרת לאפליקציה לקרוא את כל הודעות הדואר הקולי שלך."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"שינוי הרשאות המיקום הגיאוגרפי של הדפדפן"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"מאפשר לאפליקציה לשנות את הרשאות המיקום הגיאוגרפי של הדפדפן. אפליקציות זדוניות עלולות להשתמש בכך כדי לאפשר משלוח של פרטי מיקום לאתרים זדוניים אחרים."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"אימות חבילות"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"מאפשר לאפליקציה לחפש שינויים במצב אמון."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"ציון סוכן אמון."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"מאפשר לאפליקציה לספק סוכן אמון."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"הפעלת תפריט ההגדרות של סוכן האמון."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"מאפשר לאפליקציה להתחיל פעילות המשנה את ההתנהגות של סוכן האמון."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"איגוד אל שירות סוכן אמון"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"מאפשר לאפליקציה לאגוד אל שירות סוכן אמון."</string> <string name="permlab_recovery" msgid="3157024487744125846">"אינטראקציה עם מערכת שחזור ועדכונים"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"בחר שנה"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> נבחר"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> נמחק"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"עבודה <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"האם להשתמש ב\'נעל באפליקציה\'?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"\'נעל באפליקציה\' נועל את המסך באפליקציה יחידה.\n\nכדי לצאת, לחץ על לחצן האפליקציות האחרונות $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"לא"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"הפעל"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"הפעל את \'נעל באפליקציה\'"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"צא מ\'נעל באפליקציה\'"</string> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 6aedff7..1bd8906 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"壁紙のトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"音声対話サービスへのバインド"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"音声対話サービスのトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"音声キーフレーズの管理"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"音声注目ワードの認識用キーフレーズを管理することを所有者に許可します。通常のアプリでは不要です。"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"リモートディスプレイへのバインド"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"リモートディスプレイのトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ウィジェットサービスにバインド"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"音声全般の設定(音量、出力に使用するスピーカーなど)の変更をアプリに許可します。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"録音"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"マイクを使った録音をアプリに許可します。これにより、アプリがいつでも確認なしで録音できるようになります。"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM通信"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string> <string name="permlab_camera" msgid="3616391919559751192">"写真と動画の撮影"</string> <string name="permdesc_camera" msgid="8497216524735535009">"カメラでの写真と動画の撮影をアプリに許可します。これにより、アプリが確認なしでいつでもカメラを使用できるようになります。"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"カメラの使用中に通信インジケータLEDを無効にする"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"インストール済みアラームアプリのアラームを設定することをアプリに許可します。この機能が実装されていないアラームアプリもあります。"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ボイスメールの追加"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ボイスメール受信トレイにメッセージを追加することをアプリに許可します。"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"すべてのボイスメールの読み取り"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"すべてのボイスメールの読み取りをアプリに許可します。"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ブラウザの現在地情報に対する権限の変更"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ブラウザの現在地情報に対する権限の変更をアプリに許可します。この許可を悪意のあるアプリに利用されると、任意のウェブサイトに現在地情報が送信される恐れがあります。"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"パッケージのベリファイ"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"信頼状態の変更をリッスンすることをアプリに許可します。"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"信頼できるエージェントの提供"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"信頼できるエージェントの提供をアプリに許可します。"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"信頼できるエージェントの設定メニューの起動"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"信頼できるエージェントの動作を変更するアクティビティを開始することをアプリに許可します。"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"信頼できるエージェントサービスへのバインド"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"信頼できるエージェントサービスにバインドすることをアプリに許可します。"</string> <string name="permlab_recovery" msgid="3157024487744125846">"アップデートと回復システムへのアクセス"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"年を選択"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>を選択しました"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g>を削除しました"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"仕事の<xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml index 01824b2..a7601d2 100644 --- a/core/res/res/values-ka-rGE/strings.xml +++ b/core/res/res/values-ka-rGE/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"მფლობელს შეეძლება ფონის ზედა დონის ინტერფეისთან დაკავშირება. არასდროს გამოიყენება ჩვეულებრივ აპებში."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ხმის ინტერაქტორთან შეკავშირება"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"მფლობელს შეეძლება შეკავშირდეს ხმის ინტერაქციის სერვისების ზედა დონის ინტერფეისთან. ჩვეულებრივ აპს ეს წესით არასოდეს უნდა დასჭირდეს."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ხმოვანი საიდუმლო ფრაზების მართვა"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"საშუალებას აძლევს მფლობელს მართოს საიდუმლო ფრაზები ხმოვანი ჯადოსნური სიტყვის ამოცნობისათვის. ეს ჩვეულებრივ აპებს არ უნდა დასჭირდეს."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"დისტანციურ მონიტორზე მიბმა"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"მფლობელს შეეძლება მიებას დისტანციურ მონიტორის ზედა დონის ინტერფეისს. ჩვეულებრივ აპს ეს წესით არასოდეს უნდა დაჭირდეს."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ვიჯეტ სერვისთან დაკავშირება"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"აპს შეეძლება აუდიოს გლობალური პარამეტრების შეცვლა. მაგ.: ხმის სიმაღლე და რომელი დინამიკი გამოიყენება სიგნალის გამოსტანად."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"აუდიოს ჩაწერა"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"აპს შეეძლება აუდიო ჩაწერა მიკროფონით. ნებართვა აპს აუდიო ჩაწერის უფლებას აძლევს ნებისმიერ დროს, თქვენი თანხმობის გარეშე."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"კომუნიკაცია SIM-თან"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string> <string name="permlab_camera" msgid="3616391919559751192">"სურათებისა და ვიდეოების გადაღება"</string> <string name="permdesc_camera" msgid="8497216524735535009">"აპს შეეძლება კამერით სურათისა და ვიდეოს გადაღება. ეს ნებართვა აპს უფლებას აძლევს, ნებისმიერ დროს გამოიყენოს კამერა თქვენი დადასტურების გარეშე."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"კამერის გამოყენებისას გადამცემი ინდიკატორის LED გათიშვა"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"აპს შეეძლება მაღვიძარას დაყენება დაინსტალირებული მაღვიძარას აპლიკაციაში. ამ ფუნქციას მაღვიძარას ზოგიერთი აპი არ იყენებს."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ხმოვანი ფოსტის დამატება"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"აპს შეეძლება დაამატოს შეტყობინებები თქვენი ხმოვანი ფოსტის შემოსულებში."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"მთელი ხმოვანი ფოსტის წაკითხვა"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"აპს ეძლევა მთელი თქვენი ხმოვანი ფოსტების წაკითხვის უფლება."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ბრაუზერის გეოლოკაციის უფლებების შეცვლა"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"აპს შეეძლება ბრაუზერის გეოლოკაციის უფლებების შეცვლა. მავნე აპებმა ეს შესაძლოა გამოიყენონ ნებისმიერი ვებსაიტისთვის მდებარეობის შესახებ ინფორმაციის გასაგზავნად."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"პაკეტების გადამოწმება"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"საშუალებას აძლევს აპლიკაციას მოუსმინოს ცვლილებებს სანდო მდგომარეობაში."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"სანდო აგენტის წარმოდგენა."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"საშუალებას აძლევს აპლიკაციას წარმოადგინოს სანდო აგენტი."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ნდობის აგენტის პარამეტრების მენიუს გამოძახება."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"საშუალებას აძლევს აპლიკაციას გამოიძახოს აქტივობა, რაც ნდობის აგენტის ქცევას ცვლის."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"სანდო აგენტის სერვისზე მიმაგრება."</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"საშუალებას აძლევს აპლიკაციას მიემაგროს სანდო აგენტის სერვისს."</string> <string name="permlab_recovery" msgid="3157024487744125846">"განახლებასთან და აღდგენის სისტემასთან ინტერაქცია"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"აირჩიეთ წელი"</string> <string name="item_is_selected" msgid="949687401682476608">"არჩეულია <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> წაიშალა"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"სამსახური <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml index 79c762a..d8bf2ff 100644 --- a/core/res/res/values-km-rKH/strings.xml +++ b/core/res/res/values-km-rKH/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ឲ្យម្ចាស់ចងចំណុចប្រទាក់កម្រិតកំពូលនៃផ្ទាំងរូបភាព។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ភ្ជាប់ទៅអ្នកសហការសំឡេង"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"អនុញ្ញាតឲ្យម្ចាស់ភ្ជាប់ទៅចំណុចប្រទាក់កម្រិតកំពូលរបស់សេវាកម្មអន្តរកម្មសំឡេង។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"គ្រប់គ្រងឃ្លាសំឡេង"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"អនុញ្ញាតឲ្យម្ចាស់គ្រប់គ្រងឃ្លាសម្រាប់ការរកឃើញពាក្យជាសំឡេង។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ភ្ជាប់ទៅការបង្ហាញពីចម្ងាយ"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"អនុញ្ញាតឲ្យម្ចាស់ភ្ជាប់ទៅចំណុចប្រទាក់កម្រិតកំពូលនៃការបង្ហាញពីចម្ងាយ។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ចងសេវាកម្មធាតុក្រាហ្វិក"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ឲ្យកម្មវិធីកែការកំណត់សំឡេងសកល ដូចជាកម្រិតសំឡេង និងអូប៉ាល័រដែលបានប្រើសម្រាប់លទ្ធផល។"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ថតសំឡេង"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"ឱ្យកម្មវិធីថតសំឡេងជាមួយមីក្រូហ្វូន។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីថតសំឡេងនៅពេលណាមួយដោយគ្មានការបញ្ជាក់របស់អ្នក។"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"ការភ្ជាប់ស៊ីមកាត"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"ឲ្យកម្មវិធីផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត។ វាគ្រោះថ្នាក់ណាស់។"</string> <string name="permlab_camera" msgid="3616391919559751192">"ថតរូប និងវីដេអូ"</string> <string name="permdesc_camera" msgid="8497216524735535009">"ឲ្យកម្មវិធីថតរូប និងវីដេអូដោយប្រើម៉ាស៊ីនថត។ វាឲ្យកម្មវិធីប្រើម៉ាស៊ីនថតនៅពេលណាមួយដោយគ្មានការបញ្ជាក់របស់អ្នក។"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"បិទពន្លឺបង្ហាញការបញ្ជូនពេលម៉ាស៊ីនថតកំពុងប្រើ"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"ឲ្យកម្មវិធីកំណត់សំឡេងរោទ៍ក្នុងកម្មវិធីនាឡិការោទ៍បានដំឡើង។ កម្មវិធីនាឡិការោទ៍មួយចំនួនអាចមិនអនុវត្តលក្ខណៈនេះ។"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"បន្ថែមសារជាសំឡេង"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ឲ្យកម្មវិធីបន្ថែមសារទៅប្រអប់ទទួលសារជាសំឡេងរបស់អ្នក។"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"អានសារជាសំឡេងទាំងអស់"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ឲ្យកម្មវិធីអានសារជាសំឡេងរបស់អ្នកទាំងអស់។"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"កែសិទ្ធិទីតាំងភូមិសាស្ត្ររបស់កម្មវិធីអ៊ីនធឺណិត"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ឲ្យកម្មវិធីកែសិទ្ធិទីតាំងភូមិសាស្ត្ររបស់កម្មវិធីអ៊ីនធឺណិត។ កម្មវិធីព្យាបាទអាចប្រើវា ដើម្បីឲ្យផ្ញើព័ត៌មានទីតាំងទៅតំបន់បណ្ដាញដោយបំពាន។"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ផ្ទៀងផ្ទាត់កញ្ចប់"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"ឲ្យកម្មវិធីស្ដាប់ការផ្លាស់ប្ដូរក្នុងស្ថានភាពដែលទុកចិត្ត។"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"ផ្ដល់ភ្នាក់ងារដែលទុកចិត្ត។"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ឲ្យកម្មវិធីផ្ដល់ភ្នាក់ងារដែលទុកចិត្ត។"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ចាប់ផ្ដើមម៉ឺនុយការកំណត់ភ្នាក់ងារជឿទុកចិត្ត។"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"អនុញ្ញាតឲ្យកម្មវិធីចាប់ផ្ដើមសកម្មដែលផ្លាស់ប្ដូរឥរិយាបថភ្នាក់ងារជឿទុកចិត្ត។"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ភ្ជាប់ទៅសេវាកម្មភ្នាក់ងារដែលទុកចិត្ត"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"ឲ្យកម្មវិធីភ្ជាប់សេវាកម្មភ្នាក់ងារដែលទុកចិត្ត។"</string> <string name="permlab_recovery" msgid="3157024487744125846">"អន្តរកម្មជាមួយបច្ចុប្បន្នភាព និងប្រព័ន្ធសង្គ្រោះ"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"ជ្រើសឆ្នាំ"</string> <string name="item_is_selected" msgid="949687401682476608">"បានជ្រើស <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"បានលុប <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"កន្លែងធ្វើការ <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"ប្រើការចាក់សោកម្មវិធី?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"ចាក់សោកម្មវិធីគឺចាក់សោការបង្ហាញក្នុងកម្មវិធីតែមួយ។\n\nដើម្បីចាកចេញ ចុច និងសង្កត់ប៊ូតុងកម្មវិធីថ្មីៗ $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"ទេ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ចាប់ផ្ដើម"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"ចាប់ផ្ដើមការចាក់សោកម្មវិធី"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"ចេញពីការចាក់សោកម្មវិធី"</string> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index db02343..d2fd2b0 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"권한을 가진 프로그램이 배경화면에 대한 최상위 인터페이스를 사용하도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"음성 상호작용 서비스 사용"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"권한을 가진 프로그램이 음성 상호작용 서비스의 최상위 인터페이스를 사용하도록 합니다. 일반 앱에는 필요하지 않습니다."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"음성 핵심문구 관리"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"권한을 가진 프로그램이 음성 핫워드 감지용 핵심문구를 관리할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"원격 디스플레이에 연결"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"권한을 가진 프로그램이 원격 디스플레이에 대한 최상위 인터페이스를 사용하도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"위젯 서비스와 연결"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"앱이 마이크로 오디오를 녹음할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 사용자의 확인 없이 언제든지 오디오를 녹음할 수 있습니다."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 통신"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 위험합니다."</string> <string name="permlab_camera" msgid="3616391919559751192">"사진과 동영상 찍기"</string> <string name="permdesc_camera" msgid="8497216524735535009">"앱이 카메라로 사진과 동영상을 찍을 수 있도록 허용합니다. 이 권한을 사용하면 앱이 언제든지 사용자의 확인 없이 카메라를 사용할 수 있습니다."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"카메라를 사용할 때 전송 표시 LED 사용 중지"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"앱이 설치된 알람 시계 앱에서 알람을 설정할 수 있도록 허용합니다. 일부 알람 시계 앱에는 이 기능이 구현되지 않을 수 있습니다."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"음성사서함 추가"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"앱이 음성사서함에 메시지를 추가할 수 있도록 허용합니다."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"모든 음성사서함 읽기"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"앱이 모든 음성사서함을 읽을 수 있도록 허용합니다."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"브라우저 위치 정보 권한 수정"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"앱이 브라우저의 위치 정보 권한을 수정할 수 있도록 허용합니다. 이 경우 악성 앱이 이 기능을 이용하여 임의의 웹사이트에 위치 정보를 보낼 수 있습니다."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"패키지 확인"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"애플리케이션이 Trust 상태에서의 변경사항을 수신할 수 있도록 허용합니다."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust Agent 제공"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"애플리케이션이 Trust Agent를 제공할 수 있도록 허용합니다."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Trust Agent 설정 메뉴를 실행합니다."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"애플리케이션에서 Trust Agent의 동작을 변경하는 활동을 실행하도록 허용합니다."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Trust Agent 서비스에 연결"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"애플리케이션이 Trust Agent 서비스에 바인딩할 수 있도록 허용합니다."</string> <string name="permlab_recovery" msgid="3157024487744125846">"업데이트 및 복구 시스템과 상호작용"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"연도 선택"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>이(가) 선택됨"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 삭제됨"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"업무용 <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml index 9ebcbe7..8de71f9 100644 --- a/core/res/res/values-lo-rLA/strings.xml +++ b/core/res/res/values-lo-rLA/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ອະນຸຍາດໃຫ້ຜູ່ໃຊ້ເຊື່ອມໂຍງກັບສ່ວນຕິດຕໍ່ລະດັບສູງສຸດ ຂອງພາບພື້ນຫຼັງໃດນຶ່ງ. ແອັບຯທຳມະດາບໍ່ຄວນຈຳເປັນຕ້ອງໃຊ້."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ເຊື່ອມໂຍງກັບຕົວຕິດຕໍ່ດ້ວຍສຽງ"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"ອະນຸຍາດໃຫ້ເຈົ້າຂອງເຊື່ອມໂຍງສ່ວນຕິດຕໍ່ລະດັບສູງສຸດຂອງບໍລິການການຕິດຕໍ່ດ້ວຍສຽງ."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ຈັດການລະຫັດສຽງ"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"ອະນຸຍາດໃຫ້ຈັດການລະຫັດສຳລັບການກວດພົບຄຳສັ່ງສຽງ. ແອັບຯທົ່ວໄປບໍ່ຄວນຕ້ອງໃຊ້."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ຜູກກັນເພື່ອສະແດງຜົນທາງໄກ."</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"ອະນຸຍາດໃຫ້ຜູ່ຖືຜູກກັບສ່ວນຕິດຕໍ່ລະດັບສູງສຸດ ຂອງການສະແດງຜົນທາງໄກ. ບໍ່ຈຳເປັນສຳລັບແອັບຯທົ່ວໄປ."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ເຊື່ອມໂຍງໄປຫາບໍລິການວິດເຈັດ"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂການຕັ້ງຄ່າສຽງສ່ວນກາງ ເຊັ່ນ: ລະດັບສຽງ ແລະລຳໂພງໃດທີ່ຖືກໃຊ້ສົ່ງສຽງອອກ."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ບັນທຶກສຽງ"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"ອະນຸຍາດໃຫ້ແອັບຯບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນໄດ້. ການອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຯ ສາມາດບັນທຶກສຽງໄດ້ຕະຫລອດເວລາ ໂດຍບໍ່ຕ້ອງຖ້າການຢືນຢັນຈາກທ່ານ."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"ການສື່ສານຂອງ SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string> <string name="permlab_camera" msgid="3616391919559751192">"ຖ່າຍຮູບ ແລະວິດີໂອ"</string> <string name="permdesc_camera" msgid="8497216524735535009">"ອະນຸຍາດໃຫ້ແອັບຯຖ່າຍຮູບ ແລະວິດີໂອດ້ວຍກ້ອງຖ່າຍຮູບ. ການອະນຸຍາດນີ້ຈະອານຸຍາດໃຫ້ແອັບຯ ສາມາດໃຊ້ກ້ອງຖ່າຍຮູບໄດ້ຕະຫລອດເວລາ ໂດຍບໍ່ຕ້ອງຖ້າການຢືນຢັນຈາກທ່ານ."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ປິດໄຟສັນຍານ LED ເມື່ອນຳໃຊ້ກ້ອງ"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"ອະນຸຍາດໃຫ້ແອັບຯຕັ້ງໂມງປຸກໃນແອັບຯໂມງປຸກທີ່ຕິດຕັ້ງໄວ້. ບາງແອັບຯໂມງປຸກອາດບໍ່ມີຄຸນສົມບັດແບບນີ້ເທື່ອ."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ເພີ່ມຂໍ້ຄວາມສຽງ"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ອະນຸຍາດໃຫ້ແອັບຯ ສາມາດເພີ່ມຂໍ້ຄວາມໃສ່ອິນບັອກຂໍ້ຄວາມສຽງຂອງທ່ານໄດ້."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ອ່ານຂໍ້ຄວາມສຽງທັງໝົດ"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ອະນຸຍາດໃຫ້ແອັບຯອ່ານຂໍ້ຄວາມສຽງທັງໝົດຂອງທ່ານ."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ແກ້ໄຂສິດທາງສະຖານທີ່ພູມສາດຂອງໂປຣແກຣມທ່ອງເວັບ"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂ ການອະນຸຍາດຕຳແໜ່ງທາງພູມສາດ ຂອງໂປຣແກຣມທ່ອງເວັບ. ແອັບຯທີ່ເປັນອັນຕະລາຍອາດໃຊ້ຄຸນສົມບັດນີ້ ເພື່ອສົ່ງຂໍ້ມູນສະຖານທີ່ໄປໃຫ້ເວັບໄຊຕ່າງໆໄດ້."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ຢັ້ງຢືນແພັກເກດ"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນຕິດຕາມການປ່ຽນແປງໃນສະຖານະການເຊື່ອຖື."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"ລະບຸເອເຈນທີ່ເຊື່ອຖືໄດ້."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະບຸເອເຈນທີ່ເຊື່ອຖືໄດ້."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ເປີດເມນູການຕັ້ງຄ່າເອເຈັນທີ່ເຊື່ອຖືໄດ້."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນເປີດການເຄື່ອນໄຫວທີ່ປ່ຽນແປງພຶດຕິກຳຂອງເອເຈັນທີ່ເຊື່ອຖືໄດ້."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ເຊື່ອມໂຍງຫາບໍລິການຕົວແທນການເຊື່ອຖື"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນເຊື່ອມໂຍງກັບບໍລິການຕົວແທນທີ່ເຊື່ອຖືໄດ້."</string> <string name="permlab_recovery" msgid="3157024487744125846">"ຕິດຕໍ່ກັບລະບົບອັບເດດ ແລະລະບົບກູ້ຂໍ້ມູນ."</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"ເລືອກປີ"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ຖືກເລືອກແລ້ວ"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ຖືກລຶບແລ້ວ"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"ບ່ອນເຮັດວຽກ <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index e9b1247..dcac639 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Leidžiama savininką susaistyti su aukščiausio lygio darbalaukio fono sąsaja. Įprastoms programoms to neturėtų prireikti."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"susaistyti su sąveikos balsu priemone"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Turėtojui leidžiama susaistyti programą su sąveikos balsu paslaugos aukščiausio lygio sąsaja. Įprastoms programoms to niekada neturėtų prireikti."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"tvarkyti pagrindines balso frazes"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Savininkui leidžiama tvarkyti pagrindines aktyvinamiesiems žodžiams aptikti skirtas frazes. To niekada neturėtų prireikti naudojant įprastas programas."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"susisaistyti su nuotoliniu ekranu"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Leidžiama savininkui susisaistyti su aukščiausiojo lygio nuotolinio ekrano sąsaja. Įprastoms programoms to neturėtų prireikti."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"susaistyti su valdiklio paslauga"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Leidžiama programai keisti visuotinius garso nustatymus, pvz., garsumą ir tai, kuris garsiakalbis naudojamas išvesčiai."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"įrašyti garsą"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Leidžiama programai įrašyti garsą naudojant mikrofoną. Šis leidimas suteikia galimybę programai įrašyti garsą bet kada be jūsų patvirtinimo."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM kortelės ryšys"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string> <string name="permlab_camera" msgid="3616391919559751192">"fotografuoti ir filmuoti"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Leidžiama programai fotografuoti ir filmuoti kamera. Šis leidimas suteikia teisę programai naudoti kamerą bet kada be jūsų patvirtinimo."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"neleisti perduoti LED indikatoriaus, kai naudojamas fotoaparatas"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Leidžiama programai nustatyti signalą įdiegtoje žadintuvo programoje. Kai kuriose žadintuvo programose ši funkcija gali nebūti nevykdoma."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"pridėti balso pašto pranešimų"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Leidžia programai pridėti pranešimų prie jūsų balso pašto gautųjų."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"skaityti visus balso pašto pranešimus"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Programai leidžiama skaityti visus balso pašto pranešimus."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"keisti naršyklės geografinės vietos leidimus"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Leidžiama programai keisti naršyklės geografinės vietos leidimus. Kenkėjiškos programos gali tai naudoti, kad leistų siųsti vietos informaciją abejotinoms svetainėms."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"patikrinti paketus"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Programai leidžiama atsižvelgti į patikimos būsenos pakeitimus."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Teikti patikimos priemonės paslaugą."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Programai leidžiama teikti patikimos priemonės paslaugą."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Paleisti patikimo atstovo nustatymų meniu."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Leidžiama programai paleisti veiklą, keičiančią patikimo atstovo elgseną."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Susisaistyti su „trust agent“ paslauga"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Programai leidžiama susisaistyti su „trust agent“ paslauga."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Sąveikauti su naujiniu ir atkūrimo sistema"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Pasirinkite metus"</string> <string name="item_is_selected" msgid="949687401682476608">"Pasirinkta: <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Ištrinta: <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbo <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Naudoti programos užrakinimo funkciją?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Naudojant programos užrakinimo funkciją ekrane užrakinama viena programa.\n\nJei norite išeiti, paspauskite ir palaikykite paspaudę naujausių programų mygtuką $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ĮJUNGTI"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Įjungti programos užrakinimo funkciją"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Išeiti iš programos užrakinimo funkcijos"</string> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 8716114..a9a9984 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ļauj īpašniekam piesaistīt tapetes augstākā līmeņa lietotāja saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"Saistīšana ar balss mijiedarbības elementu"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ļauj īpašniekam izveidot savienojumu ar balss mijiedarbības pakalpojuma augšējā līmeņa saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"pārvaldīt balss atslēgas frāzes"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ļauj īpašniekam pārvaldīt atslēgas frāzes balss īsinājumvārdu identificēšanai. Parastajām lietotnēm tas nekad nav nepieciešams."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"Saites izveide ar attālu displeju"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ļauj īpašniekam izveidot saiti ar attāla displeja augšējā līmeņa saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"saistīt ar logrīka pakalpojumu"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ļauj lietotnei mainīt globālos audio iestatījumus, piemēram, skaļumu un izejai izmantoto skaļruni."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ierakstīt audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ļauj lietotnei ierakstīt audio, izmantojot mikrofonu. Šī atļauja ļauj lietotnei ierakstīt audio jebkurā brīdī bez jūsu apstiprinājuma."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM saziņa"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string> <string name="permlab_camera" msgid="3616391919559751192">"uzņemt attēlus un videoklipus"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Ļauj lietotnei uzņemt attēlus un videoklipus ar kameru. Ar šo atļauju lietotne var jebkurā brīdī izmantot kameru bez jūsu apstiprinājuma."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Atspējot pārraidīšanas LED indikatoru, kad kamera tiek izmantota"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Ļauj lietotnei iestatīt signālu instalētajā modinātājpulksteņa lietotnē. Dažās modinātājpulksteņu lietotnēs šo funkciju, iespējams, nevar ieviest."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"pievienot balss pastu"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ļauj lietotnei pievienot ziņojumus jūsu balss pasta iesūtnei."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lasīt visus balss pasta ziņojumus"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ļauj lietotnei lasīt visus jūsu balss pasta ziņojumus."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"pārveidot pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ļauj lietotnei modificēt pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas. Ļaunprātīgas lietotnes to var izmantot, lai atļautu atrašanās vietas informācijas sūtīšanu uz citām vietnēm."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakotņu verificēšana"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ļauj lietojumprogrammai klausīties uzticamības statusa izmaiņas."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Nodrošināt uzticamības pārbaudes programmu"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ļauj lietojumprogrammai nodrošināt uzticamības pārbaudes programmu."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Palaist uzticamības pārbaudes programmas iestatījumu izvēlni."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ļauj lietojumprogrammai palaist darbību, kas maina uzticamības pārbaudes programmas rīcību."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Izveidot savienojumu ar uzticamības pārbaudes pakalpojumu"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ļauj lietojumprogrammai izveidot savienojumu ar uzticamības pārbaudes pakalpojumu."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Mijiedarbošanās ar atjauninājumu un atkopšanas sistēmu"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Atlasiet gadu."</string> <string name="item_is_selected" msgid="949687401682476608">"Atlasīts: <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> tika dzēsts."</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbā: <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Vai izmantot bloķēšanu darbībai vienā lietotnē?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Konfigurācija \"Bloķēšana darbībai vienā lietotnē\" nobloķē ekrānu vienā lietotnē.\n\nLai izietu, nospiediet un turiet pogu “Pēdējās izmantotās lietotnes” $."</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NĒ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"SĀKT"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Sākt bloķēšanu darbībai vienā lietotnē"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Iziet no bloķēšanas darbībai vienā lietotnē"</string> </resources> diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml index 36d54d0..c55bbcd 100644 --- a/core/res/res/values-mn-rMN/strings.xml +++ b/core/res/res/values-mn-rMN/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Эзэмшигч нь ханын зурагны дээд-төвшиний интерфейстэй холбох боломжтой. Энгийн апп-уудад шаардлагагүй."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"дуугаар харьцагчтай холбох"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Эзэмшигчид дуугаар харьцах үйлчилгээний дээд-түвшний интерфейстэй холбох боломж олгоно. Энгийн апп-уудад хэзээ ч ашиглагдахгүй."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"дууны гол хэллэгүүдийг удирдах"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Эзэмшигчид дууны хотворд-г таних гол хэллэгүүдийг удирдах боломж олгоно. Энгийн апп-д хэзээ ч ашиглагдахгүй."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"алсын дэлгэцтэй холбогдох"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Эзэмшигчид алсын дэлгэц дэх дээд давхаргын интерфэйстэй холбогдох боломж олгоно. Энгийн апп-д шаардагдахгүй."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"виджет үйлчилгээтэй холбох"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Апп нь дууны хэмжээ, спикерын гаралтад ашиглагдах глобал аудио тохиргоог өөрчлөх боломжтой."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"аудио бичих"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Апп нь микрофоноор аудио бичих боломжтой. Энэ зөвшөөрөл нь апп-д ямар ч үед таны зөвшөөрөлгүйгээр аудио бичих боломжийг олгоно."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"сим холбоо"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string> <string name="permlab_camera" msgid="3616391919559751192">"зураг авах болон видео бичих"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Апп нь камераар зураг авах болон видео бичих боломжтой. Энэ зөвшөөрөл нь апп-д ямар ч үед таны зөвшөөрөлгүйгээр камер ашиглах боломжийг олгоно."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"камер ашиглаж байх үед дамжууллыг заагч LED-г идэвхгүй болгох"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Апп нь суулгагдсан сэрүүлэгний апп дээр сэрүүлэг тохируулах боломжтой. Зарим сэрүүлэгний апп нь энэ функцийг дэмжихгүй байж болзошгүй."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"дуут шуудан нэмэх"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Таны дуут шуудангийн ирсэн мэйлд зурвас нэмэхийг апп-д зөвшөөрөх."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"бүх дуут шууданг унших"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Апп-д таны бүх дуут шууданг унших боломж олгоно."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Хөтчийн геобайршлын зөвшөөрлийг өөрчлөх"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын веб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"багцийг тулгах"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Итгэмжлэлд орж буй өөрчлөлтийг мэдэх боломжийг аппликешнд олгоно."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Итгэмжлэгдсэн төлөөлөгч нийлүүлэх"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Аппликешнд итгэмжлэгдсэн төлөөлөгч нийлүүлэх боломж олгоно."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Итгэмжлэгдсэн агентын тохиргоо цэсийг эхлүүлэх."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Аппликешнд итгэмжлэгдсэн агентын авирыг өөрчлөх боломжтой үйлдлийг эхлүүлэхийг зөвшөөрнө."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Итгэмжлэгдсэн төлөөлөгчийн үйлчилгээтэй холбогдох"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Аппликешнд итгэмжлэгдсэн төлөөлөгчтэй холбогдох боломж олгоно."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Шинэчлэлт болон сэргээх системтэй харилцах"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Жилийг сонгоно уу"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> сонгогдсон"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> устсан"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Ажлын <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Апп-дотор-түгжих-г ашиглах уу?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Апп-дотор-түгжих нь дэлгэцийг нэг апп дотор түгжинэ.\n\nГарахын тулд саяхны апп-ууд товчийг дараад барина уу $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"ҮГҮЙ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ЭХЛҮҮЛЭХ"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Апп-дотор-түгжих-г эхлүүлэх"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Апп-дотор-түгжих-с гарах"</string> </resources> diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml index e9d1e80..7c35ec5 100644 --- a/core/res/res/values-ms-rMY/strings.xml +++ b/core/res/res/values-ms-rMY/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi kertas dinding. Tidak sekali-kali diperlukan untuk apl biasa."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"terikat kepada interaksi suara"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi perkhidmatan interaksi suara. Tidak sekali-kali diperlukan untuk apl biasa."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"urus frasa kunci suara"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Membenarkan pemegang mengurus frasa kunci untuk pengesahan sebutan laluan suara. Tidak sekali-kali diperlukan untuk apl biasa."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"terikat kepada paparan jauh"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi paparan jauh. Tidak sekali-kali diperlukan untuk apl biasa."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"terikat kepada perkhidmatan widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Membenarkan apl untuk mengubah suai tetapan audio global seperti kelantangan dan pembesar suara mana digunakan untuk output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"rakam audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Membenarkan apl untuk merakam audio menggunakan mikrofon. Kebenaran ini membenarkan apl untuk merakam audio pada bila-bila masa tanpa pengesahan anda."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikasi sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string> <string name="permlab_camera" msgid="3616391919559751192">"ambil gambar dan video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Membenarkan apl mengambil gambar dan video menggunakan kamera. Kebenaran ini membenarkan apl untuk menggunakan kamera pada bila-bila masa tanpa pengesahan anda."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"lumpuhkan LED penunjuk penghantaran semasa kamera sedang digunakan"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Membenarkan apl untuk menetapkan penggera dalam apl penggera jam yang dipasang. Sesetengah applikasi jam penggera tidak boleh melaksanakan ciri ini."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"tambah mel suara"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Membenarkan apl untuk menambahkan mesej pada peti masuk mel suara anda."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"baca semua mel suara"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Membenarkan apl membaca semua mel suara anda."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ubah suai kebenaran geolokasi Penyemak Imbas"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Membenarkan apl untuk mengubah suai kebenaran geolokasi Penyemak Imbas. Apl hasad boleh menggunakannya untuk membenarkan menghantar maklumat lokasi kepada laman web sembarangan."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"sahkan pakej"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Membenarkan aplikasi mendengar perubahan dalam keadaan amanah."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Sediakan ejen amanah."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Membenarkan aplikasi menyediakan ejen amanah."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancarkan menu tetapan ejen amanah."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Membenarkan aplikasi melancarkan aktiviti yang mengubah tingkah laku ejen amanah."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Mengikat kepada perkhidmatan ejen amanah"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Membenarkan aplikasi terikat kepada perkhidmatan ejen amanah."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Berinteraksi dengan kemas kini dan sistem pemulihan"</string> @@ -1717,4 +1725,18 @@ <string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dipadamkan"</string> + <!-- no translation found for managed_profile_label_badge (2355652472854327647) --> + <skip /> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 8dace09..fb3f90e 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lar innehaveren binde det øverste nivået av grensesnittet til en bakgrunn. Skal aldri være nødvendig for vanlige apper."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"binde seg til en tjeneste for talehandlinger"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Gir innehaveren tillatelse til å binde til toppnivået av brukergrensesnittet for en tjeneste for talehandlinger. Dette skal ikke være nødvendig for vanlige apper."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrer nøkkelfraser for stemmebruk"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Tillater eieren å administrere nøkkelfraser for gjenkjennelse av talekommandoord. Skal aldri være nødvendig for normale apper."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"binde til ekstern skjerm"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lar innehaveren binde seg til det øverste grensesnittnivået for ekstern skjerm. Skal aldri være nødvendig for vanlige apper."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"binde til modultjenste"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Lar appen endre globale lydinnstillinger slik som volum og hvilken høyttaler som brukes for lydavspilling."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ta opp lyd"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Lar appen ta opp lyd med mikrofonen. Dette betyr at appen kan ta opp lyd når som helst uten at du har bedt om det."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-kommunikasjon"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string> <string name="permlab_camera" msgid="3616391919559751192">"ta bilder og videoer"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Lar appen ta bilder og filme med kameraet. Denne tillatelsen gjør at appen kan bruke kameraet når som helst uten bekreftelse fra deg."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiver LED-lyset for indikering av overføring når kameraet er i bruk"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Lar appen stille inn alarmen for en installert alarmklokke-app. Enkelte alarmklokke-apper implementerer kanskje ikke denne funksjonen."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"legge til talepost"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Lar appen legge til meldinger i talepostkassen din."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lese alle meldingene i talepostkassen"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Lar appen lese alle meldingene i talepostkassen din."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"endre nettleserens tillatelser for geoposisjonering"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Lar appen endre nettleserens tillatelser for geoposisjonering. Ondsinnede apper kan bruke dette for å tillate sending av posisjonsinformasjon til vilkårlige nettsteder."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekrefte pakker"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Gir appen tillatelse til å oppdage endringer i tillitsstatusen."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Angivelse av en pålitelig agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillater appen å angi en pålitelig agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Start innstillingsmenyen til tillitsagenten."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Tillater apper å starte en aktivitet som endrer atferden til tillitsagenter."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Tilknytt en tillitsagent-tjeneste."</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Gir appen tillatelse til å knyttes til en tillitsagent-tjeneste."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Samhandling med oppdateringer og gjenopprettingssystem"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Velg året"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Jobb-<xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruke lås-til-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lås-til-app låser skjermen i en enkelt app.\n\nFor å avslutte trykker du på og holder nede nylige apper-knappen $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEI"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Start lås-til-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Avslutt lås-til-app"</string> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index a1a5357..b60d8c0 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Hiermee wordt de houder toegestaan zich te verbinden met de hoofdinterface van een achtergrond. Nooit vereist voor normale apps."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"binden aan een service voor spraakinteractie"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Hiermee kan de houder binden aan de hoofdinterface van een service voor spraakinteractie. Nooit vereist voor normale apps."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gesproken trefwoorden beheren"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Hiermee kan de houder de trefwoorden voor gesproken hotword-detectie beheren. Nooit vereist voor normale apps."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"verbinding maken met een extern display"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Hiermee wordt de houder toegestaan verbinding te maken met de hoofdinterface van een extern display. Nooit vereist voor normale apps."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"verbinden met een widgetservice"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Hiermee kan de app algemene audio-instellingen wijzigen zoals het volume en welke luidspreker wordt gebruikt voor de uitvoer."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"audio opnemen"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Hiermee kan de app audio opnemen met de microfoon. Met deze toestemming kan de app op elk moment audio opnemen, zonder om uw bevestiging te vragen."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-communicatie"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string> <string name="permlab_camera" msgid="3616391919559751192">"foto\'s en video\'s maken"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Hiermee kan de app foto\'s en video\'s maken met de camera. Met deze toestemming kan de app de camera altijd gebruiken, zonder uw bevestiging."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"indicatielampje uitschakelen wanneer camera wordt gebruikt"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Hiermee kan de app een alarm instellen in een geïnstalleerde wekkerapp. Deze functie wordt door sommige wekkerapps niet geïmplementeerd."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"voicemail toevoegen"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Hiermee kan de app berichten toevoegen aan de inbox van uw voicemail."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"alle voicemails lezen"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Hiermee kan de app al uw voicemails lezen."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"geolocatierechten voor browser aanpassen"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Hiermee kan de app de geolocatierechten van de browser aanpassen. Schadelijke apps kunnen dit gebruiken om locatiegegevens te verzenden naar willekeurige websites."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakketten controleren"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Toestaan dat een app controleert op wijzigingen in de trust-status."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Een trust-agent aanleveren."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Hiermee kan een app een trust-agent aanleveren."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Menu met instellingen voor vertrouwensagent starten."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Hiermee kan een app een activiteit starten waarmee het gedrag van de vertrouwensagent wordt gewijzigd."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Binden aan een trust-agentservice"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Toestaan dat een app wordt gebonden aan een trust-agentservice."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interactie met update- en herstelsysteem"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Jaar selecteren"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> geselecteerd"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> verwijderd"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Lock-to-app gebruiken?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Met Lock-to-app wordt het scherm vergrendeld in één app.\n\nAls u dit wilt afsluiten, houdt u de knop \'Recente apps\' $ ingedrukt"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Lock-to-app starten"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Lock-to-app afsluiten"</string> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 76f1521..f71c8a8 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Pozwala na tworzenie powiązania z interfejsem najwyższego poziomu tapety. Nieprzeznaczone dla zwykłych aplikacji."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"powiąż z interaktorem głosowym"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Zezwala na tworzenie powiązania z interfejsem najwyższego poziomu usługi interakcji głosowej. Nieprzeznaczone dla zwykłych aplikacji."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"zarządzanie frazami głosowymi"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Pozwala zarządzać frazami używanymi do wykrywania słów-kluczy w komendach głosowych. Nie powinno być nigdy potrzebne w zwykłych aplikacjach."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"powiązanie z wyświetlaczem zdalnym"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Zezwala na tworzenie powiązania z interfejsem najwyższego poziomu wyświetlacza zdalnego. Nieprzeznaczone dla zwykłych aplikacji."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"powiązanie z usługą widżetów"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Pozwala aplikacji na modyfikowanie globalnych ustawień dźwięku, takich jak głośność oraz urządzenie wyjściowe."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"nagrywanie dźwięku"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Pozwala aplikacji na nagrywanie dźwięku przez mikrofon. Aplikacja z tym uprawnieniem może nagrywać dźwięk w dowolnym momencie bez Twojego potwierdzenia."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikacja z kartą SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string> <string name="permlab_camera" msgid="3616391919559751192">"wykonywanie zdjęć i filmów wideo"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Pozwala aplikacji na robienie zdjęć i nagrywanie filmów przy użyciu aparatu. Aplikacja z tym uprawnieniem może użyć aparatu w dowolnym momencie bez Twojego potwierdzenia."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"wyłącz wskaźnik LED transmisji, gdy aparat jest w użyciu"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Pozwala aplikacji na ustawienie alarmu w zainstalowanej aplikacji budzika. Funkcja ta może nie być zaimplementowana w niektórych aplikacjach tego typu."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodawanie poczty głosowej"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Pozwala aplikacji na dodawanie wiadomości do skrzynki odbiorczej poczty głosowej."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"odczyt całej poczty głosowej"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Zezwala aplikacji na odczyt całej Twojej poczty głosowej."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modyfikowanie pozwoleń przeglądarki dotyczących lokalizacji geograficznej"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Pozwala aplikacji na modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej. Złośliwe aplikacje mogą używać tej opcji do wysyłania informacji o lokalizacji do dowolnych witryn."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"weryfikowanie pakietów"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Zezwala aplikacji na monitorowanie zmian w stanie zaufania."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Dostarczaj agenta zaufania."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Zezwala aplikacji na dostarczanie agenta zaufania."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Uruchom menu ustawień zaufanego agenta."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umożliwia aplikacji uruchamianie czynności, która zmienia działanie zaufanego agenta."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Powiąż z usługą agenta zaufania"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Zezwala aplikacji na powiązanie z usługą agenta zaufania."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interakcja z systemem odzyskiwania i aktualizacjami"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Wybierz rok"</string> <string name="item_is_selected" msgid="949687401682476608">"Wybrałeś <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> usunięte"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (praca)"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Chcesz użyć funkcji lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Po włączeniu funkcji lock-to-app na ekranie będzie wyświetlona pojedyncza aplikacja.\n\nAby wyłączyć tę funkcję, naciśnij i przytrzymaj przycisk ostatnio używanych aplikacji $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NIE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Włącz funkcję lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Wyłącz funkcję lock-to-app"</string> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 471e41f..4c5140f 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite ao titular vincular-se à interface de nível superior de uma imagem de fundo. Nunca deverá ser necessário para aplicações normais."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"vincular a um interlocutor de voz"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite que o titular vincule a interface de nível superior de um serviço de interação de voz. Nunca deverá ser necessário para aplicações normais."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gerir expressões-chave de voz"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite ao proprietário gerir as expressões-chave para a deteção de palavras de ativação de voz. Nunca deverá ser necessário para aplicações normais."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"associar a um ecrã remoto"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite ao detentor associar a interface de nível superior a um ecrã remoto. Nunca deve ser necessário para aplicações normais."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincular a um serviço de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que a aplicação modifique definições de áudio globais, tais como o volume e qual o altifalante utilizado para a saída de som."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que a aplicação grave áudio com o microfone. Esta autorização permite que a aplicação grave áudio em qualquer altura sem a confirmação do utilizador."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicação com o SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que a aplicação envie comandos para o SIM. Esta ação é muito perigosa."</string> <string name="permlab_camera" msgid="3616391919559751192">"tirar fotografias e vídeos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permite que a aplicação tire fotografias e grave vídeos com a câmara. Esta autorização permite que a aplicação utilize a câmara sem a sua confirmação em qualquer altura."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desativar LED indicador de transmissão com a câmara em utilização"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que a aplicação defina um alarme numa aplicação de despertador instalada. Algumas aplicações de despertador podem não integrar esta funcionalidade."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"adicionar correio de voz"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que a aplicação adicione mensagens à sua caixa de entrada de correio de voz."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ler todo o correio de voz"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que a aplicação leia todo o correio de voz."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificar permissões de geolocalização do Navegador"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que a aplicação modifique as permissões de geolocalização do navegador. As aplicações maliciosas podem usar isto para permitir o envio de informações de localização para Web sites arbitrárias."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar pacotes"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que uma aplicação registe alterações no trust state."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fornecer um agente fidedigno."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que uma aplicação forneça um agente fidedigno."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Inicie o menu de definições do agente de fidedignidade."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que uma aplicação inicie uma atividade que altere o comportamento do agente de fidedignidade."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vincular a um serviço de trust agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que uma aplicação fique vinculada a um serviço de trust agent."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interagir com o sistema de recuperação e de atualização"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Selecionar ano"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 0ed34e2..e677fb7 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite que o proprietário utilize interface de nível superior de um plano de fundo. Nunca deve ser necessário para aplicativos normais."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"associar a um interagente de voz"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite que o proprietário use a interface de nível superior de um serviço de interação de voz. Não deve ser necessário para aplicativos comuns."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gerenciar frases-chave de voz"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite ao titular gerenciar as frases-chave para detecção de hotword por voz. Isso nunca será necessário para aplicativos normais."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"usar uma tela remota"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite que o proprietário use a interface de nível superior de uma tela remota. Não deve ser necessário para aplicativos comuns."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"sujeitar-se a um serviço de widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que o aplicativo modifique configurações de áudio globais como volume e alto-falantes de saída."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que o aplicativo grave áudio com o microfone. Esta permissão autoriza o aplicativo a gravar áudio a qualquer momento, sem sua confirmação."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicação com sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que o aplicativo envie comandos ao SIM. Muito perigoso."</string> <string name="permlab_camera" msgid="3616391919559751192">"tirar fotos e gravar vídeos"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permite que o aplicativo tire fotos e filme vídeos com a câmera. Esta permissão autoriza o aplicativo a usar a câmera a qualquer momento sem sua confirmação."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desativar a transmissão do LED indicador quando a câmera estiver em uso"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que o aplicativo defina um alarme em um aplicativo despertador instalado. Alguns aplicativos despertador podem não implementar este recurso."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"adicionar correio de voz"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que o aplicativo adicione mensagens a sua caixa de entrada do correio de voz."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ler todo o correio de voz"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que o aplicativo leia todos os seus correios de voz."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifique as permissões de geolocalização de seu navegador"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que o aplicativo modifique as permissões de geolocalização do navegador. Aplicativos maliciosos podem usar isso para permitir o envio de informações locais para sites arbitrários."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar pacotes"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que o aplicativo detecte alterações no estado de confiança."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fornecer um agente de confiança."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que um aplicativo forneça um agente de confiança."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Abra o menu de configurações do agente de confiança."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que um aplicativo inicie uma atividade que altera o comportamento do agente de confiança."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Associar a um serviço de agente de confiança"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que o aplicativo se associe a um serviço de agente de confiança."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interagir com o sistema de atualizações e recuperação"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Selecione o ano"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> excluído"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Trabalho: <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index c1341c2..880a05d 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -634,6 +634,10 @@ <skip /> <!-- no translation found for permdesc_bindVoiceInteraction (2345721766501778101) --> <skip /> + <!-- no translation found for permlab_manageVoiceKeyphrases (1252285102392793548) --> + <skip /> + <!-- no translation found for permdesc_manageVoiceKeyphrases (8476560722907530008) --> + <skip /> <!-- no translation found for permlab_bindRemoteDisplay (1782923938029941960) --> <skip /> <!-- no translation found for permdesc_bindRemoteDisplay (1261242718727295981) --> @@ -885,6 +889,10 @@ <string name="permlab_recordAudio" msgid="3876049771427466323">"registrar audio"</string> <!-- no translation found for permdesc_recordAudio (4906839301087980680) --> <skip /> + <!-- no translation found for permlab_sim_communication (1180265879464893029) --> + <skip /> + <!-- no translation found for permdesc_sim_communication (5725159654279639498) --> + <skip /> <string name="permlab_camera" msgid="3616391919559751192">"fotografar e registrar videos"</string> <!-- no translation found for permdesc_camera (8497216524735535009) --> <skip /> @@ -1658,6 +1666,10 @@ <skip /> <!-- no translation found for permdesc_addVoicemail (6604508651428252437) --> <skip /> + <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) --> + <skip /> + <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) --> + <skip /> <!-- no translation found for permlab_writeGeolocationPermissions (5962224158955273932) --> <skip /> <!-- no translation found for permdesc_writeGeolocationPermissions (1083743234522638747) --> @@ -2182,6 +2194,10 @@ <skip /> <!-- no translation found for permdesc_provide_trust_agent (3865702641053068148) --> <skip /> + <!-- no translation found for permlab_launch_trust_agent_settings (7494179366945389098) --> + <skip /> + <!-- no translation found for permdesc_launch_trust_agent_settings (985453787420853278) --> + <skip /> <!-- no translation found for permlab_bind_trust_agent_service (8242093169457695334) --> <skip /> <!-- no translation found for permdesc_bind_trust_agent_service (7041930026024507515) --> @@ -2863,4 +2879,18 @@ <skip /> <!-- no translation found for deleted_key (7659477886625566590) --> <skip /> + <!-- no translation found for managed_profile_label_badge (2355652472854327647) --> + <skip /> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index ef5a384..b734b22 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite proprietarului să se conecteze la interfaţa de nivel superior a unei imagini de fundal. Nu ar trebui să fie niciodată necesară pentru aplicaţiile obişnuite."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"conectare la un serviciu de interacțiune vocală"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite proprietarului să se conecteze la interfața de nivel superior a unui serviciu de interacțiune vocală. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestionarea expresiilor cheie vocale"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite titularului să gestioneze expresiile cheie pentru detectarea expresiei de activare. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"conectare la un ecran la distanță"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite proprietarului să se conecteze la interfața de nivel superior a unui ecran la distanță. Nu ar trebui să fie niciodată necesară pentru aplicațiile obișnuite."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"conectare la un serviciu widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite aplicaţiei să modifice setările audio globale, cum ar fi volumul şi difuzorul care este utilizat pentru ieşire."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"înregistrare audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite aplicaţiei să efectueze înregistrări audio cu ajutorul microfonului. Cu această permisiune aplicaţia efectuează oricând înregistrări audio fără confirmare."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicare cu cardul SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string> <string name="permlab_camera" msgid="3616391919559751192">"realizarea de fotografii şi videoclipuri"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Permite aplicaţiei să realizeze fotografii şi videoclipuri cu camera foto. Cu această permisiune aplicaţia utilizează camera foto oricând şi fără confirmare."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"dezactivează ledul care indică când este utilizată camera foto"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite aplicaţiei să seteze o alarmă într-o aplicaţie de ceas cu alarmă instalată. Este posibil ca unele aplicaţii de ceas cu alarmă să nu implementeze această funcţie."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"adăugare mesagerie vocală"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite aplicaţiei să adauge mesaje în Mesaje primite în mesageria vocală."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"acces la toate mesajele vocale"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite aplicației să acceseze toate mesajele vocale."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificare permisiuni pentru locaţia geografică a browserului"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite aplicaţiei să modifice permisiunile privind locaţia geografică a browserului. Aplicaţiile rău intenţionate pot utiliza această permisiune pentru a permite trimiterea informaţiilor privind locaţia către site-uri web arbitrare."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificare pachete"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite unei aplicații să detecteze modificările în starea de încredere."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Indicați un agent de încredere."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite unei aplicații să indice un agent de încredere."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lansați meniul de setări pentru agentul de încredere."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite unei aplicații să lanseze o activitate care schimbă comportamentul agentului de încredere."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Asocierea la un serviciu „agenți de încredere”."</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite unei aplicații să se asocieze la un serviciu „agent de încredere”."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interacțiune cu sistemul de recuperare și de actualizare"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Selectați anul"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selectat"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> a fost șters"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de serviciu"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index ac68bda..f727364 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Приложение сможет подключаться к базовому интерфейсу службы обоев. Это разрешение не используется обычными приложениями."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"Подключение к службам голосового взаимодействия"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Приложение сможет подключаться к базовому интерфейсу служб голосового взаимодействия. Это разрешение обычно используется только специальными приложениями."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Управление ключевыми фразами для распознавания голосовых команд"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Управление ключевыми фразами для функции распознавания голосовых команд. Это разрешение обычно используется только специальными приложениями."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"Подключение к удаленному дисплею"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Приложение сможет подключаться к базовому интерфейсу удаленного дисплея. Это разрешение обычно используется только специальными приложениями."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"Подключение к службе виджетов"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Приложение сможет изменять системные настройки звука, например уровень громкости и активный динамик."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"Запись аудио"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Приложение сможет записывать аудио с помощью микрофона в любое время без уведомления."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"Обращение к SIM-карте"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string> <string name="permlab_camera" msgid="3616391919559751192">"Фото- и видеосъемка"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Приложение сможет снимать фотографии и видеоролики с помощью камеры в любое время без вашего разрешения."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Отключать светодиодный индикатор во время использования камеры"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Приложение сможет настраивать будильник. Функция поддерживается не во всех программах."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"Добавление голосовых сообщений"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Приложение сможет добавлять голосовые сообщения в папку \"Входящие\"."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Доступ к голосовой почте"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Чтение голосовых сообщений."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Изменение прав доступа к геоданным в браузере"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Приложение сможет изменять настройки доступа к геоданным в браузере. Вредоносные программы смогут таким образом отправлять информацию о местоположении на любые веб-сайты."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Проверка пакетов"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Приложение сможет отслеживать изменения в статусе доверия."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Доверенный агент"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Предоставление доверенных агентов."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Запуск настроек Trust Agent"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Запуск меню, позволяющего управлять настройками Trust Agent."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Подключение к службе Trust Agents"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Приложение сможет подключаться к службе Trust Agents."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Взаимодействовать с системой восстановления и обновлениями"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Выберите год"</string> <string name="item_is_selected" msgid="949687401682476608">"Выбран элемент <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Цифра <xliff:g id="KEY">%1$s</xliff:g> удалена"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Рабочий <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 20674b0..0d4ee13 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania tapety. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"viazanie na hlasovú interakciu"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania služby hlasovej interakcie. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"správa vyslovených kľúčových fráz"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Umožňuje držiteľovi spravovať kľúčové frázy pre rozpoznávanie vyslovených kľúčových slov. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"viazať na vzdialený displej"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania vzdialeného displeja. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"viazať sa k službe miniaplikácie"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Umožňuje aplikácii upraviť globálne nastavenia zvuku, ako je hlasitosť, alebo určiť, z ktorého reproduktora bude zvuk vychádzať."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrávať zvuk"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Umožňuje aplikácii zaznamenávať zvuk pomocou mikrofónu. Toto povolenie umožňuje aplikácii zaznamenávať zvuk kedykoľvek bez vášho potvrdenia."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikácia s kartou SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Umožňuje aplikácii odosielať príkazy na kartu SIM. Toto je veľmi nebezpečné povolenie."</string> <string name="permlab_camera" msgid="3616391919559751192">"fotiť a nakrúcať videá"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Umožňuje aplikácii fotografovať a nahrávať videá pomocou fotoaparátu. Toto povolenie umožňuje aplikácii používať fotoaparát kedykoľvek a bez vášho potvrdenia."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Zakázať indikátor LED prenosu pri používaní fotoaparátu"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikácii nastaviť budík v nainštalovanej aplikácii budík. Niektoré aplikácie budíka nemusia túto funkciu implementovať."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"pridať hlasovú schránku"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Umožní aplikácii pridávať správy do doručenej pošty hlasovej schránky."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"čítanie všetkých hlasových schránok"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Umožňuje aplikácii čítať všetky vaše hlasové schránky."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"zmeniť povolenia prehliadača poskytovať informácie o zemepisnej polohe"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Umožňuje aplikácii zmeniť povolenia prehliadača na poskytovanie údajov o zemepisnej polohe. Škodlivé aplikácie to môžu použiť na odosielanie informácií o polohe ľubovoľným webovým stránkam."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"overiť balíky"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Umožňuje aplikácii reagovať na zmeny stavu dôveryhodnosti."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Poskytnúť dôveryhodného agenta"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Umožňuje aplikácii poskytnúť dôveryhodného agenta."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Spustenie ponuky nastavení dôveryhodného agenta"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umožňuje aplikácii spustiť aktivitu, ktorá zmení správanie dôveryhodného agenta."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Viazanie sa na službu zástupcu dôveryhodnosti"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Umožňuje aplikácii viazať sa na službu zástupcu dôveryhodnosti."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interakcia so systémom aktualizácií a obnovenia"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string> <string name="item_is_selected" msgid="949687401682476608">"Bola vybratá položka <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Číslo <xliff:g id="KEY">%1$s</xliff:g> bolo odstránené"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Práca – <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 01c3d0f..44c523c 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Imetniku omogoča povezavo z vmesnikom ozadja najvišje ravni. Tega nikoli ni treba uporabiti za navadne programe."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"povezovanje z glasovnim interaktorjem"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Imetniku omogoča povezovanje z vmesnikom storitve glasovne interakcije najvišje ravni. Tega ni treba nikoli uporabiti za navadne aplikacije."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"upravljanje glasovnih ključnih besednih zvez"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Imetniku omogoča upravljanje ključnih besednih zvez za zaznavanje sprožilne besede. Tega ni treba nikoli uporabiti za navadne aplikacije."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"povezava z oddaljenim prikazom"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Imetniku omogoča povezovanje z vmesnikom oddaljenega prikaza najvišje ravni. Tega ni treba nikoli uporabiti za navadne aplikacije."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"poveži s storitvijo pripomočka"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Aplikaciji omogoča spreminjanje splošnih zvočnih nastavitev, na primer glasnost in kateri zvočnik se uporablja."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"snemanje zvoka"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Aplikaciji omogoča snemanje zvoka z mikrofonom. S tem dovoljenjem lahko aplikacija kadar koli snema zvok brez vaše potrditve."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"komuniciranje s kartico SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string> <string name="permlab_camera" msgid="3616391919559751192">"fotografiranje in snemanje videoposnetkov"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Aplikaciji omogoča fotografiranje in snemanje videoposnetkov s kamero. S tem dovoljenjem lahko aplikacija kadar koli uporablja kamero brez vaše potrditve."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"onemogoči LED-indikator prenašanja, ko je fotoaparat v uporabi"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Programu omogoča nastavitev alarma v nameščenem programu budilke. Nekateri programi budilke morda nimajo te funkcije."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodajanje odzivnika"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Programu omogoča dodajanje sporočil prejetim sporočilom odzivnika."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"branje vseh sporočil v odzivniku"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Aplikaciji omogoča branje vseh sporočil v odzivniku."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Spreminjanje dovoljenj za geolokacijo brskalnika"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Programu omogoča spreminjanje geolokacijskih dovoljenj v brskalniku. Zlonamerni programi lahko to izkoristijo za pošiljanje podatkov o lokaciji poljubnim spletnim mestom."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"preveri pakete"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Aplikaciji dovoli spremljanje sprememb stanja zaupanja."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Navedba posrednika zaupanja."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Aplikaciji dovoli navesti posrednika zaupanja."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Odpri meni z nastavitvami posrednika zaupanja."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Dovoli aplikaciji zagon dejavnosti, ki spremeni način delovanja posrednika zaupanja."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Povezovanje s storitvijo posrednikov zaupanja"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Aplikaciji dovoli povezovanje s storitvijo posrednikov zaupanja."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Uporaba sistema za posodobitev in obnovitev"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Izberite leto"</string> <string name="item_is_selected" msgid="949687401682476608">"Izbrano: <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Številka <xliff:g id="KEY">%1$s</xliff:g> je izbrisana"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za delo"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Želite uporabiti zaklepanje v aplikaciji?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Zaklepanje v aplikaciji zaklene zaslon v eni aplikaciji.\n\nČe želite zapustiti ta način, pritisnite in pridržite gumb za nedavne aplikacije $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ZAŽENI"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Zagon zaklepanja v aplikaciji"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Izhod iz zaklepanja v aplikaciji"</string> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 074d4f7..a07b7fa 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Дозвољава власнику да се повеже са интерфејсом позадине највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"повежи са гласовним интерактором"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Дозвољава власнику да се повеже са интерфејсом највишег нивоа услуге гласовне интеракције. Не би требало никада да буде потребно за уобичајене апликације."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"управљање усменим кључним фразама"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Дозвољава власнику да управља кључним фразама за откривање усмених актуелних речи. Никада не би требало да буде потребно за уобичајене апликације."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"повезивање са удаљеним екраном"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Дозвољава власнику да се повеже са интерфејсом удаљеног екрана највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"обавезивање на услугу виџета"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"снимање аудио записа"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Дозвољава апликацији да снима звук помоћу микрофона. Ова дозвола омогућава апликацији да снима звук у било ком тренутку без ваше потврде."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"Комуникација са SIM картицом"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string> <string name="permlab_camera" msgid="3616391919559751192">"снимање фотографија и видео снимака"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Дозвољава апликацији да снима слике и видео снимке камером. Ова дозвола омогућава апликацији да у било ком тренутку користи камеру без ваше потврде."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"онемогући пренос LED осветљења индикатора док се камера користи"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Дозвољава апликацији да подеси аларм у инсталираној апликацији будилника. Неке апликације будилника можда не примењују ову функцију."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"додавање говорне поште"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Дозвољава апликацији да додаје поруке у пријемно сандуче говорне поште."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"читај сву говорну пошту"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Дозвољава апликацији да чита говорну пошту."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"измена дозвола за географске локације Прегледача"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Дозвољава апликацији да измени дозволе Прегледача за утврђивање географске локације. Злонамерне апликације то могу да злоупотребе и искористе за слање информација о локацији насумичним веб сајтовима."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"верификовање пакета"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Дозвољава апликацији да прати промене Trust стања."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Обезбеђивање поузданог агента."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Дозвољава апликацији да обезбеди поузданог агента."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Покрени мени подешавања поузданог агента."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Дозвољава апликацији да покреће активност која мења понашање поузданог агента."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Везивање за услугу Trust agents"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Дозвољава апликацији да се веже за услугу Trust agents."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Интеракција са системом за ажурирање и опоравак"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Изаберите годину"</string> <string name="item_is_selected" msgid="949687401682476608">"Изабрали сте <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Избрисали сте <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> на послу"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 6b5431f..4db7351 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Innehavaren kan binda till den översta nivåns gränssnitt för en bakgrund. Ska inte behövas för vanliga appar."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind till en röstkomponent"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en rösttjänst. Ska inte behövas för vanliga appar."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"hantera sökfraser för röstkommandon"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Innehavaren får hantera sökfraser för identifiering av röstkommandon. Detta ska inte behövas för vanliga appar."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind till en fjärrskärm"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en fjärrskärm. Ska inte behövas för vanliga appar."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind till en widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Tillåter att appen ändrar globala ljudinställningar som volym och vilken högtalarutgång som används."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"spela in ljud"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Tillåter att appen spelar in ljud med mikrofonen. Med den här behörigheten tillåts appen att spela in ljud när som helst utan ditt godkännande."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikation"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string> <string name="permlab_camera" msgid="3616391919559751192">"ta bilder och spela in videoklipp"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Tillåter att appen tar bilder och spelar in videor med kameran. Med den här behörigheten tillåts appen att använda kameran när som helst utan ditt godkännande."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"inaktivera LED-sändningsindikator när kameran används"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillåter att appen ställer in ett alarm i en befintlig alarmapp. Vissa alarmappar har inte den här funktionen."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"lägg till röstbrevlåda"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Gör att appen lägger till meddelanden i röstbrevlådans inkorg."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"läsa alla röstmeddelanden"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Tillåter att appen läser alla dina röstmeddelanden."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Ändra geografisk plats för webbläsaren"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Tillåter att appen ändrar webbläsarens behörigheter för geografisk plats. Skadliga appar kan använda detta för att tillåta att platsinformation skickas till godtyckliga webbplatser."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"kontrollera paket"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Tillåter att en app lyssnar efter ändringar i den betrodda agentens status."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Tillhandahåll en betrodd agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillåter att en app tillhandahåller en betrodd agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Öppna inställningsmenyn för betrodda agenter."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Tillåter att en app startar en aktivitet som ändrar den betrodda agentens beteende."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind till en tjänst från en betrodd agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Tillåter att en app binds vid en tjänst från en betrodd agent."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Interagera med uppdaterings- och återställningssystemet"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Välj år"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> har markerats"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> har tagits bort"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> för arbetet"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Vill du använda Lås till app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Med funktionen Lås till app låses skärmen i en enskild app.\n\nAvsluta genom att trycka länge på knappen för de senaste apparna $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"STARTA"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Starta Lås till app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Avsluta Lås till app"</string> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 9c958ac..df51691 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Inaruhusu kishikiliaji kushurutisha kwa kusano ya kiwango cha juu cha mandhari. Haipaswi kamwe kuhitajika kwa programu za kawaida."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bandika kwenye mwingiliano wa sauti"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Humruhusu mmiliki kubandika kwenye kiolesura cha hali ya juu cha huduma ya muingiliano wa sauti. Isihitajike kamwe kwa programu za kawaida."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Simamia misimbo kuu ya sauti"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Huruhusu mmiliki kusimamia misimbo kuu ya utambuzi wa neno tekelezi la sauti. Haipaswi kuhitajika kwa programu za kawaida kamwe."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"fungisha kwenye mwonekano wa mbali"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Huruhusu mtumiaji kujifungia kiolesura cha kiwango cha juu cha mwonekano wa mbali. Haipaswi kuhitajika kwa programu za kawaida."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"funga kwenye huduma ya widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Inaruhusu programu kurekebisha mipangilio ya sauti kila mahali kama vile sauti na ni kipaza sauti kipi ambacho kinatumika kwa kutoa."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"kurekodi sauti"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Inaruhusu programu kurekodi sauti kwa kinasa sauti. Idhini hii inaruhusu programu kurekodi sauti wakati wowote bila ya uthibitisho wako."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"mawasiliano ya sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string> <string name="permlab_camera" msgid="3616391919559751192">"Kupiga picha na kurekodi video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Inaruhusu programu kupiga picha na video kwa kamera. Kibali hiki kinaruhusu programu kutumia kamera kwa wakati wowote bila uthibitisho wako."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"zima LED ya kisambaza kiashirio wakati kamera inatumika"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Inaruhusu programu kuweka kengele katika programu iliyosakinishwa ya kengele. Programu zingine za kengele zinawezakosa kutekeleza kipengee hiki."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"ongeza barua ya sauti"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Huruhusu programu kuongeza mawasiliano kwenye kikasha cha ujumbe wa sauti."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"soma ujumbe wote wa sauti"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Huruhusu programu isome ujumbe wako wote wa sauti."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Rekebisha vibali vya Kivinjari cha eneo la jio"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Inaruhusu programu kurekebisha ruhusa za eneo la jio za kivinjari. Programu hasidi zinaweza tumia hii kuruhusu kutuma taarifa ya eneo kwa wavuti holela."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"thibitisha furushi"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Huruhusu programu kusikiliza mabadiliko katika hali ya kuaminiwa."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Toa wakala wa uaminifu."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Huruhusu programu kutoa wakala wa uaminifu."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Fungua menyu ya mipangilio ya madalali wa kuaminiwa."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Huruhusu programu kufungua kitendo ambacho hubadilisha tabia ya madalali wa kuaminiwa."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Funga kwenye huduma ya dalali wa kuaminiwa"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Huruhusu programu kufungamanisha kwenye huduma ya dalali wa kuaminiwa."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Ingiliana na sasisho na mfumo wa kurejesha"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Chagua mwaka"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kimechaguliwa"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> kimefutwa"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kazi <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Ungependa kutumia lazimisha kutumia programu?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lazimisha kutumia programu huonyeshwa katika programu moja. \n\n Ili uondoke bonyeza na ushikilie kitufe cha programu za hivi majuzi $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"HAPANA"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"ANZA"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Anzisha Lazimisha kutumia programu"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Ondoka kwenye Lazimisha kutumia programu"</string> </resources> diff --git a/core/res/res/values-television/themes.xml b/core/res/res/values-television/themes.xml index f92ff40..3333f1b 100644 --- a/core/res/res/values-television/themes.xml +++ b/core/res/res/values-television/themes.xml @@ -16,8 +16,13 @@ <resources> <style name="Theme.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> <style name="Theme.Dialog.AppError" parent="Theme.Leanback.Dialog.AppError" /> + <style name="Theme.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" /> <style name="Theme.Holo.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> + <style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" /> <style name="Theme.Holo.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> + <style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" /> <style name="Theme.Material.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> + <style name="Theme.Material.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" /> <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> + <style name="Theme.Material.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" /> </resources> diff --git a/core/res/res/values-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml index e01caa3..1938675 100644 --- a/core/res/res/values-television/themes_device_defaults.xml +++ b/core/res/res/values-television/themes_device_defaults.xml @@ -16,4 +16,6 @@ <resources> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> + <style name="Theme.DeviceDefault.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" /> + <style name="Theme.DeviceDefault.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" /> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 1522f2e..a0f0bb5 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"อนุญาตให้ผู้ใช้เชื่อมโยงกับส่วนติดต่อผู้ใช้ระดับสูงสุดของวอลเปเปอร์ ไม่ควรต้องใช้สำหรับแอปพลิเคชันทั่วไป"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"เชื่อมโยงกับโปรแกรมโต้ตอบด้วยเสียง"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"อนุญาตให้ผู้ใช้อุปกรณ์เชื่อมโยงกับอินเทอร์เฟซระดับบนสุดของบริการโต้ตอบด้วยเสียง ไม่จำเป็นสำหรับแอปทั่วไป"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"จัดการเสียงพูดวลีคำหลัก"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"ให้แอปสามารถจัดการวลีคำหลักสำหรับการตรวจหาเสียงพูดคำที่นิยม ไม่จำเป็นสำหรับแอปทั่วไป"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ผูกกับจอแสดงผลระยะไกล"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"อนุญาตให้ผู้ใช้ผูกกับอินเทอร์เฟซระดับสูงสุดของจอแสดงผลระยะไกล ซึ่งแอปพลิเคชันทั่วไปไม่จำเป็นต้องใช้"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"เชื่อมโยงกับบริการวิดเจ็ต"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"อนุญาตให้แอปพลิเคชันปรับเปลี่ยนการตั้งค่าเสียงทั้งหมดได้ เช่น ระดับเสียงและลำโพงที่จะใช้งาน"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"บันทึกเสียง"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"อนุญาตให้แอปพลิเคชันบันทึกเสียงด้วยไมโครโฟน การอนุญาตนี้ทำให้แอปพลิเคชันสามารถบันทึกเสียงได้ทุกเมื่อโดยไม่ต้องรอการยืนยันจากคุณ"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"การสื่อสารกับ SIM"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string> <string name="permlab_camera" msgid="3616391919559751192">"ถ่ายภาพและวิดีโอ"</string> <string name="permdesc_camera" msgid="8497216524735535009">"อนุญาตให้แอปพลิเคชันถ่ายภาพและวิดีโอด้วยกล้องถ่ายรูปนี้ การอนุญาตนี้จะทำให้แอปพลิเคชันสามารถใช้กล้องถ่ายรูปได้ทุกเมื่อโดยไม่ต้องรอการยืนยันจากคุณ"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ปิดไฟสัญญาณ LED เมื่อใช้งานกล้อง"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"อนุญาตให้แอปพลิเคชันตั้งเวลาปลุกในแอปพลิเคชันนาฬิกาปลุกที่ติดตั้ง แอปพลิเคชันนาฬิกาปลุกบางรายการอาจไม่ใช้คุณลักษณะนี้"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"เพิ่มข้อวามเสียง"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"อนุญาตให้แอปพลิเคชันเพิ่มข้อความลงในกล่องข้อความเสียงของคุณ"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"อ่านข้อความเสียงทั้งหมด"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"อนุญาตให้แอปอ่านข้อความเสียงทั้งหมดของคุณ"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"แก้ไขการอนุญาตเกี่ยวกับการระบุตำแหน่งทางภูมิศาสตร์ของเบราว์เซอร์"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"อนุญาตให้แอปพลิเคชันแก้ไขการอนุญาตตำแหน่งทางภูมิศาสตร์ของเบราว์เซอร์ แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ในการส่งข้อมูลตำแหน่งไปยังเว็บไซต์ต่างๆ ได้ตามต้องการ"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ยืนยันแพ็กเกจ"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"อนุญาตให้แอปพลิเคชันฟังการเปลี่ยนแปลงที่มีต่อสถานะความน่าเชื่อถือ"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"เสนอตัวแทนที่เชื่อถือได้"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ช่วยให้แอปพลิเคชันสามารถเสนอตัวแทนที่เชื่อถือได้"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"เปิดเมนูการตั้งค่าตัวแทนที่เชื่อถือได้"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"อนุญาตให้แอปพลิเคชันเปิดกิจกรรมที่เปลี่ยนพฤติกรรมตัวแทนที่เชื่อถือได้"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ผูกกับบริการของตัวแทนที่เชื่อถือได้"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"อนุญาตให้แอปพลิเคชันผูกกับบริการของตัวแทนที่เชื่อถือได้"</string> <string name="permlab_recovery" msgid="3157024487744125846">"โต้ตอบกับการอัปเดตและระบบการกู้คืน"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"เลือกปี"</string> <string name="item_is_selected" msgid="949687401682476608">"เลือก <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"ลบ <xliff:g id="KEY">%1$s</xliff:g> แล้ว"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g>ที่ทำงาน"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"ใช้การล็อกแอปไหม"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"การล็อกแอปจะล็อกการแสดงไว้ในแอปเดียว\n\nหากต้องการออก ให้กดปุ่มแอปล่าสุด $ ค้างไว้"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"ไม่"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"เริ่มต้น"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"เริ่มใช้การล็อกแอป"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"ออกจากการล็อกแอป"</string> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 330bbe7..2b0eb8c 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Pinapayagan ang may-hawak na sumailalim sa nangungunang interface ng isang wallpaper. Hindi kailanman dapat na kailanganin para sa normal na apps."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"i-bind sa isang voice interactor"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Nagbibigay-daan sa may-hawak na i-bind ang top-level na interface ng isang serbisyo sa pakikipag-ugnayan gamit ang boses. Hindi kailanman dapat kailanganin ng mga normal na app."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"pamahalaan ang mga keyphrase ng boses"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Pinapayagan ang holder na pamahalaan ang mga keyphrase para sa pagtukoy ng hotword ng boses. Hindi dapat kailanman kailanganin para sa mga normal na app."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"magpasaklaw sa isang remote na display"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Binibigyang-daan ang may-hawak na masaklaw ang pinakamataas na antas ng interface ng isang remote na display. Hindi dapat kailanman kailanganin ng normal na apps."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"itali sa serbisyo ng widget"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Pinapayagan ang app na baguhin ang mga pandaigdigang setting ng audio gaya ng volume at kung aling speaker ang ginagamit para sa output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"mag-record ng audio"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Pinapayagan ang app na mag-record ng audio gamit ang mikropono. Pinapayagan ng pahintulot na ito ang app na mag-record ng audio anumang oras nang wala ng iyong kumpirmasyon."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"pag-uusap sa sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string> <string name="permlab_camera" msgid="3616391919559751192">"kumuha ng mga larawan at video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Pinapayagan ang app na kumuha ng mga larawan at video gamit ang camera. Pinapayagan ng pahintulot na ito ang app na gamitin ang camera anumang oras nang wala ng iyong kumpirmasyon."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"i-disable ang LED na tagapagpahiwatig kapag ginagamit ang camera"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Pinapayagan ang app na magtakda ng alarm sa isang naka-install na app ng alarm clock. Maaaring hindi ipatupad ng ilang apps ng alarm clock ang tampok na ito."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"magdagdag ng voicemail"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Pinapayagan ang app na magdagdag ng mga mensahe sa iyong inbox ng voicemail."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"basahin ang lahat ng voicemail"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Pinapayagan ang app na basahin ang lahat ng iyong voicemail."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"baguhin ang mga pahintulot ng geolocation ng Browser"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Pinapayagan ang app na baguhin ang mga pahintulot sa geolocation ng Browser. Maaari itong gamitin ng nakakahamak na apps upang payagan ang pagpapadala ng impormasyon ng lokasyon sa mga hindi tukoy na web site."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"i-verify ang mga package"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Pinapayagan ang isang application na makinig para sa mga pagbabago sa estado ng trust."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Magbigay ng trust agent."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Pinapayagan ang isang application na magbigay ng trust agent."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Ilunsad ang menu ng mga setting ng trust agent."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Nagbibigay-daan sa isang application na maglunsad ng aktibidad na nagbabago sa pagkilos ng trust agent."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Sumailalim sa isang serbisyo ng trust agent"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Pinapayagan ang isang application na sumailalim sa isang serbisyo ng trust agent."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Makipag-ugnay sa system ng pag-update at pagbawi"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Pumili ng taon"</string> <string name="item_is_selected" msgid="949687401682476608">"Napili ang <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Tinanggal ang <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> sa Trabaho"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Gagamitin ang lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Nila-lock ng Lock-to-app ang display sa iisang app.\n\nUpang lumabas pindutin nang matagal ang button ng mga kamakailang app $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"HINDI"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"SIMULAN"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Simulan ang Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Lumabas sa Lock-to-app"</string> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index d235ca3..6c3ec18 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Cihazın sahibine, duvar kağıdının en üst düzey arayüzüne bağlanma izni verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bir ses etkileşimi hizmetine bağlanma"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"İzin sahibinin, bir ses etkileşimi hizmetine ait üst düzey arayüze bağlanmasına izin verir. Normal uygulamalar için hiçbir zaman gerekli değildir."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"sesli anahtar ifadeleri yönet"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"İzin sahibine, söylenen özel kelimeleri algılamak için anahtar ifadeleri yönetme izni verir. Normal uygulamalar için hiçbir zaman gerekli olmamalıdır."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"uzak ekrana bağlan"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"İzin sahibine, bir uzak ekranın en üst düzey arayüzüne bağlanma izni verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bir widget hizmetine bağla"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Uygulamaya ses düzeyi ve ses çıkışı için kullanılan hoparlör gibi genel ses ayarlarını değiştirme izni verir."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ses kaydet"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Uygulamaya mikrofonla ses kaydetme izni verir. Bu izin, uygulamanın istediği zaman onayınız olmadan ses kaydetmesine olanak sağlar."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"sim iletişimi"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string> <string name="permlab_camera" msgid="3616391919559751192">"resim çekme ve görüntü kaydetme"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Uygulamaya kamerayla fotoğraf ve video çekme izni verir. Bu izin, uygulamanın sizin onayınız olmadan istediği zaman kamerayı kullanmasına olanak sağlar."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Kamera kullanımda iken iletim göstergesi LED\'ini devre dışı bırak"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Uygulamaya, çalar saat uygulamasının alarmını ayarlama izni verir. Bazı çalar saat uygulamaları bu özelliği uygulayamayabilir."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"sesli mesaj ekle"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Uygulamaya, sesli mesaj gelen kutunuza mesaj ekleme izni verir."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"tüm sesli mesajları oku"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Uygulamanın tüm sesli mesajlarınızı okumasına izin verir."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Tarayıcı\'nın coğrafi konum izinlerini değiştir"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Uygulamaya, Tarayıcı\'nın coğrafi konum izinlerini değiştirme izni verir. Kötü amaçlı uygulamalar keyfi web sitelerine konum bilgisi gönderilmesini sağlamak için bunu kullanabilirler."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"paketleri doğrula"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Bir uygulamanın, güven durumundaki değişiklikleri dinlemesine izin verir."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Güven aracısı sağlama."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Bir uygulamanın güven aracısı sağlamasına izin verir."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Güven aracısı ayarlar menüsünü başlat."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Bir uygulamanın güven aracısının çalışma biçimini değiştiren bir etkinlik başlatmasına izin verir."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Güven aracı hizmetine bağlan"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Bir uygulamanın, güven aracı hizmetine bağlanmasına izin verir."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Güncelleme ve kurtarma sistemiyle etkileşim kur"</string> @@ -1717,4 +1725,17 @@ <string name="select_year" msgid="7952052866994196170">"Yılı seçin"</string> <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seçildi"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> silindi"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (İş)"</string> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index ffe735c..f439595 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Дозволяє власнику прив’язуватися до інтерфейсу верхнього рівня фонового малюнка. Ніколи не застосовується для звичайних програм."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"підключитися до служби голосової взаємодії"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Додаток зможе підключатися до інтерфейсу верхнього рівня служби голосової взаємодії. Звичайні додатки ніколи не використовують цей дозвіл."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"керувати голосовими ключовими фразами"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Власник може керувати ключовими фразами, які розпізнаватимуться як голосові команди швидкого запуску. Звичайні додатки ніколи не використовують цей дозвіл."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"прив’язуватися до віддаленого екрана"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Дозволяє власникові прив’язуватися до інтерфейсу верхнього рівня віддаленого екрана. Ніколи не застосовується для звичайних програм."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"прив\'язувати до служби віджетів"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Дозволяє програмі змінювати загальні налаштування звуку, як-от гучність і динамік, який використовується для виводу сигналу."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"запис-ти аудіо"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Дозволяє програмі записувати звук за допомогою мікрофона. Такий дозвіл дає програмі змогу будь-коли записувати звук без вашого підтвердження."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"комунікація із SIM-картою"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string> <string name="permlab_camera" msgid="3616391919559751192">"фотограф. та знімати відео"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Дозволяє програмі фотографувати та знімати відео за допомогою камери. Такий дозвіл дає програмі змогу будь-коли використовувати камеру без вашого підтвердження."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"вимикати світлодіодний індикатор передавання, коли використовується камера"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Дозволяє програмі налаштовувати сигнал у встановленій програмі будильника. У деяких програмах будильника ця функція може не застосовуватися."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"додавати голосову пошту"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Дозволяє програмі додавати повідомлення в папку \"Вхідні\" голосової пошти."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"читати всі голосові повідомлення"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Додаток може читати всі голосові повідомлення."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"змінювати дозволи географічного місцезнаходження у веб-переглядачі"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Дозволяє програмі змінювати дозволи географічного місцезнаходження у веб-переглядачі. Шкідливі програми можуть використовувати це, щоб дозволяти надсилати інформацію про місцезнаходження довільним веб-сайтам."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"перевіряти пакети"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Дозволяє додатку відстежувати зміни в стані довіри."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Призначення довірчого агента."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Додаток може призначати довірчого агента."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Запустити меню налаштувань довірчого агента"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Додаток може запускати функцію, яка змінює поведінку довірчого агента."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Прив’язуватися до служби довірчих агентів"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Дозволяє додатку прив’язуватися до служби довірчих агентів."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Взаємодіяти з оновленнями системи та системою відновлення."</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Виберіть рік"</string> <string name="item_is_selected" msgid="949687401682476608">"Вибрано: <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> видалено"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Робоча <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Використовувати блокування в додатку?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Блокування в додатку блокує дисплей у певному додатку.\n\nЩоб вимкнути, натисніть і втримуйте кнопку останніх додатків ($)"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"НІ"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"УВІМКНУТИ"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Увімкнути блокування в додатку"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Вимкнути блокування в додатку"</string> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 11b7519..9d8905a 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của hình nền. Không cần thiết cho các ứng dụng thông thường."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"liên kết với trình tương tác bằng giọng nói"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của dịch vụ tương tác bằng giọng nói. Không cần thiết cho các ứng dụng thông thường."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"quản lý cụm từ khóa bằng giọng nói"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Cho phép chủ sở hữu quản lý các cụm từ khóa để phát hiện từ nóng bằng giọng nói. Không bao giờ cần cho ứng dụng thông thường."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"liên kết với màn hình từ xa"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của màn hình từ xa. Không cần thiết cho các ứng dụng thông thường."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"liên kết với dịch vụ tiện ích con"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Cho phép ứng dụng sửa đổi cài đặt âm thanh chung chẳng hạn như âm lượng và loa nào được sử dụng cho thiết bị ra."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ghi âm"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Cho phép ứng dụng ghi âm bằng micrô. Quyền này cho phép ứng dụng ghi âm bất kỳ lúc nào mà không cần sự xác nhận của bạn."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"liên lạc qua sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string> <string name="permlab_camera" msgid="3616391919559751192">"chụp ảnh và quay video"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Cho phép ứng dụng chụp ảnh và quay video bằng máy ảnh. Quyền này cho phép ứng dụng sử dụng máy ảnh bất kỳ lúc nào mà không cần sự xác nhận của bạn."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"vô hiệu hóa tính năng phát đèn LED chỉ báo khi máy ảnh đang được sử dụng"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Cho phép ứng dụng đặt báo thức trong ứng dụng đồng hồ báo thức được cài đặt. Một số ứng dụng đồng hồ báo thức có thể không thực thi tính năng này."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"thêm thư thoại"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Cho phép ứng dụng thêm thông báo vào hộp thư thoại đến của bạn."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"đọc tất cả các thư thoại"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Cho phép ứng dụng này đọc tất cả các thư thoại của bạn."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"sửa đổi các quyền về vị trí địa lý của Trình duyệt"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Cho phép ứng dụng sửa đổi cấp phép vị trí địa lý của Trình duyệt. Ứng dụng độc hại có thể lợi dụng quyền này để cho phép gửi thông tin vị trí tới các trang web tùy ý."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"xác minh gói"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Cho phép ứng dụng quan sát các thay đổi ở trạng thái đáng tin cậy."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Cung cấp tác nhân đáng tin cậy."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Cho phép ứng dụng cung cấp tác nhân đáng tin cậy."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Chạy menu cài đặt đại lý đáng tin cậy."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Cho phép ứng dụng chạy hoạt động thay đổi hoạt động của đại lý đáng tin cậy."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Liên kết với một dịch vụ của đại lý đáng tin cậy"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Cho phép ứng dụng liên kết với một dịch vụ của đại lý đáng tin cậy."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Tương tác với hệ thống khôi phục và bản cập nhật"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Chọn năm"</string> <string name="item_is_selected" msgid="949687401682476608">"Đã chọn <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"Đã xóa <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> làm việc"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Sử dụng lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app sẽ khóa màn hình trong một ứng dụng.\n\nĐể thoát, nhấn và giữ nút ứng dụng gần đây $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"KHÔNG"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"BẮT ĐẦU"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Bắt đầu Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Thoát Lock-to-app"</string> </resources> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 8d82a17..6052fb0 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -36,4 +36,8 @@ <!-- Maximum velocity to initiate a fling, as measured in dips per second. --> <dimen name="config_viewMaxFlingVelocity">8000dp</dimen> + <!-- Number of notifications to keep in the notification service historical archive. + Reduced intentionally for watches to retain minimal memory footprint --> + <integer name="config_notificationServiceArchiveSize">1</integer> + </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 55ecc3a..eb1cb3b 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允许用户绑定到壁纸的顶级接口。普通应用绝不需要此权限。"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"绑定到语音互动器"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允许应用绑定到语音互动服务的顶级接口。普通应用绝不需要此权限。"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理语音关键短语"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允许应用管理用于语音启动指令检测的关键短语。普通应用绝不需要此权限。"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"绑定至远程显示屏"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允许应用绑定至远程显示屏的顶级接口。普通应用绝不需要此权限。"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"绑定到小部件服务"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"允许该应用使用麦克风录制音频。此权限可让该应用不经您的确认即可随时录制音频。"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM卡通信"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string> <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string> <string name="permdesc_camera" msgid="8497216524735535009">"允许该应用使用相机拍摄照片和视频。此权限可让该应用随时使用相机,而无需您的确认。"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"在相机使用过程中停用传输指示灯 LED"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"允许应用在已安装的闹钟应用中设置闹钟。有些闹钟应用可能无法实现此功能。"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"添加语音邮件"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允许应用向您的语音信箱收件箱添加邮件。"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"读取所有语音邮件"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允许应用读取您所有的语音邮件。"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改“浏览器”地理位置的权限"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允许应用修改“浏览器”的地理位置权限。恶意应用可能借此向任意网站发送位置信息。"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"验证软件包"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"允许应用检测信任状态的变化。"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理。"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允许应用提供信任的代理。"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"启动信任的代理设置菜单。"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允许应用启动可变更信任的代理行为的活动。"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"绑定至信任的代理服务"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允许应用绑定至信任的代理服务。"</string> <string name="permlab_recovery" msgid="3157024487744125846">"与更新和恢复系统互动"</string> @@ -1717,4 +1725,18 @@ <string name="select_year" msgid="7952052866994196170">"选择年份"</string> <string name="item_is_selected" msgid="949687401682476608">"已选择<xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"已删除<xliff:g id="KEY">%1$s</xliff:g>"</string> + <!-- no translation found for managed_profile_label_badge (2355652472854327647) --> + <skip /> + <!-- no translation found for lock_to_app_title (5895142291937470019) --> + <skip /> + <!-- no translation found for lock_to_app_description (8597199033462406175) --> + <skip /> + <!-- no translation found for lock_to_app_negative (8522854387366288195) --> + <skip /> + <!-- no translation found for lock_to_app_positive (7085139175671313864) --> + <skip /> + <!-- no translation found for lock_to_app_start (8889002974248178076) --> + <skip /> + <!-- no translation found for lock_to_app_exit (7033017307788432861) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index b58a58a..2db1ba6 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允許應用程式繫結至桌布的頂層介面 (不建議一般應用程式使用)。"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"繫結至語音互動器"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允許應用程式繫結至語音互動服務的頂層介面,但一般應用程式並不需要使用。"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理語音關鍵字句"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允許應用程式管理語音關鍵字句,用於偵測語音啟動字詞 (一般應用程式並不需要)。"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"繫結至遠端螢幕"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允許應用程式繫結至遠端屏螢的頂層介面 (不建議一般應用程式使用)。"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"繫結至小工具服務"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允許應用程式修改全域音頻設定,例如音量和用於輸出的喇叭。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音效"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"允許應用程式使用麥克風錄音。這項權限允許應用程式隨時錄音,而不需經您確認。"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 卡通訊"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string> <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string> <string name="permdesc_camera" msgid="8497216524735535009">"允許應用程式使用相機拍照和錄影。這項權限允許應用程式隨時使用相機,而不需經您確認。"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"使用相機時停用傳輸指示燈"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能沒有這項功能。"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"新增留言"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息加到您的留言信箱收件箱。"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理資訊權限。惡意應用程式可能會藉此允許將您的位置資訊任意傳送給某些網站。"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"允許應用程式聽取信任狀態的變更。"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理程式。"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允許應用程式提供信任的代理程式。"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"啟動信任的代理程式設定選單。"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允許應用程式啟動可變更信任的代理程式行為的活動。"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"繫結至信任的代理程式服務"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允許應用程式繫結至信任的代理程式服務。"</string> <string name="permlab_recovery" msgid="3157024487744125846">"與更新和復原系統互動"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"選取年份"</string> <string name="item_is_selected" msgid="949687401682476608">"已選取<xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 已刪除"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index f7dae2c..d362084 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允許應用程式繫結至桌布的頂層介面 (一般應用程式不需使用)。"</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"繫結至語音互動器"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允許應用程式繫結至語音互動服務的頂層介面 (一般應用程式並不需要)。"</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理語音關鍵字句"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允許應用程式管理語音關鍵字句,用於偵測語音啟動字詞 (一般應用程式並不需要)。"</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"繫結至遠端螢幕"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允許應用程式繫結至遠端螢幕的頂層介面 (一般應用程式不需使用)。"</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"繫結至小工具服務"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允許應用程式修改全域音訊設定,例如音量和用來輸出的喇叭。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"允許應用程式使用麥克風錄音。這項權限可讓應用程式隨時錄音,不需經過您的確認。"</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 卡通訊"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string> <string name="permlab_camera" msgid="3616391919559751192">"拍攝相片和影片"</string> <string name="permdesc_camera" msgid="8497216524735535009">"允許應用程式使用相機拍照和錄影。這項權限可讓應用程式隨時使用相機,而不需請求您進行確認。"</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"使用攝影機時停用傳輸指示器 LED"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能無法執行這項功能。"</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"新增語音留言"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息新增至您的語音信箱收件匣。"</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理位置權限。請注意,惡意應用程式可能利用此功能允許將您的位置資訊任意傳送給某些網站。"</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"允許應用程式接聽信任狀態變更。"</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理程式。"</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允許應用程式提供信任的代理程式。"</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"啟動信任的代理程式設定選單。"</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允許應用程式啟動可變更信任的代理程式行為的活動。"</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"繫結至信任的代理程式服務"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允許應用程式繫結至信任的代理程式服務。"</string> <string name="permlab_recovery" msgid="3157024487744125846">"與更新和還原系統互動"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"選取年份"</string> <string name="item_is_selected" msgid="949687401682476608">"已選取 <xliff:g id="ITEM">%1$s</xliff:g>"</string> <string name="deleted_key" msgid="7659477886625566590">"已刪除 <xliff:g id="KEY">%1$s</xliff:g>"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7d9e36a..23d2966 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -400,6 +400,8 @@ <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ivumela umbambi ukuhlanganisa uxhumano nomsebenzisi kwezinga eliphezulu lwephephadonga. Akusoze kwadingeka kwezinhlelo zokusebenza ezivamile."</string> <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"hlanganisa kusisebenzisani sezwi"</string> <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ivumela umbambi ukuhlanganisa isixhumi esibonakalayo sesevisi yokusebenzisana yezwi. Akufanele kudingekele izinhlelo zokusebenza ezivamile."</string> + <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"phatha amabinzana angukhiye wezwi"</string> + <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ivumela isibambi ukuthi siphathe amabinzana angukhiye wokutholwa kwe-hotword yezwi. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string> <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bophezela kusibonisi sesilawuli kude"</string> <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ivumela umbambi ukuhlanganisa isixhumi esibonakalayo esisezingeni eliphezulu sesibonisi sesilawuli kude. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string> <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bophezela kube isevisi yesinqunjana"</string> @@ -532,6 +534,8 @@ <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ivumela uhlelo lokusebenza ukushintsha izilungiselelo zomsindo we-global njengevolomu nokuthi isiphi isipika esisetshenziselwa okukhiphayo."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"qopha umsindo"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ivumela uhlelo lokusebenza ukurekhoda umsindo nge-microphone. Le mvume ivumela uhlelo lokusebenza ukuqopha umsindo noma kunini ngaphandle kokuqinisekisa kwakho."</string> + <string name="permlab_sim_communication" msgid="1180265879464893029">"uxhumano le-sim"</string> + <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string> <string name="permlab_camera" msgid="3616391919559751192">"thatha izithombe namavidiyo"</string> <string name="permdesc_camera" msgid="8497216524735535009">"Ivumela uhlelo lokusebenza ukuthatha izithombe namavidiyo ngekhamera. Le mvume ivumela uhlelo lokusebenza ukusebenzisa ikhamera nganoma isiphi isikhathi ngaphandle kwemvume yakho."</string> <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"khubaza i-LED yesikhombi sokudlulisa uma ikhamera isebenza"</string> @@ -997,6 +1001,8 @@ <string name="permdesc_setAlarm" msgid="316392039157473848">"Ivumela uhlelo lokusebenza ukuthi isethe i-alamu ensizeni efkiwe ye-alamu. Ezinye izinhlelo zokusebenza ze-alamu kungenzeka zingakusebenzisi lokho."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"engeza imeyili yezwi"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ivumela uhlelo lokusebenza ukwengeza imiyalezo kwibhokisi lakho lemeyili yezwi."</string> + <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"funda wonke amavoyisimeyili"</string> + <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ivumela uhlelo lokusebenza ukuthi lufunde wonke amavoyisimeyili wakho."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Gugula izimvume zendawo Yesiphequluli"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ivumela uhlelo lokusebenza ukuthi iguqule izimvume eziphathelene nezindawo Zesiphequluli. Izuhlelo lokusebenza eziyingozi zingasebenzisa lokhu ukuvumela ukuvumela imininingwane yendawo kunoma imaphi amawebusayithi."</string> <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"qinisekisa amaphakheji"</string> @@ -1358,6 +1364,8 @@ <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ivumela uhlelo lokusebenza ukuthi lilalelele izinguquko kusimo sethemba."</string> <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Nikeza umsebenzeli owethembekile."</string> <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ivumela uhlelo lokusebenza ukunikeza umsebenzeli owethembekile."</string> + <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Qalisa imenyu yezilungiselelo zomsebenzeli wethemba."</string> + <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ivumela uhlelo lokusebenza ukuthi luqalise umsebenzi oguqula ukuziphatha komsebenzeli wethemba."</string> <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bophezela kusevisi yomenzeli wethemba"</string> <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ivumela uhlelo lokusebenza ukuthi libophezeleke kusevisi yomenzeli wethemba."</string> <string name="permlab_recovery" msgid="3157024487744125846">"Ixhumana nesibuyekezo nesistimu yokutakula"</string> @@ -1717,4 +1725,11 @@ <string name="select_year" msgid="7952052866994196170">"Khetha unyaka"</string> <string name="item_is_selected" msgid="949687401682476608">"I-<xliff:g id="ITEM">%1$s</xliff:g> ekhethiwe"</string> <string name="deleted_key" msgid="7659477886625566590">"I-<xliff:g id="KEY">%1$s</xliff:g> isusiwe"</string> + <string name="managed_profile_label_badge" msgid="2355652472854327647">"Umsebenzi <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="lock_to_app_title" msgid="5895142291937470019">"Sebenzisa isikhiya sohlelo lokusebenza?"</string> + <string name="lock_to_app_description" msgid="8597199033462406175">"Isikhiya kuhlelo lokusebenza sikhiyela isibonisi kuhlelo lokusebenza olulodwa.\n\nUkuze uphume cindezela uphinde ubambe inkinobho yezinhlelo zokusebenza zakamuva engu-$"</string> + <string name="lock_to_app_negative" msgid="8522854387366288195">"CHA"</string> + <string name="lock_to_app_positive" msgid="7085139175671313864">"QALA"</string> + <string name="lock_to_app_start" msgid="8889002974248178076">"Qala Isikhiya sohlelo lokusebenza"</string> + <string name="lock_to_app_exit" msgid="7033017307788432861">"Phuma kusikhiya sohlelo lokusebenza"</string> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index ed648fb..a8a4d7a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -496,6 +496,12 @@ <!-- Internal layout used internally for window decor --> <attr name="windowActionBarFullscreenDecorLayout" format="reference" /> + <!-- The duration, in milliseconds, of the window background fade duration + when transitioning into or away from an Activity when called with an + Activity Transition. Corresponds to + {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. --> + <attr name="windowTransitionBackgroundFadeDuration" format="integer"/> + <!-- ============ --> <!-- Alert Dialog styles --> <!-- ============ --> @@ -828,6 +834,8 @@ <attr name="preferenceFragmentPaddingSide" format="dimension" /> <!-- Default style for switch preferences. --> <attr name="switchPreferenceStyle" format="reference" /> + <!-- Default style for seekbar preferences. --> + <attr name="seekBarPreferenceStyle" format="reference" /> <!-- ============================ --> <!-- Text selection handle styles --> @@ -1813,6 +1821,11 @@ Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. --> <attr name="navigationBarColor" format="color" /> + <!-- The duration, in milliseconds, of the window background fade duration + when transitioning into or away from an Activity when called with an + Activity Transition. Corresponds to + {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. --> + <attr name="windowTransitionBackgroundFadeDuration" /> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> @@ -2411,7 +2424,7 @@ <!-- Names a View such that it can be identified for Transitions. Names should be unique in the View hierarchy. --> - <attr name="viewName" format="string" /> + <attr name="transitionName" format="string" /> <!-- Specifies that this view should permit nested scrolling within a compatible ancestor view. --> @@ -2419,6 +2432,30 @@ <!-- Sets the state-based animator for the View. --> <attr name="stateListAnimator" format="reference"/> + + <!-- Tint to apply to the background. --> + <attr name="backgroundTint" format="color" /> + + <!-- Blending mode used to apply the background tint. --> + <attr name="backgroundTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -3008,7 +3045,29 @@ <!-- Indicates the initial checked state of this button. --> <attr name="checked" format="boolean" /> <!-- Drawable used for the button graphic (e.g. checkbox, radio button, etc). --> - <attr name="button" format="reference"/> + <attr name="button" format="reference" /> + <!-- Tint to apply to the button graphic. --> + <attr name="buttonTint" format="color" /> + <!-- Blending mode used to apply the button graphic tint. --> + <attr name="buttonTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> </declare-styleable> <declare-styleable name="CheckedTextView"> <!-- Indicates the initial checked state of this text. --> @@ -3093,6 +3152,28 @@ <!-- Determines whether to measure all children or just those in the VISIBLE or INVISIBLE state when measuring. Defaults to false. --> <attr name="measureAllChildren" format="boolean" /> + <!-- Tint to apply to the foreground. --> + <attr name="foregroundTint" format="color" /> + <!-- Blending mode used to apply the foreground tint. --> + <attr name="foregroundTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> </declare-styleable> <declare-styleable name="ExpandableListView"> <!-- Indicator shown beside the group View. This can be a stateful Drawable. --> @@ -3198,6 +3279,10 @@ <!-- @hide The alpha value (0-255) set on the ImageView's drawable. Equivalent to calling ImageView.setAlpha(int), not the same as View.setAlpha(float). --> <attr name="drawableAlpha" format="integer" /> + <!-- Tint to apply to the image. --> + <attr name="tint" /> + <!-- Blending mode used to apply the image tint. --> + <attr name="tintMode" /> </declare-styleable> <declare-styleable name="ToggleButton"> <!-- The text for the button when it is checked. --> @@ -3388,6 +3473,98 @@ <!-- Defines if the associated drawables need to be mirrored when in RTL mode. Default is false --> <attr name="mirrorForRtl" format="boolean" /> + <!-- Tint to apply to the progress indicator. --> + <attr name="progressTint" format="color" /> + <!-- Blending mode used to apply the progress indicator tint. --> + <attr name="progressTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the progress indicator background. --> + <attr name="progressBackgroundTint" format="color" /> + <!-- Blending mode used to apply the progress indicator background tint. --> + <attr name="progressBackgroundTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the secondary progress indicator. --> + <attr name="secondaryProgressTint" format="color" /> + <!-- Blending mode used to apply the secondary progress indicator tint. --> + <attr name="secondaryProgressTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the indepterminate progress indicator. --> + <attr name="indeterminateTint" format="color" /> + <!-- Blending mode used to apply the indeterminate progress indicator tint. --> + <attr name="indeterminateTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the background. --> + <attr name="backgroundTint" /> + <!-- Blending mode used to apply the background tint. --> + <attr name="backgroundTintMode" /> </declare-styleable> <declare-styleable name="SeekBar"> @@ -3397,6 +3574,28 @@ <attr name="thumbOffset" format="dimension" /> <!-- Whether to split the track and leave a gap for the thumb drawable. --> <attr name="splitTrack" format="boolean" /> + <!-- Tint to apply to the button graphic. --> + <attr name="thumbTint" format="color" /> + <!-- Blending mode used to apply the button graphic tint. --> + <attr name="thumbTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> </declare-styleable> <declare-styleable name="StackView"> @@ -3859,8 +4058,11 @@ <attr name="inputType" /> </declare-styleable> <declare-styleable name="PopupWindow"> + <!-- The background to use for the popup window. --> <attr name="popupBackground" format="reference|color" /> + <!-- The animation style to use for the popup window. --> <attr name="popupAnimationStyle" format="reference" /> + <!-- Whether the popup window should overlap its anchor view. --> <attr name="overlapAnchor" format="boolean" /> </declare-styleable> <declare-styleable name="ViewAnimator"> @@ -4063,6 +4265,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_rowSpan" format="integer" min="1" /> + <!-- The relative proportion of horizontal space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_rowWeight" format="float" /> <!-- The column boundary delimiting the left of the group of cells occupied by this view. --> <attr name="layout_column" /> @@ -4071,6 +4276,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_columnSpan" format="integer" min="1" /> + <!-- The relative proportion of vertical space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_columnWeight" format="float" /> <!-- Gravity specifies how a component should be placed in its group of cells. The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> @@ -4605,6 +4813,32 @@ mirror images so that adjacent images always seam. --> <enum name="mirror" value="2" /> </attr> + <!-- Defines the horizontal tile mode. When the tile mode is enabled, the bitmap is repeated. + Gravity is ignored when the tile mode is enabled. Default value is "disabled". --> + <attr name="tileModeX"> + <!-- Do not tile the bitmap. This is the default value. --> + <enum name="disabled" value="-1" /> + <!-- Replicates the edge color. --> + <enum name="clamp" value="0" /> + <!-- Repeats the bitmap in both direction. --> + <enum name="repeat" value="1" /> + <!-- Repeats the shader's image horizontally and vertically, alternating + mirror images so that adjacent images always seam. --> + <enum name="mirror" value="2" /> + </attr> + <!-- Defines the vertical tile mode. When the tile mode is enabled, the bitmap is repeated. + Gravity is ignored when the tile mode is enabled. Default value is "disabled". --> + <attr name="tileModeY"> + <!-- Do not tile the bitmap. This is the default value. --> + <enum name="disabled" value="-1" /> + <!-- Replicates the edge color. --> + <enum name="clamp" value="0" /> + <!-- Repeats the bitmap in both direction. --> + <enum name="repeat" value="1" /> + <!-- Repeats the shader's image horizontally and vertically, alternating + mirror images so that adjacent images always seam. --> + <enum name="mirror" value="2" /> + </attr> <!-- Enables or disables the mipmap hint. See {@link android.graphics.Bitmap#setHasMipMap(boolean)} for more information. Default value is false. --> @@ -4763,13 +4997,29 @@ <attr name="height" /> <!-- Enables or disables dithering. --> <attr name="dither" /> + <!-- If set, specifies the color to apply to the drawable as a tint. By default, + no tint is applied. May be a color state list. --> + <attr name="tint" /> + <!-- When a tint color is set, specifies its Porter-Duff blending mode. The + default value is src_in, which treats the drawable as an alpha mask. --> + <attr name="tintMode" /> </declare-styleable> <!-- ========================== --> - <!-- Vector drawable class --> + <!-- VectorDrawable class --> <!-- ========================== --> <eat-comment /> + <!-- Drawable used to draw vector paths. --> + <declare-styleable name="VectorDrawable"> + <!-- If set, specifies the color to apply to the drawable as a tint. By default, + no tint is applied. May be a color state list. --> + <attr name="tint" /> + <!-- When a tint color is set, specifies its Porter-Duff blending mode. The + default value is src_in, which treats the drawable as an alpha mask. --> + <attr name="tintMode" /> + </declare-styleable> + <!-- Define the virtual size of the drawing surface paths will draw to. --> <declare-styleable name="VectorDrawableViewport"> <!-- The width of the canvas the drawing is on. --> @@ -4786,7 +5036,29 @@ <attr name="height" /> </declare-styleable> - <!-- Defines the path used in Vector Drawables. --> + <!-- Defines the group used in VectorDrawables. --> + <declare-styleable name="VectorDrawableGroup"> + <!-- The Name of this group --> + <attr name="name" /> + <!-- The amount to rotate the group --> + <attr name="rotation" /> + <!-- The X coordinate of the center of rotation of a group --> + <attr name="pivotX" /> + <!-- The Y coordinate of the center of rotation of a group --> + <attr name="pivotY" /> + <!-- The amount to translate the group on X coordinate --> + <attr name="translateX" format="float"/> + <!-- The amount to translate the group on Y coordinate --> + <attr name="translateY" format="float"/> + <!-- The amount to scale the group on X coordinate --> + <attr name="scaleX" /> + <!-- The amount to scale the group on X coordinate --> + <attr name="scaleY" /> + <!-- The alpha of the group (0 is transparent and 1 is opaque) --> + <attr name="alpha" /> + </declare-styleable> + + <!-- Defines the path used in VectorDrawables. --> <declare-styleable name="VectorDrawablePath"> <!-- The Name of this path --> <attr name="name" /> @@ -4794,12 +5066,6 @@ <attr name="strokeWidth" format="float" /> <!-- The opacity of a path stroke --> <attr name="strokeOpacity" format="float" /> - <!-- The amount to rotate the path stroke --> - <attr name="rotation" /> - <!-- The X coordinate of the center of rotation of a path --> - <attr name="pivotX" /> - <!-- The Y coordinate of the center of rotation of a path --> - <attr name="pivotY" /> <!-- The color to stroke the path if not defined implies no stroke--> <attr name="stroke" format="color" /> <!-- The color to fill the path if not defined implies no fill--> @@ -4833,6 +5099,25 @@ </declare-styleable> <!-- ========================== --> + <!-- AnimatedVectorDrawable class --> + <!-- ========================== --> + <eat-comment /> + + <!-- Define the AnimatedVectorDrawable. --> + <declare-styleable name="AnimatedVectorDrawable"> + <!-- The static vector drawable. --> + <attr name="drawable" /> + </declare-styleable> + + <!-- Defines the target path or group used in the AnimatedVectorDrawable. --> + <declare-styleable name="AnimatedVectorDrawableTarget"> + <!-- The name of this target path or group --> + <attr name="name" /> + <!-- The animation for this target path or group --> + <attr name="animation" /> + </declare-styleable> + + <!-- ========================== --> <!-- Animation class attributes --> <!-- ========================== --> <eat-comment /> @@ -5007,10 +5292,18 @@ </declare-styleable> <declare-styleable name="PathInterpolator"> + <!-- The x coordinate of the first control point of the cubic Bezier --> <attr name="controlX1" format="float" /> + <!-- The y coordinate of the first control point of the cubic Bezier --> <attr name="controlY1" format="float" /> + <!-- The x coordinate of the second control point of the cubic Bezier --> <attr name="controlX2" format="float" /> + <!-- The y coordinate of the second control point of the cubic Bezier --> <attr name="controlY2" format="float" /> + <!-- The control points defined as a path. + When pathData is defined, then both of the control points of the + cubic Bezier will be ignored. --> + <attr name="pathData"/> </declare-styleable> <!-- ========================== --> @@ -5030,10 +5323,10 @@ <attr name="interpolator" /> <!-- The match order to use for the transition. This is a comma-separated list of values, containing one or more of the following: - id, itemId, viewName, instance. These correspond to + id, itemId, name, instance. These correspond to {@link android.transition.Transition#MATCH_ID}, {@link android.transition.Transition#MATCH_ITEM_ID}, - {@link android.transition.Transition#MATCH_VIEW_NAME}, and + {@link android.transition.Transition#MATCH_NAME}, and {@link android.transition.Transition#MATCH_INSTANCE}, respectively. This corresponds to {@link android.transition.Transition#setMatchOrder(int...)}. --> <attr name="matchOrder" format="string" /> @@ -5088,10 +5381,10 @@ <attr name="targetClass" /> <!-- The fully-qualified name of the Class to exclude from this transition. --> <attr name="excludeClass" format="string" /> - <!-- The viewName of the target on which this transition will animation changes. --> - <attr name="targetViewName" format="string" /> - <!-- The viewName of the target to exclude from this transition. --> - <attr name="excludeViewName" format="string" /> + <!-- The transitionName of the target on which this transition will animation changes. --> + <attr name="targetName" format="string" /> + <!-- The transitionName of the target to exclude from this transition. --> + <attr name="excludeName" format="string" /> </declare-styleable> <!-- Use <code>set</code> as the root tag of the XML resource that @@ -5149,6 +5442,9 @@ <enum name="floatType" value="0" /> <!-- valueFrom and valueTo are integers. --> <enum name="intType" value="1" /> + <!-- valueFrom and valueTo are paths defined as strings. + This type is used for path morphing in AnimatedVectorDrawable. --> + <enum name="pathType" value="2" /> </attr> </declare-styleable> @@ -5160,6 +5456,12 @@ <declare-styleable name="PropertyAnimator"> <!-- Name of the property being animated. --> <attr name="propertyName" format="string"/> + <!-- Name of the property being animated as the X coordinate of the pathData. --> + <attr name="propertyXName" format="string"/> + <!-- Name of the property being animated as the Y coordinate of the pathData. --> + <attr name="propertyYName" format="string"/> + <!-- The path used to animate the properties in the ObjectAnimator --> + <attr name="pathData"/> </declare-styleable> @@ -6126,13 +6428,16 @@ <!-- Use <code>trust-agent</code> as the root tag of the XML resource that describes an {@link android.service.trust.TrustAgentService}, which is referenced from its {@link android.service.trust.TrustAgentService#TRUST_AGENT_META_DATA} - meta-data entry. Described here are the attributes that can be included in that tag. - @hide --> + meta-data entry. Described here are the attributes that can be included in that tag. --> <declare-styleable name="TrustAgent"> <!-- Component name of an activity that allows the user to modify - the settings for this trust agent. - @hide --> + the settings for this trust agent. --> <attr name="settingsActivity" /> + <!-- Title for a preference that allows that user to launch the + activity to modify trust agent settings. --> + <attr name="title" /> + <!-- Summary for the same preference as the title. --> + <attr name="summary" /> </declare-styleable> <!-- =============================== --> @@ -6365,6 +6670,16 @@ <attr name="settingsActivity" /> </declare-styleable> + <!-- Use <code>voice-enrollment-application</code> + as the root tag of the XML resource that escribes the supported keyphrases (hotwords) + by the enrollment application. + Described here are the attributes that can be included in that tag. --> + <declare-styleable name="VoiceEnrollmentApplication"> + <attr name="searchKeyphraseId" format="integer" /> + <attr name="searchKeyphrase" format="string" /> + <attr name="searchKeyphraseSupportedLocales" format="string" /> + </declare-styleable> + <!-- Attributes used to style the Action Bar. --> <declare-styleable name="ActionBar"> <!-- The type of navigation to use. --> @@ -6477,6 +6792,8 @@ <attr name="switchPadding" format="dimension" /> <!-- Whether to split the track and leave a gap for the thumb drawable. --> <attr name="splitTrack" /> + <!-- Whether to draw on/off text. --> + <attr name="showText" format="boolean" /> </declare-styleable> <declare-styleable name="Pointer"> @@ -6539,6 +6856,10 @@ <attr name="disableDependentsState" /> </declare-styleable> + <declare-styleable name="SeekBarPreference"> + <attr name="layout" /> + </declare-styleable> + <!-- Use <code>tts-engine</code> as the root tag of the XML resource that describes a text to speech engine implemented as a subclass of {@link android.speech.tts.TextToSpeechService}. diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 814d8fc..fc1d0df 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -814,6 +814,14 @@ via adb. The default value of this attribute is <code>true</code>. --> <attr name="allowBackup" format="boolean" /> + <!-- Indicates that even though the application provides a <code>BackupAgent</code>, + only full-data streaming backup operations are to be performed to save the app's + data. This lets the app rely on full-data backups while still participating in + the backup and restore process via the BackupAgent's full-data backup APIs. + When this attribute is <code>true</code> the app's BackupAgent overrides of + the onBackup() and onRestore() callbacks can be empty stubs. --> + <attr name="fullBackupOnly" format="boolean" /> + <!-- Whether the application in question should be terminated after its settings have been restored during a full-system restore operation. Single-package restore operations will never cause the application to @@ -873,17 +881,33 @@ <!-- The name of the logical parent of the activity as it appears in the manifest. --> <attr name="parentActivityName" format="string" /> - <!-- Define an activity that will persist across reboots. If such an activity is in the - Recents list when the device is shut off it will appear in the Recents list when - the device is next powered on. To be persisted all activities in the task from the - root activity up to the last activity before a <em>break</em> must be declared with - the persistable attribute. A <em>break</em> is the first activity after the root - started with Intent.FLAG_CLEAR_TASK_WHEN_RESET. - - <p>Activities that are declared with the persistable attribute will be provided with a - forced-persistable Bundle in onCreate() and onSavedInstanceState(), and must only - be passed a persistable Bundle in their Intent.extras. --> - <attr name="persistable" format="boolean" /> + <!-- Define how an activity persist across reboots. Activities defined as "never" will not + be persisted. Those defined as "always" will be persisted. Those defined as "taskOnly" + will persist the root activity of the task only. See below for more detail as to + what gets persisted. --> + <attr name="persistableMode"> + <!-- The default. If this activity forms the root of a task then that task will be + persisted across reboots but only the launching intent will be used. All + activities above this activity in the task will not be persisted. In addition + this activity will not be passed a PersistableBundle into which it could have + stored its state. --> + <enum name="persistRootOnly" value="0" /> + <!-- If this activity forms the root of a task then that task will not be persisted + across reboots --> + <enum name="doNotPersist" value="1" /> + <!-- If this activity forms the root of a task then the task and this activity will + be persisted across reboots. If the activity above this activity is also + tagged with the attribute <code>"persist"</code> then it will be persisted as well. + And so on up the task stack until either an activity without the + <code>persistableMode="persistAcrossReboots"</code> attribute or one that was launched + with the flag Intent.FLAG_CLEAR_TASK_WHEN_RESET is encountered. + + <p>Activities that are declared with the persistAcrossReboots attribute will be + provided with a PersistableBundle in onSavedInstanceState(), These activities may + use this PeristableBundle to save their state. Then, following a reboot, that + PersistableBundle will be provided back to the activity in its onCreate() method. --> + <enum name="persistAcrossReboots" value="2" /> + </attr> <!-- This attribute specifies that an activity shall become the root activity of a new task each time it is launched. Using this attribute permits the user to @@ -961,6 +985,14 @@ Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} --> <attr name="autoRemoveFromRecents" format="boolean" /> + <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the + next activity in the task. If the next activity also has this attribute set to true then + it will yield the baseIntent to any activity that it launches in the same task. This + continues until an activity is encountered which has this attribute set to false. False + is the default. This attribute set to true also permits activity's use of the + TaskDescription to change labels, colors and icons in the recent task list. --> + <attr name="relinquishTaskIdentity" format="boolean" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1044,6 +1076,7 @@ <attr name="testOnly" /> <attr name="backupAgent" /> <attr name="allowBackup" /> + <attr name="fullBackupOnly" /> <attr name="killAfterRestore" /> <attr name="restoreNeedsApplication" /> <attr name="restoreAnyVersion" /> @@ -1623,11 +1656,12 @@ <!-- @hide This broacast receiver will only receive broadcasts for the primary user. Can only be used with receivers. --> <attr name="primaryUserOnly" format="boolean" /> - <attr name="persistable" /> + <attr name="persistableMode" /> <attr name="allowEmbedded" /> <attr name="documentLaunchMode" /> <attr name="maxRecents" /> <attr name="autoRemoveFromRecents" /> + <attr name="relinquishTaskIdentity" /> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 9f6c7ad..dd316ed 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -125,9 +125,6 @@ <color name="micro_text_light">#434343</color> - <color name="leanback_dark_gray">#ff333333</color> - <color name="leanback_light_gray">#ff888888</color> - <drawable name="notification_template_icon_bg">#3333B5E5</drawable> <drawable name="notification_template_icon_low_bg">#0cffffff</drawable> @@ -143,7 +140,7 @@ <color name="keyguard_avatar_nick_color">#ffffffff</color> <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color> - <color name="accessibility_focus_highlight">#80ffff00</color> + <color name="accessibility_focus_highlight">#bf39b500</color> </resources> diff --git a/core/res/res/values/colors_leanback.xml b/core/res/res/values/colors_leanback.xml new file mode 100644 index 0000000..dc0c673 --- /dev/null +++ b/core/res/res/values/colors_leanback.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- Colors specific to Leanback themes. --> +<resources> + <color name="background_leanback_dark">#ff324248</color> + <color name="background_leanback_light">#ffeeeeee</color> + + <color name="primary_text_leanback_dark">#cceeeeee</color> + <color name="secondary_text_leanback_dark">#99eeeeee</color> + + <color name="primary_text_leanback_light">#cc222222</color> + <color name="secondary_text_leanback_light">#99222222</color> + +</resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 27ac6c3..d350ef2 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -628,6 +628,9 @@ <!-- Default value for LED off time when the battery is low on charge in miliseconds --> <integer name="config_notificationsBatteryLedOff">2875</integer> + <!-- Number of notifications to keep in the notification service historical archive --> + <integer name="config_notificationServiceArchiveSize">250</integer> + <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> @@ -788,6 +791,19 @@ config_enableFusedLocationOverlay is false. --> <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string> + <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be + replaced by an app at run-time. When disabled, only the + config_hardwareFlpPackageName package will be searched for Hardware Flp, + otherwise packages whose signature matches the signatures of + config_locationProviderPackageNames will be searched, and the service + with the highest version number will be picked. Anyone who wants to + disable the overlay mechanism can set it to false. + --> + <bool name="config_enableHardwareFlpOverlay" translatable="false">true</bool> + <!-- Package name providing Hardware Flp. Used only when + config_enableHardwareFlpOverlay is false. --> + <string name="config_hardwareFlpPackageName" translatable="false">com.android.location.fused</string> + <!-- Whether to enable geocoder overlay which allows geocoder to be replaced by an app at run-time. When disabled, only the config_geocoderProviderPackageName package will be searched for @@ -1446,6 +1462,10 @@ <string name="config_customAdbPublicKeyConfirmationComponent" >com.android.systemui/com.android.systemui.usb.UsbDebuggingActivity</string> + <!-- Name of the CustomDialog that is used for VPN --> + <string name="config_customVpnConfirmDialogComponent" + >com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string> + <!-- Apps that are authorized to access shared accounts, overridden by product overlays --> <string name="config_appsAuthorizedForSharedAccounts">;com.android.settings;</string> @@ -1554,4 +1574,35 @@ <item>users</item> </string-array> + <!-- default telephony hardware configuration for this platform. + --> + <!-- this string array should be overridden by the device to present a list + telephony hardware resource. this is used by the telephony device controller + (TDC) to offer the basic capabilities of the hardware to the telephony + framework + --> + <!-- an array of "[hardware type],[hardware-uuid],[state],[[hardware-type specific]]" + with, [[hardware-type specific]] in: + - "[[ril-model],[rat],[max-active-voice],[max-active-data],[max-active-standby]]" + for 'modem' hardware + - "[[associated-modem-uuid]]" + for 'sim' hardware. + refer to HardwareConfig in com.android.internal.telephony for specific details/values + those elements can carry. + --> + <string-array translatable="false" name="config_telephonyHardware"> + <!-- modem --> + <item>"0,modem,0,0,0,1,1,1"</item> + <!-- sim --> + <item>"1,sim,0,modem"</item> + </string-array> + + <!-- This string array can be overriden to add an additional DRM support for WebView EME. --> + <!-- Array of "[keySystemName],[UuidOfMediaDrm]" --> + <string-array name="config_keySystemUuidMapping" translatable="false"> + <!-- Example: + <item>"x-com.microsoft.playready,9A04F079-9840-4286-AB92-E65BE0885F95"</item> + --> + </string-array> + </resources> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index be7e6c1..18e4574 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -15,6 +15,11 @@ --> <resources> + <!-- Preference fragment padding, sides --> + <dimen name="preference_fragment_padding_side_material">0dp</dimen> + + <dimen name="preference_screen_header_padding_side_material">0dp</dimen> + <!-- Default height of an action bar. --> <dimen name="action_bar_default_height_material">56dp</dimen> <!-- Default padding of an action bar. --> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 639091e..c64e910 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -84,4 +84,5 @@ <item type="id" name="current_scene" /> <item type="id" name="scene_layoutid_cache" /> <item type="id" name="mask" /> + <item type="id" name="transitionPosition" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e16082f..727d286 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2115,7 +2115,7 @@ <public type="attr" name="controlY1" /> <public type="attr" name="controlX2" /> <public type="attr" name="controlY2" /> - <public type="attr" name="viewName" /> + <public type="attr" name="transitionName" /> <public type="attr" name="transitionGroup" /> <public type="attr" name="viewportWidth" /> <public type="attr" name="viewportHeight" /> @@ -2136,7 +2136,7 @@ <public type="attr" name="colorControlActivated" /> <public type="attr" name="colorButtonNormal" /> <public type="attr" name="colorControlHighlight" /> - <public type="attr" name="persistable" /> + <public type="attr" name="persistableMode" /> <public type="attr" name="titleTextAppearance" /> <public type="attr" name="subtitleTextAppearance" /> <public type="attr" name="slideEdge" /> @@ -2168,8 +2168,8 @@ <public type="attr" name="fromId" /> <public type="attr" name="reversible" /> <public type="attr" name="splitTrack" /> - <public type="attr" name="targetViewName" /> - <public type="attr" name="excludeViewName" /> + <public type="attr" name="targetName" /> + <public type="attr" name="excludeName" /> <public type="attr" name="matchOrder" /> <public type="attr" name="windowDrawsSystemBarBackgrounds" /> <public type="attr" name="statusBarColor"/> @@ -2179,8 +2179,42 @@ <public type="attr" name="contentInsetLeft" /> <public type="attr" name="contentInsetRight" /> <public type="attr" name="paddingMode" /> + <public type="attr" name="layout_rowWeight" /> + <public type="attr" name="layout_columnWeight" /> + <public type="attr" name="translateX" /> + <public type="attr" name="translateY" /> <public type="attr" name="selectableItemBackgroundBorderless" /> <public type="attr" name="elegantTextHeight" /> + <public type="attr" name="searchKeyphraseId" /> + <public type="attr" name="searchKeyphrase" /> + <public type="attr" name="searchKeyphraseSupportedLocales" /> + <public type="attr" name="windowTransitionBackgroundFadeDuration" /> + <public type="attr" name="overlapAnchor" /> + <public type="attr" name="progressTint" /> + <public type="attr" name="progressTintMode" /> + <public type="attr" name="progressBackgroundTint" /> + <public type="attr" name="progressBackgroundTintMode" /> + <public type="attr" name="secondaryProgressTint" /> + <public type="attr" name="secondaryProgressTintMode" /> + <public type="attr" name="indeterminateTint" /> + <public type="attr" name="indeterminateTintMode" /> + <public type="attr" name="backgroundTint" /> + <public type="attr" name="backgroundTintMode" /> + <public type="attr" name="foregroundTint" /> + <public type="attr" name="foregroundTintMode" /> + <public type="attr" name="buttonTint" /> + <public type="attr" name="buttonTintMode" /> + <public type="attr" name="thumbTint" /> + <public type="attr" name="thumbTintMode" /> + <public type="attr" name="fullBackupOnly" /> + <public type="attr" name="propertyXName" /> + <public type="attr" name="propertyYName" /> + <public type="attr" name="relinquishTaskIdentity" /> + <public type="attr" name="tileModeX" /> + <public type="attr" name="tileModeY" /> + <public type="attr" name="actionModeShareDrawable" /> + <public type="attr" name="actionModeFindDrawable" /> + <public type="attr" name="actionModeWebSearchDrawable" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2432,5 +2466,26 @@ <!-- An interpolator which accelerates fast and keeps accelerating until the end. --> <public type="interpolator" name="fast_out_linear_in" /> + <!-- Used for Activity Transitions, this transition indicates that no Transition + should be used. --> <public type="transition" name="no_transition" id="0x010f0000"/> + <!-- A transition that moves and resizes a view --> + <public type="transition" name="move"/> + <!-- A transition that fades views in and out. --> + <public type="transition" name="fade"/> + <!-- A transition that moves views in or out of the scene to or from the edges when + a view visibility changes. --> + <public type="transition" name="explode"/> + <!-- A transition that moves views in or out of the scene to or from the bottom edge when + a view visibility changes. --> + <public type="transition" name="slide_bottom"/> + <!-- A transition that moves views in or out of the scene to or from the top edge when + a view visibility changes. --> + <public type="transition" name="slide_top"/> + <!-- A transition that moves views in or out of the scene to or from the right edge when + a view visibility changes. --> + <public type="transition" name="slide_right"/> + <!-- A transition that moves views in or out of the scene to or from the left edge when + a view visibility changes. --> + <public type="transition" name="slide_left"/> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1f76bb5..e017f53 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1122,6 +1122,12 @@ interface of a voice interaction service. Should never be needed for normal apps.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_manageVoiceKeyphrases">manage voice keyphrases</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_manageVoiceKeyphrases">Allows the holder to manage the keyphrases for voice hotword detection. + Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_bindRemoteDisplay">bind to a remote display</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level @@ -1589,6 +1595,11 @@ without your confirmation.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_sim_communication">sim communication</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_sim_communication">Allows the app to send commands to the SIM. This is very dangerous.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_camera">take pictures and videos</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_camera">Allows the app to take pictures and videos @@ -2960,6 +2971,13 @@ to your voicemail inbox.</string> <!-- Title of an application permission, listed so the user can choose whether + they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permlab_readAllVoicemail">read all voicemail</string> + <!-- Description of an application permission, listed so the user can choose whether + they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permdesc_readAllVoicemail">Allows the app to read all your voicemails.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_writeGeolocationPermissions">modify Browser geolocation permissions</string> <!-- Description of an application permission, listed so the user can choose whether @@ -3784,6 +3802,8 @@ <string name="permlab_provide_trust_agent">Provide a trust agent.</string> <!-- Description of an application permission that lets it provide a trust agent. --> <string name="permdesc_provide_trust_agent">Allows an application to provide a trust agent.</string> + <string name="permlab_launch_trust_agent_settings">Lunch trust agent settings menu.</string> + <string name="permdesc_launch_trust_agent_settings">Allows an application to lunch an activity that changes the trust agent behavior.</string> <!-- Title of an application permission that lets it bind to a trust agent service. --> <string name="permlab_bind_trust_agent_service">Bind to a trust agent service</string> @@ -4708,6 +4728,13 @@ <!-- Accessibility announcement when a number that had been typed in is deleted [CHAR_LIMIT=NONE] --> <string name="deleted_key"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string> + <!-- + Used to wrap a label for content description for a managed profile, e.g. "Work Email" instead + of email when there are two email apps. + [CHAR LIMIT=20] + --> + <string name="managed_profile_label_badge">Work <xliff:g id="label" example="Email">%1$s</xliff:g></string> + <!-- DO NOT TRANSLATE --> <string name="time_placeholder">--</string> @@ -4719,4 +4746,17 @@ <!-- DO NOT TRANSLATE --> <string name="day_of_week_label_typeface">sans-serif</string> + <!-- Lock-to-app dialog title. --> + <string name="lock_to_app_title">Use lock-to-app?</string> + <!-- Lock-to-app dialog description. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. --> + <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $</string> + <!-- Lock-to-app negative response. --> + <string name="lock_to_app_negative">NO</string> + <!-- Lock-to-app positive response. --> + <string name="lock_to_app_positive">START</string> + <!-- Starting lock-to-app indication. --> + <string name="lock_to_app_start">Start Lock-to-app</string> + <!-- Exting lock-to-app indication. --> + <string name="lock_to_app_exit">Exit Lock-to-app</string> + </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index c769cd9..d6be3133 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -237,14 +237,6 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/fast_fade_out</item> </style> - <!-- Window animations for swipe-dismissable windows. {@hide} --> - <style name="Animation.SwipeDismiss"> - <item name="taskOpenEnterAnimation">@anim/swipe_window_enter</item> - <item name="taskOpenExitAnimation">@anim/swipe_window_exit</item> - <item name="taskCloseEnterAnimation">@anim/swipe_window_enter</item> - <item name="taskCloseExitAnimation">@anim/swipe_window_exit</item> - </style> - <!-- Status Bar Styles --> <style name="TextAppearance.StatusBar"> <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> @@ -988,6 +980,10 @@ please see styles_device_defaults.xml. <item name="android:switchTextOff">@android:string/capital_off</item> </style> + <style name="Preference.SeekBarPreference"> + <item name="android:layout">@android:layout/preference_widget_seekbar</item> + </style> + <style name="Preference.PreferenceScreen"> </style> @@ -1043,6 +1039,10 @@ please see styles_device_defaults.xml. <item name="android:switchTextOff">@android:string/capital_off</item> </style> + <style name="Preference.Holo.SeekBarPreference"> + <item name="android:layout">@android:layout/preference_widget_seekbar</item> + </style> + <style name="Preference.Holo.PreferenceScreen"> </style> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index 84dbc79..84d38ce 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -254,6 +254,7 @@ easier. <style name="Preference.DeviceDefault.PreferenceScreen" parent="Preference.Material.PreferenceScreen"/> <style name="Preference.DeviceDefault.RingtonePreference" parent="Preference.Material.RingtonePreference"/> <style name="Preference.DeviceDefault.SwitchPreference" parent="Preference.Material.SwitchPreference"/> + <style name="Preference.DeviceDefault.SeekBarPreference" parent="Preference.Material.SeekBarPreference"/> <!-- AlertDialog Styles --> <style name="AlertDialog.DeviceDefault" parent="AlertDialog.Material"/> diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml index a37da4e..72d0379 100644 --- a/core/res/res/values/styles_leanback.xml +++ b/core/res/res/values/styles_leanback.xml @@ -14,46 +14,12 @@ limitations under the License. --> <resources> - <style name="DialogWindowTitle.Leanback" parent="DialogWindowTitle.Holo"> - <item name="android:textAppearance">@style/TextAppearance.Leanback.DialogWindowTitle</item> - </style> - - <style name="TextAppearance.Leanback.DialogWindowTitle" parent="TextAppearance.Holo.DialogWindowTitle"> - <item name="android:fontFamily">sans-serif-condensed</item> - <item name="android:textColor">?attr/textColorPrimary</item> - </style> - - <style name="Leanback.ButtonBar" parent="Holo.ButtonBar"> - <item name="showDividers">none</item> - </style> - - <style name="AlertDialog.Leanback" parent="AlertDialog.Holo"> + <style name="AlertDialog.Leanback" parent="AlertDialog.Material"> <item name="layout">@android:layout/alert_dialog_leanback</item> <item name="buttonPanelSideLayout">@android:layout/alert_dialog_leanback_button_panel_right</item> - <item name="progressLayout">@android:layout/progress_dialog_leanback</item> - <item name="fullDark">@android:color/background_dark</item> - <item name="topDark">@android:color/background_dark</item> - <item name="centerDark">@android:color/background_dark</item> - <item name="bottomDark">@android:color/background_dark</item> - <item name="fullBright">@android:color/background_dark</item> - <item name="topBright">@android:color/background_dark</item> - <item name="centerBright">@android:color/background_dark</item> - <item name="bottomBright">@android:color/background_dark</item> - <item name="bottomMedium">@android:color/background_dark</item> - <item name="centerMedium">@android:color/background_dark</item> </style> <style name="AlertDialog.Leanback.Light"> - <item name="fullDark">@android:color/leanback_light_gray</item> - <item name="topDark">@android:color/leanback_light_gray</item> - <item name="centerDark">@android:color/leanback_light_gray</item> - <item name="bottomDark">@android:color/leanback_light_gray</item> - <item name="fullBright">@android:color/leanback_light_gray</item> - <item name="topBright">@android:color/leanback_light_gray</item> - <item name="centerBright">@android:color/leanback_light_gray</item> - <item name="bottomBright">@android:color/leanback_light_gray</item> - <item name="bottomMedium">@android:color/leanback_light_gray</item> - <item name="centerMedium">@android:color/leanback_light_gray</item> </style> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 7120521..75f905c 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -37,8 +37,8 @@ please see styles_device_defaults.xml. </style> <style name="PreferenceFragment.Material"> - <item name="paddingStart">@dimen/preference_fragment_padding_side</item> - <item name="paddingEnd">@dimen/preference_fragment_padding_side</item> + <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item> + <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item> </style> <style name="Preference.Material.Information"> @@ -64,6 +64,10 @@ please see styles_device_defaults.xml. <item name="switchTextOff">@string/capital_off</item> </style> + <style name="Preference.Material.SeekBarPreference"> + <item name="layout">@android:layout/preference_widget_seekbar_material</item> + </style> + <style name="Preference.Material.PreferenceScreen"/> <style name="Preference.Material.DialogPreference"> @@ -86,6 +90,38 @@ please see styles_device_defaults.xml. <item name="showDefault">true</item> </style> + <!-- No margins or background by default. Could be different for x-large screens --> + <style name="PreferencePanel.Material"> + </style> + + <!-- The attributes are overridden here because the x-large or large resources may have + changed the margins and background in the parent PreferencePanel style. --> + <style name="PreferencePanel.Material.Dialog"> + <item name="layout_marginStart">0dip</item> + <item name="layout_marginEnd">0dip</item> + <item name="layout_marginTop">0dip</item> + <item name="layout_marginBottom">0dip</item> + <item name="background">@null</item> + </style> + + <style name="PreferenceHeaderPanel.Material"> + <item name="layout_marginStart">@dimen/preference_screen_side_margin</item> + <item name="layout_marginEnd">@dimen/preference_screen_side_margin_negative</item> + <item name="paddingTop">@dimen/preference_screen_header_vertical_padding</item> + <item name="paddingBottom">@dimen/preference_screen_header_vertical_padding</item> + </style> + + <style name="PreferenceHeaderList.Material"> + <item name="paddingStart">@dimen/preference_screen_header_padding_side_material</item> + <item name="paddingEnd">@dimen/preference_screen_header_padding_side_material</item> + <item name="scrollbarStyle">@integer/preference_screen_header_scrollbarStyle</item> + </style> + + <style name="PreferenceFragmentList.Material"> + <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item> + <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item> + </style> + <!-- Begin Material theme styles --> <!-- Text styles --> @@ -457,11 +493,12 @@ please see styles_device_defaults.xml. <item name="thumb">@drawable/switch_thumb_material_anim</item> <item name="splitTrack">true</item> <item name="switchTextAppearance">@style/TextAppearance.Material.Widget.Switch</item> - <item name="textOn"></item> - <item name="textOff"></item> + <item name="textOn">@string/capital_on</item> + <item name="textOff">@string/capital_off</item> <item name="switchMinWidth">4dip</item> <item name="switchPadding">4dip</item> <item name="background">?attr/selectableItemBackgroundBorderless</item> + <item name="showText">false</item> </style> <style name="Widget.Material.EditText" parent="Widget.EditText"/> @@ -624,6 +661,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Spinner.DropDown.ActionBar"> <item name="background">@drawable/spinner_background_material</item> + <item name="overlapAnchor">true</item> </style> <style name="Widget.Material.TabWidget" parent="Widget.TabWidget"> diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml index 5bac1f9..0c854d3 100644 --- a/core/res/res/values/styles_micro.xml +++ b/core/res/res/values/styles_micro.xml @@ -14,6 +14,19 @@ limitations under the License. --> <resources> + <style name="Animation.Micro"/> + + <style name="Animation.Micro.Activity" parent="Animation.Holo.Activity"> + <item name="activityOpenEnterAnimation">@anim/slide_in_micro</item> + <item name="activityOpenExitAnimation">@null</item> + <item name="activityCloseEnterAnimation">@null</item> + <item name="activityCloseExitAnimation">@anim/slide_out_micro</item> + <item name="taskOpenEnterAnimation">@anim/slide_in_micro</item> + <item name="taskOpenExitAnimation">@null</item> + <item name="taskCloseEnterAnimation">@null</item> + <item name="taskCloseExitAnimation">@anim/slide_out_micro</item> + </style> + <style name="AlertDialog.Micro" parent="AlertDialog.Holo.Light"> <item name="fullDark">@null</item> <item name="topDark">@null</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d8e31ea..3e82d08 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -218,6 +218,7 @@ <java-symbol type="id" name="pin_error_message" /> <java-symbol type="id" name="timePickerLayout" /> <java-symbol type="id" name="profile_icon" /> + <java-symbol type="id" name="transitionPosition" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -603,6 +604,14 @@ <java-symbol type="string" name="kilobyteShort" /> <java-symbol type="string" name="last_month" /> <java-symbol type="string" name="launchBrowserDefault" /> + <java-symbol type="string" name="lock_to_app_title" /> + <java-symbol type="string" name="lock_to_app_description" /> + <java-symbol type="string" name="lock_to_app_negative" /> + <java-symbol type="string" name="lock_to_app_positive" /> + <java-symbol type="drawable" name="ic_recent" /> + <java-symbol type="layout" name="lock_to_app_enter" /> + <java-symbol type="layout" name="lock_to_app_exit" /> + <java-symbol type="drawable" name="lock_task_notify_bg" /> <java-symbol type="string" name="lockscreen_access_pattern_cell_added" /> <java-symbol type="string" name="lockscreen_access_pattern_cleared" /> <java-symbol type="string" name="lockscreen_access_pattern_detected" /> @@ -863,6 +872,7 @@ <java-symbol type="string" name="action_bar_home_description_format" /> <java-symbol type="string" name="action_bar_home_subtitle_description_format" /> <java-symbol type="string" name="wireless_display_route_description" /> + <java-symbol type="string" name="managed_profile_label_badge" /> <java-symbol type="string" name="mediasize_unknown_portrait" /> <java-symbol type="string" name="mediasize_unknown_landscape" /> <java-symbol type="string" name="mediasize_iso_a0" /> @@ -999,6 +1009,8 @@ <java-symbol type="array" name="config_sameNamedOperatorConsideredRoaming" /> <java-symbol type="array" name="config_callBarringMMI" /> <java-symbol type="array" name="config_globalActionsList" /> + <java-symbol type="array" name="config_telephonyHardware" /> + <java-symbol type="array" name="config_keySystemUuidMapping" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="indicator_input_error" /> @@ -1122,6 +1134,15 @@ <java-symbol type="drawable" name="ic_corp_badge" /> <java-symbol type="drawable" name="ic_corp_icon_badge" /> + <java-symbol type="drawable" name="sim_light_blue" /> + <java-symbol type="drawable" name="sim_light_green" /> + <java-symbol type="drawable" name="sim_light_orange" /> + <java-symbol type="drawable" name="sim_light_purple" /> + <java-symbol type="drawable" name="sim_dark_blue" /> + <java-symbol type="drawable" name="sim_dark_green" /> + <java-symbol type="drawable" name="sim_dark_orange" /> + <java-symbol type="drawable" name="sim_dark_purple" /> + <java-symbol type="layout" name="action_bar_home" /> <java-symbol type="layout" name="action_bar_title_item" /> <java-symbol type="layout" name="action_menu_item_layout" /> @@ -1462,6 +1483,7 @@ <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> + <java-symbol type="bool" name="config_enableHardwareFlpOverlay" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> <java-symbol type="bool" name="config_enableGeofenceOverlay" /> <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> @@ -1518,6 +1540,7 @@ <java-symbol type="integer" name="config_notificationsBatteryLedOn" /> <java-symbol type="integer" name="config_notificationsBatteryLowARGB" /> <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> + <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> <java-symbol type="integer" name="config_radioScanningTimeout" /> <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" /> <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" /> @@ -1552,6 +1575,7 @@ <java-symbol type="string" name="chooser_wallpaper" /> <java-symbol type="string" name="config_datause_iface" /> <java-symbol type="string" name="config_fusedLocationProviderPackageName" /> + <java-symbol type="string" name="config_hardwareFlpPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> <java-symbol type="string" name="config_geofenceProviderPackageName" /> <java-symbol type="string" name="config_networkLocationProviderPackageName" /> @@ -1647,6 +1671,7 @@ <java-symbol type="integer" name="config_maximumScreenDimDuration" /> <java-symbol type="fraction" name="config_maximumScreenDimRatio" /> <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" /> + <java-symbol type="string" name="config_customVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="layout" name="resolver_list" /> @@ -1879,5 +1904,6 @@ <java-symbol type="color" name="timepicker_default_selector_color_material" /> <java-symbol type="color" name="timepicker_default_numbers_background_color_material" /> <java-symbol type="style" name="TextAppearance.Material.TimePicker.TimeLabel" /> + <java-symbol type="attr" name="seekBarPreferenceStyle" /> </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index cb5cb0c..5eec197 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -321,6 +321,7 @@ please see themes_device_defaults.xml. <item name="preferenceInformationStyle">@android:style/Preference.Information</item> <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item> <item name="switchPreferenceStyle">@android:style/Preference.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@android:style/Preference.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item> <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item> @@ -1203,6 +1204,7 @@ please see themes_device_defaults.xml. <item name="preferenceInformationStyle">@android:style/Preference.Holo.Information</item> <item name="checkBoxPreferenceStyle">@android:style/Preference.Holo.CheckBoxPreference</item> <item name="switchPreferenceStyle">@android:style/Preference.Holo.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@android:style/Preference.Holo.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@android:style/Preference.Holo.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@android:style/Preference.Holo.DialogPreference</item> <item name="editTextPreferenceStyle">@android:style/Preference.Holo.DialogPreference.EditTextPreference</item> @@ -1543,6 +1545,7 @@ please see themes_device_defaults.xml. <item name="preferenceInformationStyle">@android:style/Preference.Holo.Information</item> <item name="checkBoxPreferenceStyle">@android:style/Preference.Holo.CheckBoxPreference</item> <item name="switchPreferenceStyle">@android:style/Preference.Holo.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@android:style/Preference.Holo.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@android:style/Preference.Holo.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@android:style/Preference.Holo.DialogPreference</item> <item name="editTextPreferenceStyle">@android:style/Preference.Holo.DialogPreference.EditTextPreference</item> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 27c8754..8e83e48 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -160,6 +160,7 @@ easier. <item name="preferenceInformationStyle">@style/Preference.DeviceDefault.Information</item> <item name="checkBoxPreferenceStyle">@style/Preference.DeviceDefault.CheckBoxPreference</item> <item name="switchPreferenceStyle">@style/Preference.DeviceDefault.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@style/Preference.DeviceDefault.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference</item> <item name="editTextPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.EditTextPreference</item> @@ -421,6 +422,7 @@ easier. <item name="preferenceInformationStyle">@style/Preference.DeviceDefault.Information</item> <item name="checkBoxPreferenceStyle">@style/Preference.DeviceDefault.CheckBoxPreference</item> <item name="switchPreferenceStyle">@style/Preference.DeviceDefault.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@style/Preference.DeviceDefault.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference</item> <item name="editTextPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.EditTextPreference</item> diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml index eba8dec..a571b98 100644 --- a/core/res/res/values/themes_leanback.xml +++ b/core/res/res/values/themes_leanback.xml @@ -14,26 +14,38 @@ limitations under the License. --> <resources> - <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Holo.Dialog.BaseAlert"> - <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item> + <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert"> + <item name="colorBackground">@color/background_leanback_dark</item> + <item name="textColorPrimary">@color/primary_text_leanback_dark</item> + <item name="textColorSecondary">@color/secondary_text_leanback_dark</item> <item name="alertDialogStyle">@style/AlertDialog.Leanback</item> - <item name="buttonBarStyle">@style/Leanback.ButtonBar</item> - <item name="listDividerAlertDialog">@null</item> </style> - <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Holo.Light.Dialog.BaseAlert"> - <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item> + <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert"> + <item name="colorBackground">@color/background_leanback_light</item> + <item name="textColorPrimary">@color/primary_text_leanback_light</item> + <item name="textColorSecondary">@color/secondary_text_leanback_light</item> <item name="alertDialogStyle">@style/AlertDialog.Leanback.Light</item> - <item name="buttonBarStyle">@style/Leanback.ButtonBar</item> - <item name="listDividerAlertDialog">@null</item> </style> - <style name="Theme.Leanback.Light.Dialog" parent="Theme.Holo.Light.Dialog"> - <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item> + <style name="Theme.Leanback.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker"> + <item name="colorBackground">@color/background_leanback_dark</item> + <item name="textColorPrimary">@color/primary_text_leanback_dark</item> + <item name="textColorSecondary">@color/secondary_text_leanback_dark</item> + <item name="alertDialogStyle">@style/AlertDialog.Leanback</item> + </style> + + <style name="Theme.Leanback.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker"> + <item name="colorBackground">@color/background_leanback_light</item> + <item name="textColorPrimary">@color/primary_text_leanback_light</item> + <item name="textColorSecondary">@color/secondary_text_leanback_light</item> + <item name="alertDialogStyle">@style/AlertDialog.Leanback.Light</item> + </style> + + <style name="Theme.Leanback.Light.Dialog" parent="Theme.Material.Light.Dialog"> </style> - <style name="Theme.Leanback.Dialog" parent="Theme.Holo.Dialog"> - <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item> + <style name="Theme.Leanback.Dialog" parent="Theme.Material.Dialog"> </style> <style name="Theme.Leanback.Dialog.AppError" parent="Theme.Leanback.Dialog"> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 97ad7e4..b52b3a0 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -277,17 +277,22 @@ please see themes_device_defaults.xml. <!-- Preference styles --> <item name="preferenceScreenStyle">@style/Preference.Material.PreferenceScreen</item> <item name="preferenceFragmentStyle">@style/PreferenceFragment.Material</item> - <item name="preferenceFragmentPaddingSide">0dip</item> <item name="preferenceCategoryStyle">@style/Preference.Material.Category</item> <item name="preferenceStyle">@style/Preference.Material</item> <item name="preferenceInformationStyle">@style/Preference.Material.Information</item> <item name="checkBoxPreferenceStyle">@style/Preference.Material.CheckBoxPreference</item> <item name="switchPreferenceStyle">@style/Preference.Material.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@style/Preference.Material.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@style/Preference.Material.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@style/Preference.Material.DialogPreference</item> <item name="editTextPreferenceStyle">@style/Preference.Material.DialogPreference.EditTextPreference</item> <item name="ringtonePreferenceStyle">@style/Preference.Material.RingtonePreference</item> <item name="preferenceLayoutChild">@layout/preference_child_material</item> + <item name="preferencePanelStyle">@style/PreferencePanel.Material</item> + <item name="preferenceHeaderPanelStyle">@style/PreferenceHeaderPanel.Material</item> + <item name="preferenceListStyle">@style/PreferenceHeaderList.Material</item> + <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item> + <item name="preferenceFragmentPaddingSide">@dimen/preference_fragment_padding_side_material</item> <item name="detailsElementBackground">?attr/colorBackground</item> <!-- Search widget styles --> @@ -625,17 +630,22 @@ please see themes_device_defaults.xml. <!-- Preference styles --> <item name="preferenceScreenStyle">@style/Preference.Material.PreferenceScreen</item> <item name="preferenceFragmentStyle">@style/PreferenceFragment.Material</item> - <item name="preferenceFragmentPaddingSide">0dip</item> <item name="preferenceCategoryStyle">@style/Preference.Material.Category</item> <item name="preferenceStyle">@style/Preference.Material</item> <item name="preferenceInformationStyle">@style/Preference.Material.Information</item> <item name="checkBoxPreferenceStyle">@style/Preference.Material.CheckBoxPreference</item> <item name="switchPreferenceStyle">@style/Preference.Material.SwitchPreference</item> + <item name="seekBarPreferenceStyle">@style/Preference.Material.SeekBarPreference</item> <item name="yesNoPreferenceStyle">@style/Preference.Material.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@style/Preference.Material.DialogPreference</item> <item name="editTextPreferenceStyle">@style/Preference.Material.DialogPreference.EditTextPreference</item> <item name="ringtonePreferenceStyle">@style/Preference.Material.RingtonePreference</item> <item name="preferenceLayoutChild">@layout/preference_child_material</item> + <item name="preferencePanelStyle">@style/PreferencePanel.Material</item> + <item name="preferenceHeaderPanelStyle">@style/PreferenceHeaderPanel.Material</item> + <item name="preferenceListStyle">@style/PreferenceHeaderList.Material</item> + <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item> + <item name="preferenceFragmentPaddingSide">@dimen/preference_fragment_padding_side_material</item> <item name="detailsElementBackground">?attr/colorBackground</item> <!-- PreferenceFrameLayout attributes --> @@ -1082,25 +1092,29 @@ please see themes_device_defaults.xml. <item name="windowCloseOnTouchOutside">false</item> </style> + <style name="Theme.Material.Dialog.BaseAlert"> + <item name="windowBackground">@color/transparent</item> + <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> + <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> + <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> + </style> + <!-- Material theme for alert dialog windows, which is used by the {@link android.app.AlertDialog} class. This is basically a dialog but sets the background to empty so it can do two-tone backgrounds. For applications targeting Honeycomb or newer, this is the default AlertDialog theme. --> - <style name="Theme.Material.Dialog.Alert"> + <style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert"/> + + <style name="Theme.Material.Dialog.BaseTimePicker"> <item name="windowBackground">@color/transparent</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> - <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> - <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> + <item name="windowContentOverlay">@null</item> </style> <!-- Material theme for the TimePicker dialog windows, which is used by the {@link android.app.TimePickerDialog} class. --> - <style name="Theme.Material.Dialog.TimePicker"> - <item name="windowBackground">@color/transparent</item> - <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> - <item name="windowContentOverlay">@null</item> - </style> + <style name="Theme.Material.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker"/> <!-- Theme for a window that will be displayed either full-screen on smaller screens (small, normal) or as a dialog on larger screens @@ -1196,24 +1210,28 @@ please see themes_device_defaults.xml. (large, xlarge). --> <style name="Theme.Material.Light.DialogWhenLarge.NoActionBar" parent="@style/Theme.Material.Light.NoActionBar" /> + <style name="Theme.Material.Light.Dialog.BaseAlert"> + <item name="windowBackground">@color/transparent</item> + <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> + <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> + <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> + </style> + <!-- Material light theme for alert dialog windows, which is used by the {@link android.app.AlertDialog} class. This is basically a dialog but sets the background to empty so it can do two-tone backgrounds. For applications targeting Honeycomb or newer, this is the default AlertDialog theme. --> - <style name="Theme.Material.Light.Dialog.Alert"> + <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert"/> + + <style name="Theme.Material.Light.Dialog.BaseTimePicker"> <item name="windowBackground">@color/transparent</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> - <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> - <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> </style> <!-- Material Light theme for the TimePicker dialog windows, which is used by the {@link android.app.TimePickerDialog} class. --> - <style name="Theme.Material.Light.Dialog.TimePicker"> - <item name="windowBackground">@color/transparent</item> - <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> - </style> + <style name="Theme.Material.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker"/> <!-- Theme for a presentation window on a secondary display. --> <style name="Theme.Material.Light.Dialog.Presentation" parent="@style/Theme.Material.Light.NoActionBar.Fullscreen" /> diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml index ebdab5b..7e0467b 100644 --- a/core/res/res/values/themes_micro.xml +++ b/core/res/res/values/themes_micro.xml @@ -19,15 +19,14 @@ <item name="alertDialogStyle">@style/AlertDialog.Micro</item> <item name="dialogTheme">@style/Theme.Micro.Dialog</item> <item name="textViewStyle">@style/Widget.Micro.TextView</item> - <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item> - <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item> + <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item> <item name="windowBackground">@color/black</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> <item name="windowIsTranslucent">true</item> <item name="windowSwipeToDismiss">true</item> - </style> + </style> <style name="Theme.Micro.Light" parent="Theme.Holo.Light.NoActionBar"> <item name="alertDialogTheme">@style/Theme.Micro.Dialog.Alert</item> @@ -35,7 +34,7 @@ <item name="dialogTheme">@style/Theme.Micro.Dialog</item> <item name="textViewStyle">@style/Widget.Micro.TextView</item> <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item> - <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item> + <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item> <item name="windowBackground">@color/white</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java index 07a6a10..7f41ac1c 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java @@ -29,6 +29,7 @@ import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageParser.PackageParserException; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.net.Uri; @@ -60,7 +61,6 @@ import android.util.Log; import java.io.File; import java.io.IOException; import java.io.InputStream; - import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -303,14 +303,11 @@ public class PackageManagerTests extends AndroidTestCase { return Uri.fromFile(outFile); } - private PackageParser.Package parsePackage(Uri packageURI) { + private PackageParser.Package parsePackage(Uri packageURI) throws PackageParserException { final String archiveFilePath = packageURI.getPath(); - PackageParser packageParser = new PackageParser(archiveFilePath); + PackageParser packageParser = new PackageParser(); File sourceFile = new File(archiveFilePath); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, - metrics, 0); + PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, 0); packageParser = null; return pkg; } @@ -579,18 +576,18 @@ public class PackageManagerTests extends AndroidTestCase { PackageParser.Package pkg; - InstallParams(String outFileName, int rawResId) { + InstallParams(String outFileName, int rawResId) throws PackageParserException { this.pkg = getParsedPackage(outFileName, rawResId); - this.packageURI = Uri.fromFile(new File(pkg.mScanPath)); + this.packageURI = Uri.fromFile(new File(pkg.codePath)); } InstallParams(PackageParser.Package pkg) { - this.packageURI = Uri.fromFile(new File(pkg.mScanPath)); + this.packageURI = Uri.fromFile(new File(pkg.codePath)); this.pkg = pkg; } long getApkSize() { - File file = new File(pkg.mScanPath); + File file = new File(pkg.codePath); return file.length(); } } @@ -691,7 +688,8 @@ public class PackageManagerTests extends AndroidTestCase { } } - private PackageParser.Package getParsedPackage(String outFileName, int rawResId) { + private PackageParser.Package getParsedPackage(String outFileName, int rawResId) + throws PackageParserException { PackageManager pm = mContext.getPackageManager(); File filesDir = mContext.getFilesDir(); File outFile = new File(filesDir, outFileName); @@ -1343,7 +1341,7 @@ public class PackageManagerTests extends AndroidTestCase { assertUninstalled(info); } } finally { - File outFile = new File(ip.pkg.mScanPath); + File outFile = new File(ip.pkg.codePath); if (outFile != null && outFile.exists()) { outFile.delete(); } diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java new file mode 100644 index 0000000..cf278fb --- /dev/null +++ b/core/tests/coretests/src/android/net/IpPrefixTest.java @@ -0,0 +1,281 @@ +/* + * 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.net; + +import android.net.IpPrefix; +import android.os.Parcel; +import static android.test.MoreAsserts.assertNotEqual; +import android.test.suitebuilder.annotation.SmallTest; + +import static org.junit.Assert.assertArrayEquals; +import java.net.InetAddress; +import java.util.Random; +import junit.framework.TestCase; + + +public class IpPrefixTest extends TestCase { + + // Explicitly cast everything to byte because "error: possible loss of precision". + private static final byte[] IPV4_BYTES = { (byte) 192, (byte) 0, (byte) 2, (byte) 4}; + private static final byte[] IPV6_BYTES = { + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, + (byte) 0x0f, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xa0 + }; + + @SmallTest + public void testConstructor() { + IpPrefix p; + try { + p = new IpPrefix((byte[]) null, 9); + fail("Expected NullPointerException: null byte array"); + } catch(RuntimeException expected) {} + + try { + p = new IpPrefix((InetAddress) null, 10); + fail("Expected NullPointerException: null InetAddress"); + } catch(RuntimeException expected) {} + + try { + p = new IpPrefix((String) null); + fail("Expected NullPointerException: null String"); + } catch(RuntimeException expected) {} + + + try { + byte[] b2 = {1, 2, 3, 4, 5}; + p = new IpPrefix(b2, 29); + fail("Expected IllegalArgumentException: invalid array length"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("1.2.3.4"); + fail("Expected IllegalArgumentException: no prefix length"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("1.2.3.4/"); + fail("Expected IllegalArgumentException: empty prefix length"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("foo/32"); + fail("Expected IllegalArgumentException: invalid address"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("1/32"); + fail("Expected IllegalArgumentException: deprecated IPv4 format"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("1.2.3.256/32"); + fail("Expected IllegalArgumentException: invalid IPv4 address"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("foo/32"); + fail("Expected IllegalArgumentException: non-address"); + } catch(IllegalArgumentException expected) {} + + try { + p = new IpPrefix("f00:::/32"); + fail("Expected IllegalArgumentException: invalid IPv6 address"); + } catch(IllegalArgumentException expected) {} + } + + public void testTruncation() { + IpPrefix p; + + p = new IpPrefix(IPV4_BYTES, 32); + assertEquals("192.0.2.4/32", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 29); + assertEquals("192.0.2.0/29", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 8); + assertEquals("192.0.0.0/8", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 0); + assertEquals("0.0.0.0/0", p.toString()); + + try { + p = new IpPrefix(IPV4_BYTES, 33); + fail("Expected IllegalArgumentException: invalid prefix length"); + } catch(RuntimeException expected) {} + + try { + p = new IpPrefix(IPV4_BYTES, 128); + fail("Expected IllegalArgumentException: invalid prefix length"); + } catch(RuntimeException expected) {} + + try { + p = new IpPrefix(IPV4_BYTES, -1); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch(RuntimeException expected) {} + + p = new IpPrefix(IPV6_BYTES, 128); + assertEquals("2001:db8:dead:beef:f00::a0/128", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 122); + assertEquals("2001:db8:dead:beef:f00::80/122", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 64); + assertEquals("2001:db8:dead:beef::/64", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 3); + assertEquals("2000::/3", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 0); + assertEquals("::/0", p.toString()); + + try { + p = new IpPrefix(IPV6_BYTES, -1); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch(RuntimeException expected) {} + + try { + p = new IpPrefix(IPV6_BYTES, 129); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch(RuntimeException expected) {} + + } + + private void assertAreEqual(Object o1, Object o2) { + assertTrue(o1.equals(o2)); + assertTrue(o2.equals(o1)); + } + + private void assertAreNotEqual(Object o1, Object o2) { + assertFalse(o1.equals(o2)); + assertFalse(o2.equals(o1)); + } + + @SmallTest + public void testEquals() { + IpPrefix p1, p2; + + p1 = new IpPrefix("192.0.2.251/23"); + p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23); + assertAreEqual(p1, p2); + + p1 = new IpPrefix("192.0.2.5/23"); + assertAreEqual(p1, p2); + + p1 = new IpPrefix("192.0.2.5/24"); + assertAreNotEqual(p1, p2); + + p1 = new IpPrefix("192.0.4.5/23"); + assertAreNotEqual(p1, p2); + + + p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122"); + p2 = new IpPrefix(IPV6_BYTES, 122); + assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString()); + assertAreEqual(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122"); + assertAreEqual(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123"); + assertAreNotEqual(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef::/122"); + assertAreNotEqual(p1, p2); + + // 192.0.2.4/32 != c000:0204::/32. + byte[] ipv6bytes = new byte[16]; + System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length); + p1 = new IpPrefix(ipv6bytes, 32); + assertAreEqual(p1, new IpPrefix("c000:0204::/32")); + + p2 = new IpPrefix(IPV4_BYTES, 32); + assertAreNotEqual(p1, p2); + } + + @SmallTest + public void testHashCode() { + IpPrefix p; + int oldCode = -1; + Random random = new Random(); + for (int i = 0; i < 100; i++) { + if (random.nextBoolean()) { + // IPv4. + byte[] b = new byte[4]; + random.nextBytes(b); + p = new IpPrefix(b, random.nextInt(33)); + assertNotEqual(oldCode, p.hashCode()); + oldCode = p.hashCode(); + } else { + // IPv6. + byte[] b = new byte[16]; + random.nextBytes(b); + p = new IpPrefix(b, random.nextInt(129)); + assertNotEqual(oldCode, p.hashCode()); + oldCode = p.hashCode(); + } + } + } + + @SmallTest + public void testMappedAddressesAreBroken() { + // 192.0.2.0/24 != ::ffff:c000:0204/120, but because we use InetAddress, + // we are unable to comprehend that. + byte[] ipv6bytes = { + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff, + (byte) 192, (byte) 0, (byte) 2, (byte) 0}; + IpPrefix p = new IpPrefix(ipv6bytes, 120); + assertEquals(16, p.getRawAddress().length); // Fine. + assertArrayEquals(ipv6bytes, p.getRawAddress()); // Fine. + + // Broken. + assertEquals("192.0.2.0/120", p.toString()); + assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress()); + } + + public IpPrefix passThroughParcel(IpPrefix p) { + Parcel parcel = Parcel.obtain(); + IpPrefix p2 = null; + try { + p.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + p2 = IpPrefix.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + assertNotNull(p2); + return p2; + } + + public void assertParcelingIsLossless(IpPrefix p) { + IpPrefix p2 = passThroughParcel(p); + assertEquals(p, p2); + } + + public void testParceling() { + IpPrefix p; + + p = new IpPrefix("2001:4860:db8::/64"); + assertParcelingIsLossless(p); + + p = new IpPrefix("192.0.2.0/25"); + assertParcelingIsLossless(p); + } +} diff --git a/core/tests/coretests/src/android/net/LinkAddressTest.java b/core/tests/coretests/src/android/net/LinkAddressTest.java index 814ecdd..7bc3974 100644 --- a/core/tests/coretests/src/android/net/LinkAddressTest.java +++ b/core/tests/coretests/src/android/net/LinkAddressTest.java @@ -30,6 +30,7 @@ import java.util.List; import android.net.LinkAddress; import android.os.Parcel; import android.test.AndroidTestCase; +import static android.test.MoreAsserts.assertNotEqual; import android.test.suitebuilder.annotation.SmallTest; import static android.system.OsConstants.IFA_F_DEPRECATED; @@ -50,6 +51,17 @@ public class LinkAddressTest extends AndroidTestCase { private static final InetAddress V4_ADDRESS = NetworkUtils.numericToInetAddress(V4); private static final InetAddress V6_ADDRESS = NetworkUtils.numericToInetAddress(V6); + public void testConstants() { + // RT_SCOPE_UNIVERSE = 0, but all the other constants should be nonzero. + assertNotEqual(0, RT_SCOPE_HOST); + assertNotEqual(0, RT_SCOPE_LINK); + assertNotEqual(0, RT_SCOPE_SITE); + + assertNotEqual(0, IFA_F_DEPRECATED); + assertNotEqual(0, IFA_F_PERMANENT); + assertNotEqual(0, IFA_F_TENTATIVE); + } + public void testConstructors() throws SocketException { LinkAddress address; diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java index c80d0bf..af6a32b 100644 --- a/core/tests/coretests/src/android/net/RouteInfoTest.java +++ b/core/tests/coretests/src/android/net/RouteInfoTest.java @@ -19,7 +19,7 @@ package android.net; import java.lang.reflect.Method; import java.net.InetAddress; -import android.net.LinkAddress; +import android.net.IpPrefix; import android.net.RouteInfo; import android.os.Parcel; @@ -32,9 +32,8 @@ public class RouteInfoTest extends TestCase { return InetAddress.parseNumericAddress(addr); } - private LinkAddress Prefix(String prefix) { - String[] parts = prefix.split("/"); - return new LinkAddress(Address(parts[0]), Integer.parseInt(parts[1])); + private IpPrefix Prefix(String prefix) { + return new IpPrefix(prefix); } @SmallTest @@ -43,17 +42,17 @@ public class RouteInfoTest extends TestCase { // Invalid input. try { - r = new RouteInfo((LinkAddress) null, null, "rmnet0"); + r = new RouteInfo((IpPrefix) null, null, "rmnet0"); fail("Expected RuntimeException: destination and gateway null"); } catch(RuntimeException e) {} // Null destination is default route. - r = new RouteInfo((LinkAddress) null, Address("2001:db8::1"), null); + r = new RouteInfo((IpPrefix) null, Address("2001:db8::1"), null); assertEquals(Prefix("::/0"), r.getDestination()); assertEquals(Address("2001:db8::1"), r.getGateway()); assertNull(r.getInterface()); - r = new RouteInfo((LinkAddress) null, Address("192.0.2.1"), "wlan0"); + r = new RouteInfo((IpPrefix) null, Address("192.0.2.1"), "wlan0"); assertEquals(Prefix("0.0.0.0/0"), r.getDestination()); assertEquals(Address("192.0.2.1"), r.getGateway()); assertEquals("wlan0", r.getInterface()); @@ -74,7 +73,7 @@ public class RouteInfoTest extends TestCase { class PatchedRouteInfo { private final RouteInfo mRouteInfo; - public PatchedRouteInfo(LinkAddress destination, InetAddress gateway, String iface) { + public PatchedRouteInfo(IpPrefix destination, InetAddress gateway, String iface) { mRouteInfo = new RouteInfo(destination, gateway, iface); } diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java new file mode 100644 index 0000000..d4f6b1f --- /dev/null +++ b/core/tests/coretests/src/android/os/FileBridgeTest.java @@ -0,0 +1,156 @@ +/* + * 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.os; + +import android.os.FileBridge.FileBridgeOutputStream; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; + +import libcore.io.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Random; + +public class FileBridgeTest extends AndroidTestCase { + + private File file; + private FileOutputStream fileOs; + private FileBridge bridge; + private FileBridgeOutputStream client; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + file = getContext().getFileStreamPath("meow.dat"); + file.delete(); + + fileOs = new FileOutputStream(file); + + bridge = new FileBridge(); + bridge.setTargetFile(fileOs.getFD()); + bridge.start(); + client = new FileBridgeOutputStream(bridge.getClientSocket()); + } + + @Override + protected void tearDown() throws Exception { + fileOs.close(); + file.delete(); + } + + private void assertOpen() throws Exception { + assertFalse("expected open", bridge.isClosed()); + } + + private void closeAndAssertClosed() throws Exception { + client.close(); + + // Wait a beat for things to settle down + SystemClock.sleep(200); + assertTrue("expected closed", bridge.isClosed()); + } + + private void assertContents(byte[] expected) throws Exception { + MoreAsserts.assertEquals(expected, Streams.readFully(new FileInputStream(file))); + } + + public void testNoWriteNoSync() throws Exception { + assertOpen(); + closeAndAssertClosed(); + } + + public void testNoWriteSync() throws Exception { + assertOpen(); + client.flush(); + closeAndAssertClosed(); + } + + public void testWriteNoSync() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + assertContents("meow".getBytes(StandardCharsets.UTF_8)); + } + + public void testWriteSync() throws Exception { + assertOpen(); + client.write("cake".getBytes(StandardCharsets.UTF_8)); + client.flush(); + closeAndAssertClosed(); + assertContents("cake".getBytes(StandardCharsets.UTF_8)); + } + + public void testWriteSyncWrite() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + client.flush(); + client.write("cake".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + assertContents("meowcake".getBytes(StandardCharsets.UTF_8)); + } + + public void testEmptyWrite() throws Exception { + assertOpen(); + client.write(new byte[0]); + closeAndAssertClosed(); + assertContents(new byte[0]); + } + + public void testWriteAfterClose() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + try { + client.write("cake".getBytes(StandardCharsets.UTF_8)); + fail("wrote after close!"); + } catch (IOException expected) { + } + assertContents("meow".getBytes(StandardCharsets.UTF_8)); + } + + public void testRandomWrite() throws Exception { + final Random r = new Random(); + final ByteArrayOutputStream result = new ByteArrayOutputStream(); + + for (int i = 0; i < 512; i++) { + final byte[] test = new byte[r.nextInt(24169)]; + r.nextBytes(test); + result.write(test); + client.write(test); + client.flush(); + } + + closeAndAssertClosed(); + assertContents(result.toByteArray()); + } + + public void testGiantWrite() throws Exception { + final byte[] test = new byte[263401]; + new Random().nextBytes(test); + + assertOpen(); + client.write(test); + closeAndAssertClosed(); + assertContents(test); + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java index 5dc9ef8..433d4d2 100644 --- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java @@ -16,6 +16,9 @@ package com.android.internal.util; +import android.test.MoreAsserts; + +import java.util.Arrays; import junit.framework.TestCase; /** @@ -77,4 +80,79 @@ public class ArrayUtilsTest extends TestCase { assertFalse(ArrayUtils.containsAll(new Object[] { }, new Object[] { null })); assertFalse(ArrayUtils.containsAll(new Object[] { A }, new Object[] { null })); } + + public void testContainsInt() throws Exception { + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new int[] { }, 2)); + } + + public void testAppendInt() throws Exception { + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(null, 1)); + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1 }, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1, 2 }, 1)); + } + + public void testRemoveInt() throws Exception { + assertNull(ArrayUtils.removeInt(null, 1)); + MoreAsserts.assertEquals(new int[] { }, + ArrayUtils.removeInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new int[] { 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new int[] { 1, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new int[] { 2, 3, 1 }, + ArrayUtils.removeInt(new int[] { 1, 2, 3, 1 }, 1)); + } + + public void testContainsLong() throws Exception { + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new long[] { }, 2)); + } + + public void testAppendLong() throws Exception { + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(null, 1)); + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1 }, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1, 2 }, 1)); + } + + public void testRemoveLong() throws Exception { + assertNull(ArrayUtils.removeLong(null, 1)); + MoreAsserts.assertEquals(new long[] { }, + ArrayUtils.removeLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new long[] { 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new long[] { 1, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new long[] { 2, 3, 1 }, + ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1)); + } + } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk new file mode 100644 index 0000000..d649154 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk @@ -0,0 +1,42 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + + +## The application with a minimal main dex +include $(CLEAR_VARS) + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := MultiDexLegacyAndException + +mainDexList:= \ + $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list + +LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex + +include $(BUILD_PACKAGE) + +$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses + $(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@ + echo "com/android/multidexlegacyandexception/Test.class" >> $@ + +$(built_dex_intermediate): $(mainDexList) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml new file mode 100644 index 0000000..7fff711 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.multidexlegacyandexception" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18"/> + + <application + android:name="com.android.multidexlegacyandexception.TestApplication" + android:label="multidexlegacyandexception" + > + <activity + android:name="com.android.multidexlegacyandexception.MainActivity" + android:label="multidexlegacyandexception" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.multidexlegacyandexception" + android:label="Test for MultiDexLegacyAndException" /> +</manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml new file mode 100644 index 0000000..37eb613 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml @@ -0,0 +1,13 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + tools:context=".MainActivity" > + + <TextView + android:id="@+id/label_nb" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/label_nb" /> + +</RelativeLayout> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml new file mode 100644 index 0000000..e56e049 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">MultidexLegacyAndException</string> + <string name="action_settings">Settings</string> + <string name="label_nb">Here\'s the count: </string> + +</resources> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java new file mode 100644 index 0000000..d6883ec --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class CaughtOnlyByIntermediateException extends RuntimeException { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java new file mode 100644 index 0000000..4903e01 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class CaughtOnlyException extends RuntimeException { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java new file mode 100644 index 0000000..b08a11a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ClassInSecondaryDex { + private boolean condition; + + public ClassInSecondaryDex(boolean condition) { + this.condition = condition; + } + + public void canThrow1() throws ExceptionInMainDex, ExceptionInMainDex2, + ExceptionInSecondaryDexWithSuperInMain { + if (condition) { + throw new ExceptionInMainDex(); + } + } + + public void canThrow2() throws ExceptionInSecondaryDex, ExceptionInSecondaryDex2, + ExceptionInSecondaryDexWithSuperInMain { + if (condition) { + throw new ExceptionInSecondaryDex(); + } + } + + public static void canThrowAll(Throwable toThrow) throws Throwable { + if (toThrow != null) { + throw toThrow; + } + } + + public int get1() { + try { + canThrow1(); + canThrow2(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) { + return 23; + } + } + + public int get2() { + try { + canThrow2(); + canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex e) { + return 23; + } catch (SuperExceptionInMainDex e) { + return 27; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java new file mode 100644 index 0000000..7fc3d73 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ExceptionInMainDex extends SuperExceptionInMainDex { +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java new file mode 100644 index 0000000..3fbeac6 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ExceptionInMainDex2 extends SuperExceptionInMainDex { +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java new file mode 100644 index 0000000..9401c05 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ExceptionInSecondaryDex extends SuperExceptionInSecondaryDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java new file mode 100644 index 0000000..d1aa103 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ExceptionInSecondaryDex2 extends SuperExceptionInSecondaryDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java new file mode 100644 index 0000000..9327882 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class ExceptionInSecondaryDexWithSuperInMain extends SuperExceptionInMainDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java new file mode 100644 index 0000000..dfdc4af --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.multidexlegacyandexception; + +public class IntermediateClass { + + public static int get1(boolean condition) { + return new ClassInSecondaryDex(condition).get1(); + } + + public static int get2(boolean condition) { + return new ClassInSecondaryDex(condition).get2(); + } + + public static int get3(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInMainDex2 e) { + return 10; + } catch (ExceptionInSecondaryDex2 e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) { + return 23; + } + } + + public static int get4(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInSecondaryDex2 e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (ExceptionInMainDex2 e) { + return 10; + } catch (ExceptionInMainDex e) { + return 10; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex e) { + } catch (SuperExceptionInMainDex e) { + } catch (CaughtOnlyByIntermediateException e) { + return 35; + } + return 39; + } + + + public static int get5(Throwable thrown) { + try { + ClassInSecondaryDex.canThrowAll(thrown); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInMainDex2 e) { + return 12; + } catch (ExceptionInSecondaryDex2 e) { + return 13; + } catch (OutOfMemoryError e) { + return 14; + } catch (CaughtOnlyException e) { + return 17; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) { + return 23; + } catch (Throwable e) { + return 37; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java new file mode 100644 index 0000000..dd2ce7a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.multidexlegacyandexception; + +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + public MainActivity() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + } + + public int get1(boolean condition) { + return IntermediateClass.get1(condition); + } + + public int get2(boolean condition) { + return IntermediateClass.get2(condition); + } + public int get3(boolean condition) { + return MiniIntermediateClass.get3(condition); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java new file mode 100644 index 0000000..5957662 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.multidexlegacyandexception; + +public class MiniIntermediateClass { + + public static int get3(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) { + return 23; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java new file mode 100644 index 0000000..c94b30a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class SuperExceptionInMainDex extends Exception { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java new file mode 100644 index 0000000..6366fae --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.multidexlegacyandexception; + +public class SuperExceptionInSecondaryDex extends Exception { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java new file mode 100644 index 0000000..5e931bc --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.multidexlegacyandexception; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Run the tests with: <code>adb shell am instrument -w + com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner +</code> + */ +public class Test extends ActivityInstrumentationTestCase2<MainActivity> { + public Test() { + super(MainActivity.class); + } + + public void testExceptionInMainDex() { + assertEquals(10, TestApplication.get(true)); + } + + public void testExceptionInSecondaryDex() { + assertEquals(10, getActivity().get1(true)); + assertEquals(11, getActivity().get2(true)); + } + + public void testExceptionInIntermediate() { + assertEquals(11, IntermediateClass.get3(true)); + assertEquals(11, MiniIntermediateClass.get3(true)); + assertEquals(11, IntermediateClass.get4(true)); + assertEquals(1, IntermediateClass.get5(null)); + assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex())); + assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex())); + assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2())); + assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2())); + assertEquals(14, IntermediateClass.get5(new OutOfMemoryError())); + assertEquals(17, IntermediateClass.get5(new CaughtOnlyException())); + assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain())); + assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex())); + assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex())); + assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException())); + assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException())); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java new file mode 100644 index 0000000..dece9a4 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.multidexlegacyandexception; + +import android.support.multidex.MultiDexApplication; + +public class TestApplication extends MultiDexApplication { + + private static void canThrow1(boolean condition) throws ExceptionInMainDex { + if (condition) { + throw new ExceptionInMainDex(); + } + } + + + public static int get(boolean condition) { + try { + canThrow1(condition); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInMainDex e) { + return 27; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java index 20fe465..7b83999 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java @@ -31,7 +31,7 @@ import java.io.RandomAccessFile; * Empty service for testing legacy multidex. Access more than 64k methods but some are required at * init, some only at verification and others during execution. */ -public abstract class AbstractService extends Service { +public abstract class AbstractService extends Service implements Runnable { private final String TAG = "MultidexLegacyTestService" + getId(); private int instanceFieldNotInited; @@ -47,6 +47,12 @@ public abstract class AbstractService extends Service { @Override public void onCreate() { Log.i(TAG, "onCreate"); + new Thread(this).start(); + + } + + @Override + public void run() { Context applicationContext = getApplicationContext(); File resultFile = new File(applicationContext.getFilesDir(), getId()); try { @@ -84,7 +90,6 @@ public abstract class AbstractService extends Service { } catch (IOException e) { e.printStackTrace(); } - } @Override diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index 0f343b1..ca68e93 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -25,8 +25,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; -import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; +import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; +import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +40,7 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe private static final boolean DUMMY_FORCE_DEFAULT = false; private static final int DUMMY_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; + private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static InputMethodSubtype createDummySubtype(final String locale) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); @@ -64,142 +66,233 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe si.exported = true; si.nonLocalizedLabel = imeLabel; ri.serviceInfo = si; - final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); - for (String subtypeLocale : subtypeLocales) { - subtypes.add(createDummySubtype(subtypeLocale)); + List<InputMethodSubtype> subtypes = null; + if (subtypeLocales != null) { + subtypes = new ArrayList<InputMethodSubtype>(); + for (String subtypeLocale : subtypeLocales) { + subtypes.add(createDummySubtype(subtypeLocale)); + } } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); - for (int i = 0; i < subtypes.size(); ++i) { - final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + if (subtypes == null) { + items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, + NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); + } else { + for (int i = 0; i < subtypes.size(); ++i) { + final String subtypeLocale = subtypeLocales.get(i); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, + SYSTEM_LOCALE)); + } } } - private static List<ImeSubtypeListItem> createTestData() { + private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme", - Arrays.asList("en_US", "es_US", "fr"), + addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme", + addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", Arrays.asList("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", + Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); + return items; + } + + private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { + final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); + addDummyImeSubtypeListItems(items, + "UnknownIme", "UnknownIme", + Arrays.asList("en_US", "hi"), + true /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, + "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", + Arrays.asList("en_US"), + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", + "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } + private void assertNextInputMethod(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { + InputMethodSubtype subtype = null; + if (currentItem.mSubtypeName != null) { + subtype = createDummySubtype(currentItem.mSubtypeName.toString()); + } + final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, + currentItem.mImi, subtype); + assertEquals(nextItem, nextIme); + } + + private void assertRotationOrder(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { + final int N = expectedRotationOrderOfImeSubtypeList.length; + for (int i = 0; i < N; i++) { + final int currentIndex = i; + final int nextIndex = (currentIndex + 1) % N; + final ImeSubtypeListItem currentItem = + expectedRotationOrderOfImeSubtypeList[currentIndex]; + final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; + assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem); + } + } + + private void onUserAction(final ControllerImpl controller, + final ImeSubtypeListItem subtypeListItem) { + InputMethodSubtype subtype = null; + if (subtypeListItem.mSubtypeName != null) { + subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString()); + } + controller.onUserActionLocked(subtypeListItem.mImi, subtype); + } + @SmallTest - public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = false; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(5), nextIme); - // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(6), nextIme); - // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK" - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); + public void testControllerImpl() throws Exception { + final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); + final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); + final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); + final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); + final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); + + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); + + // switching-aware loop + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + + // switching-unaware loop + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + + // test onlyCurrentIme == true + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertRotationOrder(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + + // Make sure that disabled IMEs are not accepted. + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); } @SmallTest - public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = true; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); - - // "switchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); - - // "nonSwitchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); + public void testControllerImplWithUserAction() throws Exception { + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); + + // === switching-aware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr again. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for japaneseIme_ja_JP. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + // Check onlyCurrentIme == true. + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + + // === switching-unaware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchingUnawarelatinIme_hi); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchUnawareJapaneseIme_ja_JP); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // Check onlyCurrentIme == true. + assertRotationOrder(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + + // Rotation order should be preserved when created with the same subtype list. + final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); + final ControllerImpl newController = ControllerImpl.createFrom(controller, + sameEnabledItems); + assertRotationOrder(newController, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + assertRotationOrder(newController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + + // Rotation order should be initialized when created with a different subtype list. + final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( + latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK, + switchUnawareJapaneseIme_ja_JP); + final ControllerImpl anotherController = ControllerImpl.createFrom(controller, + differentEnabledItems); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); } - } +} diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java index 58b7db9..42de9ea 100644 --- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java +++ b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java @@ -36,8 +36,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { } } - private void setLocale(String code) { - Locale locale = new Locale(code); + private void setLocale(Locale locale) { Locale.setDefault(locale); Configuration config = new Configuration(); config.locale = locale; @@ -268,193 +267,193 @@ public abstract class OverlayBaseTest extends AndroidTestCase { */ public void testMatrix100000() throws Throwable { final int resId = R.integer.matrix_100000; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 100); } public void testMatrix100001() throws Throwable { final int resId = R.integer.matrix_100001; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 600); } public void testMatrix100010() throws Throwable { final int resId = R.integer.matrix_100010; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 500); } public void testMatrix100011() throws Throwable { final int resId = R.integer.matrix_100011; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 600); } public void testMatrix100100() throws Throwable { final int resId = R.integer.matrix_100100; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } public void testMatrix100101() throws Throwable { final int resId = R.integer.matrix_100101; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } public void testMatrix100110() throws Throwable { final int resId = R.integer.matrix_100110; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } public void testMatrix100111() throws Throwable { final int resId = R.integer.matrix_100111; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } public void testMatrix101000() throws Throwable { final int resId = R.integer.matrix_101000; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 300); } public void testMatrix101001() throws Throwable { final int resId = R.integer.matrix_101001; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 600); } public void testMatrix101010() throws Throwable { final int resId = R.integer.matrix_101010; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 500); } public void testMatrix101011() throws Throwable { final int resId = R.integer.matrix_101011; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 600); } public void testMatrix101100() throws Throwable { final int resId = R.integer.matrix_101100; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } public void testMatrix101101() throws Throwable { final int resId = R.integer.matrix_101101; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } public void testMatrix101110() throws Throwable { final int resId = R.integer.matrix_101110; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } public void testMatrix101111() throws Throwable { final int resId = R.integer.matrix_101111; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } public void testMatrix110000() throws Throwable { final int resId = R.integer.matrix_110000; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } public void testMatrix110001() throws Throwable { final int resId = R.integer.matrix_110001; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } public void testMatrix110010() throws Throwable { final int resId = R.integer.matrix_110010; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } public void testMatrix110011() throws Throwable { final int resId = R.integer.matrix_110011; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } public void testMatrix110100() throws Throwable { final int resId = R.integer.matrix_110100; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } public void testMatrix110101() throws Throwable { final int resId = R.integer.matrix_110101; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } public void testMatrix110110() throws Throwable { final int resId = R.integer.matrix_110110; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } public void testMatrix110111() throws Throwable { final int resId = R.integer.matrix_110111; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } public void testMatrix111000() throws Throwable { final int resId = R.integer.matrix_111000; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } public void testMatrix111001() throws Throwable { final int resId = R.integer.matrix_111001; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } public void testMatrix111010() throws Throwable { final int resId = R.integer.matrix_111010; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } public void testMatrix111011() throws Throwable { final int resId = R.integer.matrix_111011; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } public void testMatrix111100() throws Throwable { final int resId = R.integer.matrix_111100; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } public void testMatrix111101() throws Throwable { final int resId = R.integer.matrix_111101; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } public void testMatrix111110() throws Throwable { final int resId = R.integer.matrix_111110; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } public void testMatrix111111() throws Throwable { final int resId = R.integer.matrix_111111; - setLocale("sv_SE"); + setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } } |