diff options
223 files changed, 9897 insertions, 2689 deletions
@@ -737,7 +737,8 @@ sample_groups := -samplegroup Background \ -samplegroup Sensors \ -samplegroup Testing \ -samplegroup UI \ - -samplegroup Views + -samplegroup Views \ + -samplegroup Wearable ## SDK version identifiers used in the published docs # major[.minor] version for current SDK. (full releases only) diff --git a/api/current.txt b/api/current.txt index faf0b04..f14daa7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3052,9 +3052,10 @@ package android.animation { method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect); } - public class StateListAnimator { + public class StateListAnimator implements java.lang.Cloneable { ctor public StateListAnimator(); method public void addState(int[], android.animation.Animator); + method public android.animation.StateListAnimator clone(); method public void jumpToCurrentState(); } @@ -17034,6 +17035,7 @@ package android.net { } public class Network implements android.os.Parcelable { + method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; method public void bindSocket(java.net.Socket) throws java.io.IOException; method public int describeContents(); method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; @@ -28219,6 +28221,7 @@ package android.telecom { method public android.telecom.PhoneAccountHandle getAccountHandle(); method public android.net.Uri getAddress(); method public int getCapabilities(); + method public int getColor(); method public android.graphics.drawable.Drawable getIcon(android.content.Context); method public int getIconResId(); method public java.lang.CharSequence getLabel(); @@ -28232,6 +28235,7 @@ package android.telecom { field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10 field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4 field public static final android.os.Parcelable.Creator CREATOR; + field public static final int NO_COLOR = -1; // 0xffffffff field public static final java.lang.String SCHEME_SIP = "sip"; field public static final java.lang.String SCHEME_TEL = "tel"; field public static final java.lang.String SCHEME_VOICEMAIL = "voicemail"; @@ -28243,6 +28247,7 @@ package android.telecom { method public android.telecom.PhoneAccount build(); method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri); method public android.telecom.PhoneAccount.Builder setCapabilities(int); + method public android.telecom.PhoneAccount.Builder setColor(int); method public android.telecom.PhoneAccount.Builder setIconResId(int); method public android.telecom.PhoneAccount.Builder setShortDescription(java.lang.CharSequence); method public android.telecom.PhoneAccount.Builder setSubscriptionAddress(android.net.Uri); @@ -28821,6 +28826,9 @@ package android.telephony { ctor public SubInfoRecord(); ctor public SubInfoRecord(long, java.lang.String, int, java.lang.String, int, int, java.lang.String, int, int, int[], int, int); method public int describeContents(); + method public int getColor(); + method public android.graphics.drawable.BitmapDrawable getIconDrawable(); + method public java.lang.String getLabel(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public int color; @@ -34909,6 +34917,7 @@ package android.view { field public static final int FLAG_HARDWARE_ACCELERATED = 16777216; // 0x1000000 field public static final int FLAG_IGNORE_CHEEK_PRESSES = 32768; // 0x8000 field public static final int FLAG_KEEP_SCREEN_ON = 128; // 0x80 + field public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 1073741824; // 0x40000000 field public static final int FLAG_LAYOUT_INSET_DECOR = 65536; // 0x10000 field public static final int FLAG_LAYOUT_IN_OVERSCAN = 33554432; // 0x2000000 field public static final int FLAG_LAYOUT_IN_SCREEN = 256; // 0x100 @@ -35423,13 +35432,13 @@ package android.view.accessibility { package android.view.animation { - public class AccelerateDecelerateInterpolator implements android.view.animation.Interpolator { + public class AccelerateDecelerateInterpolator extends android.view.animation.BaseInterpolator { ctor public AccelerateDecelerateInterpolator(); ctor public AccelerateDecelerateInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class AccelerateInterpolator implements android.view.animation.Interpolator { + public class AccelerateInterpolator extends android.view.animation.BaseInterpolator { ctor public AccelerateInterpolator(); ctor public AccelerateInterpolator(float); ctor public AccelerateInterpolator(android.content.Context, android.util.AttributeSet); @@ -35531,14 +35540,14 @@ package android.view.animation { method public static android.view.animation.Animation makeOutAnimation(android.content.Context, boolean); } - public class AnticipateInterpolator implements android.view.animation.Interpolator { + public class AnticipateInterpolator extends android.view.animation.BaseInterpolator { ctor public AnticipateInterpolator(); ctor public AnticipateInterpolator(float); ctor public AnticipateInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class AnticipateOvershootInterpolator implements android.view.animation.Interpolator { + public class AnticipateOvershootInterpolator extends android.view.animation.BaseInterpolator { ctor public AnticipateOvershootInterpolator(); ctor public AnticipateOvershootInterpolator(float); ctor public AnticipateOvershootInterpolator(float, float); @@ -35546,19 +35555,23 @@ package android.view.animation { method public float getInterpolation(float); } - public class BounceInterpolator implements android.view.animation.Interpolator { + public abstract class BaseInterpolator implements android.view.animation.Interpolator { + ctor public BaseInterpolator(); + } + + public class BounceInterpolator extends android.view.animation.BaseInterpolator { ctor public BounceInterpolator(); ctor public BounceInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class CycleInterpolator implements android.view.animation.Interpolator { + public class CycleInterpolator extends android.view.animation.BaseInterpolator { ctor public CycleInterpolator(float); ctor public CycleInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class DecelerateInterpolator implements android.view.animation.Interpolator { + public class DecelerateInterpolator extends android.view.animation.BaseInterpolator { ctor public DecelerateInterpolator(); ctor public DecelerateInterpolator(float); ctor public DecelerateInterpolator(android.content.Context, android.util.AttributeSet); @@ -35633,20 +35646,20 @@ package android.view.animation { field public int index; } - public class LinearInterpolator implements android.view.animation.Interpolator { + public class LinearInterpolator extends android.view.animation.BaseInterpolator { ctor public LinearInterpolator(); ctor public LinearInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class OvershootInterpolator implements android.view.animation.Interpolator { + public class OvershootInterpolator extends android.view.animation.BaseInterpolator { ctor public OvershootInterpolator(); ctor public OvershootInterpolator(float); ctor public OvershootInterpolator(android.content.Context, android.util.AttributeSet); method public float getInterpolation(float); } - public class PathInterpolator implements android.view.animation.Interpolator { + public class PathInterpolator extends android.view.animation.BaseInterpolator { ctor public PathInterpolator(android.graphics.Path); ctor public PathInterpolator(float, float); ctor public PathInterpolator(float, float, float, float); @@ -38172,6 +38185,7 @@ package android.widget { method public int getSoftInputMode(); method public int getWidth(); method public boolean isAboveAnchor(); + method public boolean isAttachedInDecor(); method public boolean isClippingEnabled(); method public boolean isFocusable(); method public boolean isOutsideTouchable(); @@ -38179,6 +38193,7 @@ package android.widget { method public boolean isSplitTouchEnabled(); method public boolean isTouchable(); method public void setAnimationStyle(int); + method public void setAttachedInDecor(boolean); method public void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setClippingEnabled(boolean); method public void setContentView(android.view.View); diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 3720c81..da48709 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -16,6 +16,8 @@ package android.animation; +import android.content.res.ConstantState; + import java.util.ArrayList; /** @@ -41,6 +43,18 @@ public abstract class Animator implements Cloneable { boolean mPaused = false; /** + * A set of flags which identify the type of configuration changes that can affect this + * Animator. Used by the Animator cache. + */ + int mChangingConfigurations = 0; + + /** + * If this animator is inflated from a constant state, keep a reference to it so that + * ConstantState will not be garbage collected until this animator is collected + */ + private AnimatorConstantState mConstantState; + + /** * Starts this animation. If the animation has a nonzero startDelay, the animation will start * running after that delay elapses. A non-delayed animation will have its initial * value(s) set immediately, followed by calls to @@ -295,25 +309,71 @@ public abstract class Animator implements Cloneable { } } + /** + * Return a mask of the configuration parameters for which this animator may change, requiring + * that it should be re-created from Resources. The default implementation returns whatever + * value was provided through setChangingConfigurations(int) or 0 by default. + * + * @return Returns a mask of the changing configuration parameters, as defined by + * {@link android.content.pm.ActivityInfo}. + * @see android.content.pm.ActivityInfo + * @hide + */ + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + /** + * Set a mask of the configuration parameters for which this animator may change, requiring + * that it be re-created from resource. + * + * @param configs A mask of the changing configuration parameters, as + * defined by {@link android.content.pm.ActivityInfo}. + * + * @see android.content.pm.ActivityInfo + * @hide + */ + public void setChangingConfigurations(int configs) { + mChangingConfigurations = configs; + } + + /** + * Sets the changing configurations value to the union of the current changing configurations + * and the provided configs. + * This method is called while loading the animator. + * @hide + */ + public void appendChangingConfigurations(int configs) { + mChangingConfigurations |= configs; + } + + /** + * Return a {@link android.content.res.ConstantState} instance that holds the shared state of + * this Animator. + * <p> + * This constant state is used to create new instances of this animator when needed, instead + * of re-loading it from resources. Default implementation creates a new + * {@link AnimatorConstantState}. You can override this method to provide your custom logic or + * return null if you don't want this animator to be cached. + * + * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator. + * @see android.content.res.ConstantState + * @see #clone() + * @hide + */ + public ConstantState<Animator> createConstantState() { + return new AnimatorConstantState(this); + } + @Override public Animator clone() { try { final Animator anim = (Animator) super.clone(); if (mListeners != null) { - ArrayList<AnimatorListener> oldListeners = mListeners; - anim.mListeners = new ArrayList<AnimatorListener>(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mListeners.add(oldListeners.get(i)); - } + anim.mListeners = new ArrayList<AnimatorListener>(mListeners); } if (mPauseListeners != null) { - ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners; - anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mPauseListeners.add(oldListeners.get(i)); - } + anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners); } return anim; } catch (CloneNotSupportedException e) { @@ -469,4 +529,35 @@ public abstract class Animator implements Cloneable { public void setAllowRunningAsynchronously(boolean mayRunAsync) { // It is up to subclasses to support this, if they can. } + + /** + * Creates a {@link ConstantState} which holds changing configurations information associated + * with the given Animator. + * <p> + * When {@link #newInstance()} is called, default implementation clones the Animator. + */ + private static class AnimatorConstantState extends ConstantState<Animator> { + + final Animator mAnimator; + int mChangingConf; + + public AnimatorConstantState(Animator animator) { + mAnimator = animator; + // ensure a reference back to here so that constante state is not gc'ed. + mAnimator.mConstantState = this; + mChangingConf = mAnimator.getChangingConfigurations(); + } + + @Override + public int getChangingConfigurations() { + return mChangingConf; + } + + @Override + public Animator newInstance() { + final Animator clone = mAnimator.clone(); + clone.mConstantState = this; + return clone; + } + } } diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 25417ed..688d7e4 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -16,6 +16,8 @@ package android.animation; import android.content.Context; +import android.content.res.ConfigurationBoundResourceCache; +import android.content.res.ConstantState; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.Resources.Theme; @@ -30,6 +32,8 @@ import android.util.TypedValue; import android.util.Xml; import android.view.InflateException; import android.view.animation.AnimationUtils; +import android.view.animation.BaseInterpolator; +import android.view.animation.Interpolator; import com.android.internal.R; @@ -67,6 +71,9 @@ public class AnimatorInflater { private static final boolean DBG_ANIMATOR_INFLATER = false; + // used to calculate changing configs for resource references + private static final TypedValue sTmpTypedValue = new TypedValue(); + /** * Loads an {@link Animator} object from a resource * @@ -98,11 +105,34 @@ public class AnimatorInflater { /** @hide */ public static Animator loadAnimator(Resources resources, Theme theme, int id, float pathErrorScale) throws NotFoundException { - + final ConfigurationBoundResourceCache<Animator> animatorCache = resources + .getAnimatorCache(); + Animator animator = animatorCache.get(id, theme); + if (animator != null) { + if (DBG_ANIMATOR_INFLATER) { + Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id)); + } + return animator; + } else if (DBG_ANIMATOR_INFLATER) { + Log.d(TAG, "cache miss for animator " + resources.getResourceName(id)); + } XmlResourceParser parser = null; try { parser = resources.getAnimation(id); - return createAnimatorFromXml(resources, theme, parser, pathErrorScale); + animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale); + if (animator != null) { + animator.appendChangingConfigurations(getChangingConfigs(resources, id)); + final ConstantState<Animator> constantState = animator.createConstantState(); + if (constantState != null) { + if (DBG_ANIMATOR_INFLATER) { + Log.d(TAG, "caching animator for res " + resources.getResourceName(id)); + } + animatorCache.put(id, theme, constantState); + // create a new animator so that cached version is never used by the user + animator = constantState.newInstance(resources, theme); + } + } + return animator; } catch (XmlPullParserException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException("Can't load animation resource ID #0x" + @@ -122,10 +152,29 @@ public class AnimatorInflater { public static StateListAnimator loadStateListAnimator(Context context, int id) throws NotFoundException { + final Resources resources = context.getResources(); + final ConfigurationBoundResourceCache<StateListAnimator> cache = resources + .getStateListAnimatorCache(); + final Theme theme = context.getTheme(); + StateListAnimator animator = cache.get(id, theme); + if (animator != null) { + return animator; + } XmlResourceParser parser = null; try { - parser = context.getResources().getAnimation(id); - return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser)); + parser = resources.getAnimation(id); + animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser)); + if (animator != null) { + animator.appendChangingConfigurations(getChangingConfigs(resources, id)); + final ConstantState<StateListAnimator> constantState = animator + .createConstantState(); + if (constantState != null) { + cache.put(id, theme, constantState); + // return a clone so that the animator in constant state is never used. + animator = constantState.newInstance(resources, theme); + } + } + return animator; } catch (XmlPullParserException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException( @@ -172,14 +221,13 @@ public class AnimatorInflater { for (int i = 0; i < attributeCount; i++) { int attrName = attributeSet.getAttributeNameResource(i); if (attrName == R.attr.animation) { - animator = loadAnimator(context, - attributeSet.getAttributeResourceValue(i, 0)); + final int animId = attributeSet.getAttributeResourceValue(i, 0); + animator = loadAnimator(context, animId); } else { states[stateIndex++] = attributeSet.getAttributeBooleanValue(i, false) ? attrName : -attrName; } - } if (animator == null) { animator = createAnimatorFromXml(context.getResources(), @@ -192,7 +240,6 @@ public class AnimatorInflater { } stateListAnimator .addState(StateSet.trimStateSet(states, stateIndex), animator); - } break; } @@ -508,7 +555,6 @@ public class AnimatorInflater { private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize) throws XmlPullParserException, IOException { - Animator anim = null; ArrayList<Animator> childAnims = null; @@ -537,8 +583,8 @@ public class AnimatorInflater { } else { a = res.obtainAttributes(attrs, R.styleable.AnimatorSet); } - int ordering = a.getInt(R.styleable.AnimatorSet_ordering, - TOGETHER); + anim.appendChangingConfigurations(a.getChangingConfigurations()); + int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER); createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering, pixelSize); a.recycle(); @@ -565,7 +611,6 @@ public class AnimatorInflater { parent.playSequentially(animsArray); } } - return anim; } @@ -591,7 +636,6 @@ public class AnimatorInflater { private static ValueAnimator loadAnimator(Resources res, Theme theme, AttributeSet attrs, ValueAnimator anim, float pathErrorScale) throws NotFoundException { - TypedArray arrayAnimator = null; TypedArray arrayObjectAnimator = null; @@ -609,25 +653,37 @@ public class AnimatorInflater { } else { arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator); } + anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations()); } if (anim == null) { anim = new ValueAnimator(); } + anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations()); parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale); - final int resID = - arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0); + final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0); if (resID > 0) { - anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID)); + final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID); + if (interpolator instanceof BaseInterpolator) { + anim.appendChangingConfigurations( + ((BaseInterpolator) interpolator).getChangingConfiguration()); + } + anim.setInterpolator(interpolator); } arrayAnimator.recycle(); if (arrayObjectAnimator != null) { arrayObjectAnimator.recycle(); } - return anim; } + + private static int getChangingConfigs(Resources resources, int id) { + synchronized (sTmpTypedValue) { + resources.getValue(id, sTmpTypedValue, true); + return sTmpTypedValue.changingConfigurations; + } + } } diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 0aa8fdd..92762c3 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -241,6 +241,19 @@ public final class AnimatorSet extends Animator { } /** + * @hide + */ + @Override + public int getChangingConfigurations() { + int conf = super.getChangingConfigurations(); + final int nodeCount = mNodes.size(); + for (int i = 0; i < nodeCount; i ++) { + conf |= mNodes.get(i).animation.getChangingConfigurations(); + } + return conf; + } + + /** * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} * of this AnimatorSet. The default value is null, which means that no interpolator * is set on this AnimatorSet. Setting the interpolator to any non-null value @@ -628,23 +641,25 @@ public final class AnimatorSet extends Animator { * manually, as we clone each Node (and its animation). The clone will then be sorted, * and will populate any appropriate lists, when it is started. */ + final int nodeCount = mNodes.size(); anim.mNeedsSort = true; anim.mTerminated = false; anim.mStarted = false; anim.mPlayingSet = new ArrayList<Animator>(); anim.mNodeMap = new HashMap<Animator, Node>(); - anim.mNodes = new ArrayList<Node>(); - anim.mSortedNodes = new ArrayList<Node>(); + anim.mNodes = new ArrayList<Node>(nodeCount); + anim.mSortedNodes = new ArrayList<Node>(nodeCount); anim.mReversible = mReversible; anim.mSetListener = null; // Walk through the old nodes list, cloning each node and adding it to the new nodemap. // One problem is that the old node dependencies point to nodes in the old AnimatorSet. // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. - HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new> - for (Node node : mNodes) { + + for (int n = 0; n < nodeCount; n++) { + final Node node = mNodes.get(n); Node nodeClone = node.clone(); - nodeCloneMap.put(node, nodeClone); + node.mTmpClone = nodeClone; anim.mNodes.add(nodeClone); anim.mNodeMap.put(nodeClone.animation, nodeClone); // Clear out the dependencies in the clone; we'll set these up manually later @@ -652,40 +667,50 @@ public final class AnimatorSet extends Animator { nodeClone.tmpDependencies = null; nodeClone.nodeDependents = null; nodeClone.nodeDependencies = null; + // clear out any listeners that were set up by the AnimatorSet; these will // be set up when the clone's nodes are sorted - ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); + final ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); if (cloneListeners != null) { - ArrayList<AnimatorListener> listenersToRemove = null; - for (AnimatorListener listener : cloneListeners) { + for (int i = cloneListeners.size() - 1; i >= 0; i--) { + final AnimatorListener listener = cloneListeners.get(i); if (listener instanceof AnimatorSetListener) { - if (listenersToRemove == null) { - listenersToRemove = new ArrayList<AnimatorListener>(); - } - listenersToRemove.add(listener); - } - } - if (listenersToRemove != null) { - for (AnimatorListener listener : listenersToRemove) { - cloneListeners.remove(listener); + cloneListeners.remove(i); } } } } // Now that we've cloned all of the nodes, we're ready to walk through their // dependencies, mapping the old dependencies to the new nodes - for (Node node : mNodes) { - Node nodeClone = nodeCloneMap.get(node); + for (int n = 0; n < nodeCount; n++) { + final Node node = mNodes.get(n); + final Node clone = node.mTmpClone; if (node.dependencies != null) { - for (Dependency dependency : node.dependencies) { - Node clonedDependencyNode = nodeCloneMap.get(dependency.node); - Dependency cloneDependency = new Dependency(clonedDependencyNode, + clone.dependencies = new ArrayList<Dependency>(node.dependencies.size()); + final int depSize = node.dependencies.size(); + for (int i = 0; i < depSize; i ++) { + final Dependency dependency = node.dependencies.get(i); + Dependency cloneDependency = new Dependency(dependency.node.mTmpClone, dependency.rule); - nodeClone.addDependency(cloneDependency); + clone.dependencies.add(cloneDependency); + } + } + if (node.nodeDependents != null) { + clone.nodeDependents = new ArrayList<Node>(node.nodeDependents.size()); + for (Node dep : node.nodeDependents) { + clone.nodeDependents.add(dep.mTmpClone); + } + } + if (node.nodeDependencies != null) { + clone.nodeDependencies = new ArrayList<Node>(node.nodeDependencies.size()); + for (Node dep : node.nodeDependencies) { + clone.nodeDependencies.add(dep.mTmpClone); } } } - + for (int n = 0; n < nodeCount; n++) { + mNodes.get(n).mTmpClone = null; + } return anim; } @@ -1017,6 +1042,11 @@ public final class AnimatorSet extends Animator { public boolean done = false; /** + * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete + */ + private Node mTmpClone = null; + + /** * Constructs the Node with the animation that it encapsulates. A Node has no * dependencies by default; dependencies are added via the addDependency() * method. diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java index 12e5862..abac246 100644 --- a/core/java/android/animation/FloatKeyframeSet.java +++ b/core/java/android/animation/FloatKeyframeSet.java @@ -19,6 +19,7 @@ package android.animation; import android.animation.Keyframe.FloatKeyframe; import java.util.ArrayList; +import java.util.List; /** * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate @@ -47,8 +48,8 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes { @Override public FloatKeyframeSet clone() { - ArrayList<Keyframe> keyframes = mKeyframes; - int numKeyframes = mKeyframes.size(); + final List<Keyframe> keyframes = mKeyframes; + final int numKeyframes = mKeyframes.size(); FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes]; for (int i = 0; i < numKeyframes; ++i) { newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone(); diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java index 7a5b0ec..0ec5138 100644 --- a/core/java/android/animation/IntKeyframeSet.java +++ b/core/java/android/animation/IntKeyframeSet.java @@ -19,6 +19,7 @@ package android.animation; import android.animation.Keyframe.IntKeyframe; import java.util.ArrayList; +import java.util.List; /** * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate @@ -47,7 +48,7 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes { @Override public IntKeyframeSet clone() { - ArrayList<Keyframe> keyframes = mKeyframes; + List<Keyframe> keyframes = mKeyframes; int numKeyframes = mKeyframes.size(); IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes]; for (int i = 0; i < numKeyframes; ++i) { diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java index 8d15db2..0e99bff 100644 --- a/core/java/android/animation/KeyframeSet.java +++ b/core/java/android/animation/KeyframeSet.java @@ -18,6 +18,8 @@ package android.animation; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; + import android.animation.Keyframe.IntKeyframe; import android.animation.Keyframe.FloatKeyframe; import android.animation.Keyframe.ObjectKeyframe; @@ -36,16 +38,16 @@ class KeyframeSet implements Keyframes { Keyframe mFirstKeyframe; Keyframe mLastKeyframe; TimeInterpolator mInterpolator; // only used in the 2-keyframe case - ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes + List<Keyframe> mKeyframes; // only used when there are not 2 keyframes TypeEvaluator mEvaluator; public KeyframeSet(Keyframe... keyframes) { mNumKeyframes = keyframes.length; - mKeyframes = new ArrayList<Keyframe>(); - mKeyframes.addAll(Arrays.asList(keyframes)); - mFirstKeyframe = mKeyframes.get(0); - mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); + // immutable list + mKeyframes = Arrays.asList(keyframes); + mFirstKeyframe = keyframes[0]; + mLastKeyframe = keyframes[mNumKeyframes - 1]; mInterpolator = mLastKeyframe.getInterpolator(); } @@ -57,7 +59,7 @@ class KeyframeSet implements Keyframes { public void invalidateCache() { } - public ArrayList<Keyframe> getKeyframes() { + public List<Keyframe> getKeyframes() { return mKeyframes; } @@ -177,9 +179,9 @@ class KeyframeSet implements Keyframes { @Override public KeyframeSet clone() { - ArrayList<Keyframe> keyframes = mKeyframes; + List<Keyframe> keyframes = mKeyframes; int numKeyframes = mKeyframes.size(); - Keyframe[] newKeyframes = new Keyframe[numKeyframes]; + final Keyframe[] newKeyframes = new Keyframe[numKeyframes]; for (int i = 0; i < numKeyframes; ++i) { newKeyframes[i] = keyframes.get(i).clone(); } diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java index 6611c6c..c921466 100644 --- a/core/java/android/animation/Keyframes.java +++ b/core/java/android/animation/Keyframes.java @@ -16,6 +16,7 @@ package android.animation; import java.util.ArrayList; +import java.util.List; /** * This interface abstracts a collection of Keyframe objects and is called by @@ -62,7 +63,7 @@ interface Keyframes extends Cloneable { * @return A list of all Keyframes contained by this. This may return null if this is * not made up of Keyframes. */ - ArrayList<Keyframe> getKeyframes(); + List<Keyframe> getKeyframes(); Keyframes clone(); diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index d372933..97426c3 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -27,6 +27,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** @@ -791,7 +792,7 @@ public class PropertyValuesHolder implements Cloneable { // check to make sure that mProperty is on the class of target try { Object testValue = null; - ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + List<Keyframe> keyframes = mKeyframes.getKeyframes(); int keyframeCount = keyframes == null ? 0 : keyframes.size(); for (int i = 0; i < keyframeCount; i++) { Keyframe kf = keyframes.get(i); @@ -814,7 +815,7 @@ public class PropertyValuesHolder implements Cloneable { if (mSetter == null) { setupSetter(targetClass); } - ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + List<Keyframe> keyframes = mKeyframes.getKeyframes(); int keyframeCount = keyframes == null ? 0 : keyframes.size(); for (int i = 0; i < keyframeCount; i++) { Keyframe kf = keyframes.get(i); @@ -890,7 +891,7 @@ public class PropertyValuesHolder implements Cloneable { * @param target The object which holds the start values that should be set. */ void setupStartValue(Object target) { - ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + List<Keyframe> keyframes = mKeyframes.getKeyframes(); if (!keyframes.isEmpty()) { setupValue(target, keyframes.get(0)); } @@ -905,7 +906,7 @@ public class PropertyValuesHolder implements Cloneable { * @param target The object which holds the start values that should be set. */ void setupEndValue(Object target) { - ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + List<Keyframe> keyframes = mKeyframes.getKeyframes(); if (!keyframes.isEmpty()) { setupValue(target, keyframes.get(keyframes.size() - 1)); } diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java index 7256a06..d49e914 100644 --- a/core/java/android/animation/StateListAnimator.java +++ b/core/java/android/animation/StateListAnimator.java @@ -16,6 +16,7 @@ package android.animation; +import android.content.res.ConstantState; import android.util.StateSet; import android.view.View; @@ -44,25 +45,31 @@ import java.util.ArrayList; * @attr ref android.R.styleable#DrawableStates_state_pressed * @attr ref android.R.styleable#StateListAnimatorItem_animation */ -public class StateListAnimator { - - private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>(); +public class StateListAnimator implements Cloneable { + private ArrayList<Tuple> mTuples = new ArrayList<Tuple>(); private Tuple mLastMatch = null; - private Animator mRunningAnimator = null; - private WeakReference<View> mViewRef; + private StateListAnimatorConstantState mConstantState; + private AnimatorListenerAdapter mAnimatorListener; + private int mChangingConfigurations; - private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - animation.setTarget(null); - if (mRunningAnimator == animation) { - mRunningAnimator = null; + public StateListAnimator() { + initAnimatorListener(); + } + + private void initAnimatorListener() { + mAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animation.setTarget(null); + if (mRunningAnimator == animation) { + mRunningAnimator = null; + } } - } - }; + }; + } /** * Associates the given animator with the provided drawable state specs so that it will be run @@ -75,6 +82,7 @@ public class StateListAnimator { Tuple tuple = new Tuple(specs, animator); tuple.mAnimator.addListener(mAnimatorListener); mTuples.add(tuple); + mChangingConfigurations |= animator.getChangingConfigurations(); } /** @@ -118,12 +126,35 @@ public class StateListAnimator { for (int i = 0; i < size; i++) { mTuples.get(i).mAnimator.setTarget(null); } - mViewRef = null; mLastMatch = null; mRunningAnimator = null; } + @Override + public StateListAnimator clone() { + try { + StateListAnimator clone = (StateListAnimator) super.clone(); + clone.mTuples = new ArrayList<Tuple>(mTuples.size()); + clone.mLastMatch = null; + clone.mRunningAnimator = null; + clone.mViewRef = null; + clone.mAnimatorListener = null; + clone.initAnimatorListener(); + final int tupleSize = mTuples.size(); + for (int i = 0; i < tupleSize; i++) { + final Tuple tuple = mTuples.get(i); + final Animator animatorClone = tuple.mAnimator.clone(); + animatorClone.removeListener(mAnimatorListener); + clone.addState(tuple.mSpecs, animatorClone); + } + clone.setChangingConfigurations(getChangingConfigurations()); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError("cannot clone state list animator", e); + } + } + /** * Called by View * @hide @@ -182,6 +213,63 @@ public class StateListAnimator { } /** + * Return a mask of the configuration parameters for which this animator may change, requiring + * that it be re-created. The default implementation returns whatever was provided through + * {@link #setChangingConfigurations(int)} or 0 by default. + * + * @return Returns a mask of the changing configuration parameters, as defined by + * {@link android.content.pm.ActivityInfo}. + * + * @see android.content.pm.ActivityInfo + * @hide + */ + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + /** + * Set a mask of the configuration parameters for which this animator may change, requiring + * that it should be recreated from resources instead of being cloned. + * + * @param configs A mask of the changing configuration parameters, as + * defined by {@link android.content.pm.ActivityInfo}. + * + * @see android.content.pm.ActivityInfo + * @hide + */ + public void setChangingConfigurations(int configs) { + mChangingConfigurations = configs; + } + + /** + * Sets the changing configurations value to the union of the current changing configurations + * and the provided configs. + * This method is called while loading the animator. + * @hide + */ + public void appendChangingConfigurations(int configs) { + mChangingConfigurations |= configs; + } + + /** + * Return a {@link android.content.res.ConstantState} instance that holds the shared state of + * this Animator. + * <p> + * This constant state is used to create new instances of this animator when needed. Default + * implementation creates a new {@link StateListAnimatorConstantState}. You can override this + * method to provide your custom logic or return null if you don't want this animator to be + * cached. + * + * @return The {@link android.content.res.ConstantState} associated to this Animator. + * @see android.content.res.ConstantState + * @see #clone() + * @hide + */ + public ConstantState<StateListAnimator> createConstantState() { + return new StateListAnimatorConstantState(this); + } + + /** * @hide */ public static class Tuple { @@ -209,4 +297,36 @@ public class StateListAnimator { return mAnimator; } } + + /** + * Creates a constant state which holds changing configurations information associated with the + * given Animator. + * <p> + * When new instance is called, default implementation clones the Animator. + */ + private static class StateListAnimatorConstantState + extends ConstantState<StateListAnimator> { + + final StateListAnimator mAnimator; + + int mChangingConf; + + public StateListAnimatorConstantState(StateListAnimator animator) { + mAnimator = animator; + mAnimator.mConstantState = this; + mChangingConf = mAnimator.getChangingConfigurations(); + } + + @Override + public int getChangingConfigurations() { + return mChangingConf; + } + + @Override + public StateListAnimator newInstance() { + final StateListAnimator clone = mAnimator.clone(); + clone.mConstantState = this; + return clone; + } + } } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 0d17d67..07f79b8 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -16,6 +16,7 @@ package android.animation; +import android.content.res.ConfigurationBoundResourceCache; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; @@ -1289,12 +1290,7 @@ public class ValueAnimator extends Animator { public ValueAnimator clone() { final ValueAnimator anim = (ValueAnimator) super.clone(); if (mUpdateListeners != null) { - ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners; - anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mUpdateListeners.add(oldListeners.get(i)); - } + anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners); } anim.mSeekTime = -1; anim.mPlayingBackwards = false; diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java new file mode 100644 index 0000000..cde7e84 --- /dev/null +++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java @@ -0,0 +1,138 @@ +/* +* 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.res; + +import android.util.ArrayMap; +import android.util.LongSparseArray; +import java.lang.ref.WeakReference; + +/** + * A Cache class which can be used to cache resource objects that are easy to clone but more + * expensive to inflate. + * @hide + */ +public class ConfigurationBoundResourceCache<T> { + + private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache = + new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>(); + + final Resources mResources; + + /** + * Creates a Resource cache for the given Resources instance. + * + * @param resources The Resource which can be used when creating new instances. + */ + public ConfigurationBoundResourceCache(Resources resources) { + mResources = resources; + } + + /** + * Adds a new item to the cache. + * + * @param key A custom key that uniquely identifies the resource. + * @param theme The Theme instance where this resource was loaded. + * @param constantState The constant state that can create new instances of the resource. + * + */ + public void put(long key, Resources.Theme theme, ConstantState<T> constantState) { + if (constantState == null) { + return; + } + final String themeKey = theme == null ? "" : theme.getKey(); + LongSparseArray<WeakReference<ConstantState<T>>> themedCache; + synchronized (this) { + themedCache = mCache.get(themeKey); + if (themedCache == null) { + themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1); + mCache.put(themeKey, themedCache); + } + themedCache.put(key, new WeakReference<ConstantState<T>>(constantState)); + } + } + + /** + * If the resource is cached, creates a new instance of it and returns. + * + * @param key The long key which can be used to uniquely identify the resource. + * @param theme The The Theme instance where we want to load this resource. + * + * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns + * null. + */ + public T get(long key, Resources.Theme theme) { + final String themeKey = theme != null ? theme.getKey() : ""; + final LongSparseArray<WeakReference<ConstantState<T>>> themedCache; + final WeakReference<ConstantState<T>> wr; + synchronized (this) { + themedCache = mCache.get(themeKey); + if (themedCache == null) { + return null; + } + wr = themedCache.get(key); + } + if (wr == null) { + return null; + } + final ConstantState entry = wr.get(); + if (entry != null) { + return (T) entry.newInstance(mResources, theme); + } else { // our entry has been purged + synchronized (this) { + // there is a potential race condition here where this entry may be put in + // another thread. But we prefer it to minimize lock duration + themedCache.delete(key); + } + } + return null; + } + + /** + * Users of ConfigurationBoundResourceCache must call this method whenever a configuration + * change happens. On this callback, the cache invalidates all resources that are not valid + * anymore. + * + * @param configChanges The configuration changes + */ + public void onConfigurationChange(final int configChanges) { + synchronized (this) { + final int size = mCache.size(); + for (int i = size - 1; i >= 0; i--) { + final LongSparseArray<WeakReference<ConstantState<T>>> + themeCache = mCache.valueAt(i); + onConfigurationChangeInt(themeCache, configChanges); + if (themeCache.size() == 0) { + mCache.removeAt(i); + } + } + } + } + + private void onConfigurationChangeInt( + final LongSparseArray<WeakReference<ConstantState<T>>> themeCache, + final int configChanges) { + final int size = themeCache.size(); + for (int i = size - 1; i >= 0; i--) { + final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i); + final ConstantState<T> constantState = wr.get(); + if (constantState == null || Configuration.needNewResources( + configChanges, constantState.getChangingConfigurations())) { + themeCache.removeAt(i); + } + } + } + +} diff --git a/core/java/android/content/res/ConstantState.java b/core/java/android/content/res/ConstantState.java new file mode 100644 index 0000000..ee609df --- /dev/null +++ b/core/java/android/content/res/ConstantState.java @@ -0,0 +1,61 @@ +/* +* 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.res; + +/** + * A cache class that can provide new instances of a particular resource which may change + * depending on the current {@link Resources.Theme} or {@link Configuration}. + * <p> + * A constant state should be able to return a bitmask of changing configurations, which + * identifies the type of configuration changes that may invalidate this resource. These + * configuration changes can be obtained from {@link android.util.TypedValue}. Entities such as + * {@link android.animation.Animator} also provide a changing configuration method to include + * their dependencies (e.g. An AnimatorSet's changing configuration is the union of the + * changing configurations of each Animator in the set) + * @hide + */ +abstract public class ConstantState<T> { + + /** + * Return a bit mask of configuration changes that will impact + * this resource (and thus require completely reloading it). + */ + abstract public int getChangingConfigurations(); + + /** + * Create a new instance without supplying resources the caller + * is running in. + */ + public abstract T newInstance(); + + /** + * Create a new instance from its constant state. This + * must be implemented for resources that change based on the target + * density of their caller (that is depending on whether it is + * in compatibility mode). + */ + public T newInstance(Resources res) { + return newInstance(); + } + + /** + * Create a new instance from its constant state. This must be + * implemented for resources that can have a theme applied. + */ + public T newInstance(Resources res, Resources.Theme theme) { + return newInstance(res); + } +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e34ce3e..6e9efe1 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,6 +16,8 @@ package android.content.res; +import android.animation.Animator; +import android.animation.StateListAnimator; import android.util.Pools.SynchronizedPool; import android.view.ViewDebug; import com.android.internal.util.XmlUtils; @@ -115,6 +117,10 @@ public class Resources { new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = new LongSparseArray<WeakReference<ColorStateList>>(); + private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = + new ConfigurationBoundResourceCache<Animator>(this); + private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = + new ConfigurationBoundResourceCache<StateListAnimator>(this); private TypedValue mTmpValue = new TypedValue(); private boolean mPreloading; @@ -183,6 +189,24 @@ public class Resources { } /** + * Used by AnimatorInflater. + * + * @hide + */ + public ConfigurationBoundResourceCache<Animator> getAnimatorCache() { + return mAnimatorCache; + } + + /** + * Used by AnimatorInflater. + * + * @hide + */ + public ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() { + return mStateListAnimatorCache; + } + + /** * This exception is thrown by the resource APIs when a requested resource * can not be found. */ @@ -1761,23 +1785,7 @@ public class Resources { // the framework. mCompatibilityInfo.applyToDisplayMetrics(mMetrics); - int configChanges = 0xfffffff; - if (config != null) { - mTmpConfig.setTo(config); - int density = config.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = mMetrics.noncompatDensityDpi; - } - - mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); - - if (mTmpConfig.locale == null) { - mTmpConfig.locale = Locale.getDefault(); - mTmpConfig.setLayoutDirection(mTmpConfig.locale); - } - configChanges = mConfiguration.updateFrom(mTmpConfig); - configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); - } + int configChanges = calcConfigChanges(config); if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); mConfiguration.setLayoutDirection(mConfiguration.locale); @@ -1825,6 +1833,8 @@ public class Resources { clearDrawableCachesLocked(mDrawableCache, configChanges); clearDrawableCachesLocked(mColorDrawableCache, configChanges); + mAnimatorCache.onConfigurationChange(configChanges); + mStateListAnimatorCache.onConfigurationChange(configChanges); mColorStateListCache.clear(); @@ -1837,6 +1847,30 @@ public class Resources { } } + /** + * Called by ConfigurationBoundResourceCacheTest via reflection. + */ + private int calcConfigChanges(Configuration config) { + int configChanges = 0xfffffff; + if (config != null) { + mTmpConfig.setTo(config); + int density = config.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = mMetrics.noncompatDensityDpi; + } + + mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); + + if (mTmpConfig.locale == null) { + mTmpConfig.locale = Locale.getDefault(); + mTmpConfig.setLayoutDirection(mTmpConfig.locale); + } + configChanges = mConfiguration.updateFrom(mTmpConfig); + configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); + } + return configChanges; + } + private void clearDrawableCachesLocked( ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, int configChanges) { diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 58f0fc0..4fa0593 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -21,7 +21,9 @@ import android.os.Parcelable; import android.os.Parcel; import android.system.ErrnoException; +import java.io.FileDescriptor; import java.io.IOException; +import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MalformedURLException; @@ -264,18 +266,40 @@ public class Network implements Parcelable { } /** + * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the + * socket will be sent on this {@code Network}, irrespective of any process-wide network binding + * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be + * connected. + */ + public void bindSocket(DatagramSocket socket) throws IOException { + // Apparently, the kernel doesn't update a connected UDP socket's routing upon mark changes. + if (socket.isConnected()) { + throw new SocketException("Socket is connected"); + } + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. + socket.getReuseAddress(); + bindSocketFd(socket.getFileDescriptor$()); + } + + /** * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket * will be sent on this {@code Network}, irrespective of any process-wide network binding set by * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected. */ public void bindSocket(Socket socket) throws IOException { + // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes. if (socket.isConnected()) { throw new SocketException("Socket is connected"); } - // Query a property of the underlying socket to ensure the underlying - // socket exists so a file descriptor is available to bind to a network. + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. socket.getReuseAddress(); - int err = NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), netId); + bindSocketFd(socket.getFileDescriptor$()); + } + + private void bindSocketFd(FileDescriptor fd) throws IOException { + int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId); if (err != 0) { // bindSocketToNetwork returns negative errno. throw new ErrnoException("Binding socket to network " + netId, -err) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 75c435e..98a1f05 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5095,6 +5095,12 @@ public final class Settings { public static final String AIRPLANE_MODE_ON = "airplane_mode_on"; /** + * Whether Theater Mode is on. + * {@hide} + */ + public static final String THEATER_MODE_ON = "theater_mode_on"; + + /** * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio. */ public static final String RADIO_BLUETOOTH = "bluetooth"; @@ -6593,7 +6599,7 @@ public final class Settings { * Type: int (0 for false, 1 for true) * @hide */ - public static final String VOLTE_VT_ENABLED = "volte_vt_enabled"; + public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; /** * Settings to backup. This is here so that it's in the same place as the settings diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java index 0da5fb6..2c55141 100644 --- a/core/java/android/transition/ChangeBounds.java +++ b/core/java/android/transition/ChangeBounds.java @@ -16,6 +16,7 @@ package android.transition; +import android.animation.AnimatorSet; import android.content.Context; import android.graphics.PointF; @@ -31,7 +32,6 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.IntProperty; import android.util.Property; import android.view.View; import android.view.ViewGroup; @@ -77,6 +77,32 @@ public class ChangeBounds extends Transition { } }; + private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY = + new Property<ViewBounds, PointF>(PointF.class, "topLeft") { + @Override + public void set(ViewBounds viewBounds, PointF topLeft) { + viewBounds.setTopLeft(topLeft); + } + + @Override + public PointF get(ViewBounds viewBounds) { + return null; + } + }; + + private static final Property<ViewBounds, PointF> BOTTOM_RIGHT_PROPERTY = + new Property<ViewBounds, PointF>(PointF.class, "bottomRight") { + @Override + public void set(ViewBounds viewBounds, PointF bottomRight) { + viewBounds.setBottomRight(bottomRight); + } + + @Override + public PointF get(ViewBounds viewBounds) { + return null; + } + }; + int[] tempLocation = new int[2]; boolean mResizeClip = false; boolean mReparent = false; @@ -189,36 +215,20 @@ public class ChangeBounds extends Transition { } if (numChanges > 0) { if (!mResizeClip) { - Animator anim; - if (startWidth == endWidth && startHeight == endHeight) { - view.offsetLeftAndRight(startLeft - view.getLeft()); - view.offsetTopAndBottom(startTop - view.getTop()); - Path positionPath = getPathMotion().getPath(0, 0, endLeft - startLeft, - endTop - startTop); - anim = ObjectAnimator.ofInt(view, new HorizontalOffsetProperty(), - new VerticalOffsetProperty(), positionPath); - } else { - if (startLeft != endLeft) view.setLeft(startLeft); - if (startTop != endTop) view.setTop(startTop); - if (startRight != endRight) view.setRight(startRight); - if (startBottom != endBottom) view.setBottom(startBottom); - ObjectAnimator topLeftAnimator = null; - if (startLeft != endLeft || startTop != endTop) { - Path topLeftPath = getPathMotion().getPath(startLeft, startTop, - endLeft, endTop); - topLeftAnimator = ObjectAnimator - .ofInt(view, "left", "top", topLeftPath); - } - ObjectAnimator bottomRightAnimator = null; - if (startRight != endRight || startBottom != endBottom) { - Path bottomRightPath = getPathMotion().getPath(startRight, startBottom, - endRight, endBottom); - bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom", - bottomRightPath); - } - anim = TransitionUtils.mergeAnimators(topLeftAnimator, - bottomRightAnimator); - } + view.setLeftTopRightBottom(startLeft, startTop, startRight, startBottom); + ViewBounds viewBounds = new ViewBounds(view); + Path topLeftPath = getPathMotion().getPath(startLeft, startTop, + endLeft, endTop); + ObjectAnimator topLeftAnimator = ObjectAnimator + .ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath); + + Path bottomRightPath = getPathMotion().getPath(startRight, startBottom, + endRight, endBottom); + ObjectAnimator bottomRightAnimator = ObjectAnimator.ofObject(viewBounds, + BOTTOM_RIGHT_PROPERTY, null, bottomRightPath); + AnimatorSet anim = new AnimatorSet(); + anim.playTogether(topLeftAnimator, bottomRightAnimator); + if (view.getParent() instanceof ViewGroup) { final ViewGroup parent = (ViewGroup) view.getParent(); parent.suppressLayout(true); @@ -357,47 +367,41 @@ public class ChangeBounds extends Transition { return null; } - private abstract static class OffsetProperty extends IntProperty<View> { - int mPreviousValue; - - public OffsetProperty(String name) { - super(name); - } - - @Override - public void setValue(View view, int value) { - int offset = value - mPreviousValue; - offsetBy(view, offset); - mPreviousValue = value; - } - - @Override - public Integer get(View object) { - return null; - } - - protected abstract void offsetBy(View view, int by); - } - - private static class HorizontalOffsetProperty extends OffsetProperty { - public HorizontalOffsetProperty() { - super("offsetLeftAndRight"); + private static class ViewBounds { + private int mLeft; + private int mTop; + private int mRight; + private int mBottom; + private boolean mIsTopLeftSet; + private boolean mIsBottomRightSet; + private View mView; + + public ViewBounds(View view) { + mView = view; } - @Override - protected void offsetBy(View view, int by) { - view.offsetLeftAndRight(by); + public void setTopLeft(PointF topLeft) { + mLeft = Math.round(topLeft.x); + mTop = Math.round(topLeft.y); + mIsTopLeftSet = true; + if (mIsBottomRightSet) { + setLeftTopRightBottom(); + } } - } - private static class VerticalOffsetProperty extends OffsetProperty { - public VerticalOffsetProperty() { - super("offsetTopAndBottom"); + public void setBottomRight(PointF bottomRight) { + mRight = Math.round(bottomRight.x); + mBottom = Math.round(bottomRight.y); + mIsBottomRightSet = true; + if (mIsTopLeftSet) { + setLeftTopRightBottom(); + } } - @Override - protected void offsetBy(View view, int by) { - view.offsetTopAndBottom(by); + private void setLeftTopRightBottom() { + mView.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); + mIsTopLeftSet = false; + mIsBottomRightSet = false; } } } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 132e25c..562d138 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -311,7 +311,10 @@ public class Surface implements Parcelable { * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported"> * unsupported drawing operations</a> for a list of what is and isn't - * supported in a hardware-accelerated canvas. + * supported in a hardware-accelerated canvas. It is also required to + * fully cover the surface every time {@link #lockHardwareCanvas()} is + * called as the buffer is not preserved between frames. Partial updates + * are not supported. * * @return A canvas for drawing into the surface. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8e58cd6..8664a24 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -15699,6 +15699,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return changed; } + /** + * Same as setFrame, but public and hidden. For use in {@link android.transition.ChangeBounds}. + * @hide + */ + public void setLeftTopRightBottom(int left, int top, int right, int bottom) { + setFrame(left, top, right, bottom); + } + private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); if (mOverlay != null) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 63ab7d2..ef073b5 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,7 +24,6 @@ import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.media.session.MediaController; -import android.media.session.MediaSession; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -801,9 +800,6 @@ public abstract class Window { public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes(); attrs.flags = (attrs.flags&~mask) | (flags&mask); - if ((mask&WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0) { - attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; - } mForcedWindowFlags |= mask; dispatchWindowAttributesChanged(attrs); } @@ -817,6 +813,15 @@ public abstract class Window { /** * {@hide} */ + protected void setNeedsMenuKey(int value) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.needsMenuKey = value; + dispatchWindowAttributesChanged(attrs); + } + + /** + * {@hide} + */ protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) { if (mCallback != null) { mCallback.onWindowAttributesChanged(attrs); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 75c9ebd..3f84c9b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -878,9 +878,6 @@ public interface WindowManager extends ViewManager { */ public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000; - // ----- HIDDEN FLAGS. - // These start at the high bit and go down. - /** * Flag for a window in local focus mode. * Window in local focus mode can control focus independent of window manager using @@ -903,17 +900,12 @@ public interface WindowManager extends ViewManager { public static final int FLAG_SLIPPERY = 0x20000000; /** - * Flag for a window belonging to an activity that responds to {@link KeyEvent#KEYCODE_MENU} - * and therefore needs a Menu key. For devices where Menu is a physical button this flag is - * ignored, but on devices where the Menu key is drawn in software it may be hidden unless - * this flag is set. - * - * (Note that Action Bars, when available, are the preferred way to offer additional - * functions otherwise accessed via an options menu.) - * - * {@hide} + * Window flag: When requesting layout with an attached window, the attached window may + * overlap with the screen decorations of the parent window such as the navigation bar. By + * including this flag, the window manager will layout the attached window within the decor + * frame of the parent window such that it doesn't overlap with screen decorations. */ - public static final int FLAG_NEEDS_MENU_KEY = 0x40000000; + public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000; /** * Flag indicating that this Window is responsible for drawing the background for the @@ -1056,16 +1048,6 @@ public interface WindowManager extends ViewManager { */ public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004; - /** - * This is set for a window that has explicitly specified its - * FLAG_NEEDS_MENU_KEY, so we know the value on this window is the - * appropriate one to use. If this is not set, we should look at - * windows behind it to determine the appropriate value. - * - * @hide - */ - public static final int PRIVATE_FLAG_SET_NEEDS_MENU_KEY = 0x00000008; - /** In a multiuser system if this flag is set and the owner is a system process then this * window will appear on all user screens. This overrides the default behavior of window * types that normally only appear on the owning user's screen. Refer to each window type @@ -1113,6 +1095,45 @@ public interface WindowManager extends ViewManager { public int privateFlags; /** + * Value for {@link #needsMenuKey} for a window that has not explicitly specified if it + * needs {@link #NEEDS_MENU_SET_TRUE} or doesn't need {@link #NEEDS_MENU_SET_FALSE} a menu + * key. For this case, we should look at windows behind it to determine the appropriate + * value. + * + * @hide + */ + public static final int NEEDS_MENU_UNSET = 0; + + /** + * Value for {@link #needsMenuKey} for a window that has explicitly specified it needs a + * menu key. + * + * @hide + */ + public static final int NEEDS_MENU_SET_TRUE = 1; + + /** + * Value for {@link #needsMenuKey} for a window that has explicitly specified it doesn't + * needs a menu key. + * + * @hide + */ + public static final int NEEDS_MENU_SET_FALSE = 2; + + /** + * State variable for a window belonging to an activity that responds to + * {@link KeyEvent#KEYCODE_MENU} and therefore needs a Menu key. For devices where Menu is a + * physical button this variable is ignored, but on devices where the Menu key is drawn in + * software it may be hidden unless this variable is set to {@link #NEEDS_MENU_SET_TRUE}. + * + * (Note that Action Bars, when available, are the preferred way to offer additional + * functions otherwise accessed via an options menu.) + * + * {@hide} + */ + public int needsMenuKey = NEEDS_MENU_UNSET; + + /** * Given a particular set of window manager flags, determine whether * such a window may be a target for an input method when it has * focus. In particular, this checks the @@ -1120,9 +1141,9 @@ public interface WindowManager extends ViewManager { * flags and returns true if the combination of the two corresponds * to a window that needs to be behind the input method so that the * user can type into it. - * + * * @param flags The current window manager flags. - * + * * @return Returns true if such a window should be behind/interact * with an input method, false if not. */ @@ -1587,14 +1608,15 @@ public interface WindowManager extends ViewManager { out.writeInt(surfaceInsets.top); out.writeInt(surfaceInsets.right); out.writeInt(surfaceInsets.bottom); + out.writeInt(needsMenuKey); } - + public static final Parcelable.Creator<LayoutParams> CREATOR = new Parcelable.Creator<LayoutParams>() { public LayoutParams createFromParcel(Parcel in) { return new LayoutParams(in); } - + public LayoutParams[] newArray(int size) { return new LayoutParams[size]; } @@ -1634,8 +1656,9 @@ public interface WindowManager extends ViewManager { surfaceInsets.top = in.readInt(); surfaceInsets.right = in.readInt(); surfaceInsets.bottom = in.readInt(); + needsMenuKey = in.readInt(); } - + @SuppressWarnings({"PointlessBitwiseExpression"}) public static final int LAYOUT_CHANGED = 1<<0; public static final int TYPE_CHANGED = 1<<1; @@ -1669,14 +1692,16 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21; /** {@hide} */ + public static final int NEEDS_MENU_KEY_CHANGED = 1 << 22; + /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. private int[] mCompatibilityParamsBackup = null; - + public final int copyFrom(LayoutParams o) { int changes = 0; - + if (width != o.width) { width = o.width; changes |= LAYOUT_CHANGED; @@ -1813,9 +1838,14 @@ public interface WindowManager extends ViewManager { changes |= SURFACE_INSETS_CHANGED; } + if (needsMenuKey != o.needsMenuKey) { + needsMenuKey = o.needsMenuKey; + changes |= NEEDS_MENU_KEY_CHANGED; + } + return changes; } - + @Override public String debug(String output) { output += "Contents of " + this + ":"; @@ -1919,6 +1949,10 @@ public interface WindowManager extends ViewManager { if (!surfaceInsets.equals(Insets.NONE)) { sb.append(" surfaceInsets=").append(surfaceInsets); } + if (needsMenuKey != NEEDS_MENU_UNSET) { + sb.append(" needsMenuKey="); + sb.append(needsMenuKey); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java index ed6949a..21d5a5b 100644 --- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java +++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java @@ -26,17 +26,17 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; /** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. - * */ @HasNativeInterpolator -public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory { +public class AccelerateDecelerateInterpolator extends BaseInterpolator + implements NativeInterpolatorFactory { public AccelerateDecelerateInterpolator() { } - + @SuppressWarnings({"UnusedDeclaration"}) public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { } - + public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; } diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java index 1c75f16..6c8d7b1 100644 --- a/core/java/android/view/animation/AccelerateInterpolator.java +++ b/core/java/android/view/animation/AccelerateInterpolator.java @@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * */ @HasNativeInterpolator -public class AccelerateInterpolator implements Interpolator, NativeInterpolatorFactory { +public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { private final float mFactor; private final double mDoubleFactor; @@ -70,7 +70,7 @@ public class AccelerateInterpolator implements Interpolator, NativeInterpolatorF mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f); mDoubleFactor = 2 * mFactor; - + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index af4e04f..606c83e 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -321,7 +321,7 @@ public class AnimationUtils { private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) throws XmlPullParserException, IOException { - Interpolator interpolator = null; + BaseInterpolator interpolator = null; // Make sure we are on a start tag. int type; @@ -361,10 +361,7 @@ public class AnimationUtils { } 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 fe756bd..fb66c31 100644 --- a/core/java/android/view/animation/AnticipateInterpolator.java +++ b/core/java/android/view/animation/AnticipateInterpolator.java @@ -31,7 +31,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * An interpolator where the change starts backward then flings forward. */ @HasNativeInterpolator -public class AnticipateInterpolator implements Interpolator, NativeInterpolatorFactory { +public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { private final float mTension; public AnticipateInterpolator() { @@ -60,9 +60,8 @@ public class AnticipateInterpolator implements Interpolator, NativeInterpolatorF a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator); } - mTension = - a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f); - + mTension = a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f); + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java index 78e5acf..1af72da 100644 --- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java +++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java @@ -35,7 +35,8 @@ import static com.android.internal.R.styleable.AnticipateOvershootInterpolator; * the target value and finally goes back to the final value. */ @HasNativeInterpolator -public class AnticipateOvershootInterpolator implements Interpolator, NativeInterpolatorFactory { +public class AnticipateOvershootInterpolator extends BaseInterpolator + implements NativeInterpolatorFactory { private final float mTension; public AnticipateOvershootInterpolator() { @@ -78,7 +79,7 @@ public class AnticipateOvershootInterpolator implements Interpolator, NativeInte mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) * a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f); - + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/BaseInterpolator.java b/core/java/android/view/animation/BaseInterpolator.java new file mode 100644 index 0000000..9c0014c --- /dev/null +++ b/core/java/android/view/animation/BaseInterpolator.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.animation; + +/** + * An abstract class which is extended by default interpolators. + */ +abstract public class BaseInterpolator implements Interpolator { + private int mChangingConfiguration; + /** + * @hide + */ + public int getChangingConfiguration() { + return mChangingConfiguration; + } + + /** + * @hide + */ + void setChangingConfiguration(int changingConfiguration) { + mChangingConfiguration = changingConfiguration; + } +} diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java index 9d8ca90..909eaa4 100644 --- a/core/java/android/view/animation/BounceInterpolator.java +++ b/core/java/android/view/animation/BounceInterpolator.java @@ -27,7 +27,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * An interpolator where the change bounces at the end. */ @HasNativeInterpolator -public class BounceInterpolator implements Interpolator, NativeInterpolatorFactory { +public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public BounceInterpolator() { } diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java index 3114aa3..663c109 100644 --- a/core/java/android/view/animation/CycleInterpolator.java +++ b/core/java/android/view/animation/CycleInterpolator.java @@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * */ @HasNativeInterpolator -public class CycleInterpolator implements Interpolator, NativeInterpolatorFactory { +public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public CycleInterpolator(float cycles) { mCycles = cycles; } @@ -52,7 +52,7 @@ public class CycleInterpolator implements Interpolator, NativeInterpolatorFactor } mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f); - + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java index 674207c..f426f60 100644 --- a/core/java/android/view/animation/DecelerateInterpolator.java +++ b/core/java/android/view/animation/DecelerateInterpolator.java @@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * */ @HasNativeInterpolator -public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory { +public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public DecelerateInterpolator() { } @@ -62,7 +62,7 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF } mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f); - + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java index 552c611..2a047b4 100644 --- a/core/java/android/view/animation/LinearInterpolator.java +++ b/core/java/android/view/animation/LinearInterpolator.java @@ -25,17 +25,16 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; /** * An interpolator where the rate of change is constant - * */ @HasNativeInterpolator -public class LinearInterpolator implements Interpolator, NativeInterpolatorFactory { +public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public LinearInterpolator() { } - + public LinearInterpolator(Context context, AttributeSet attrs) { } - + public float getInterpolation(float input) { return input; } diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java index d6c2808..306688a 100644 --- a/core/java/android/view/animation/OvershootInterpolator.java +++ b/core/java/android/view/animation/OvershootInterpolator.java @@ -32,7 +32,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; * then comes back. */ @HasNativeInterpolator -public class OvershootInterpolator implements Interpolator, NativeInterpolatorFactory { +public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { private final float mTension; public OvershootInterpolator() { @@ -61,9 +61,8 @@ public class OvershootInterpolator implements Interpolator, NativeInterpolatorFa a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator); } - mTension = - a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f); - + mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f); + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java index 945ecf0..eec5555 100644 --- a/core/java/android/view/animation/PathInterpolator.java +++ b/core/java/android/view/animation/PathInterpolator.java @@ -42,7 +42,7 @@ import com.android.internal.R; * path.lineTo(1f, 1f); * </pre></blockquote></p> */ -public class PathInterpolator implements Interpolator { +public class PathInterpolator extends BaseInterpolator { // This governs how accurate the approximation of the Path is. private static final float PRECISION = 0.002f; @@ -98,7 +98,7 @@ public class PathInterpolator implements Interpolator { a = res.obtainAttributes(attrs, R.styleable.PathInterpolator); } parseInterpolatorFromTypeArray(a); - + setChangingConfiguration(a.getChangingConfigurations()); a.recycle(); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 0439168..4aebaae 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -4653,7 +4653,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mPositionScroller == null) { mPositionScroller = createPositionScroller(); } - mPositionScroller.startWithOffset(position, offset, offset); + mPositionScroller.startWithOffset(position, offset); } /** diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index b9f891c..5fa6e60 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -215,7 +215,12 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { private boolean mDesiredFocusableState; private boolean mDesiredFocusableInTouchModeState; + /** Lazily-constructed runnable for dispatching selection events. */ private SelectionNotifier mSelectionNotifier; + + /** Selection notifier that's waiting for the next layout pass. */ + private SelectionNotifier mPendingSelectionNotifier; + /** * When set to true, calls to requestLayout() will not propagate up the parent hierarchy. * This is used to layout the children during a layout pass. @@ -854,39 +859,50 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { private class SelectionNotifier implements Runnable { public void run() { + mPendingSelectionNotifier = null; + if (mDataChanged) { - // Data has changed between when this SelectionNotifier - // was posted and now. We need to wait until the AdapterView - // has been synched to the new data. + // Data has changed between when this SelectionNotifier was + // posted and now. Postpone the notification until the next + // layout is complete and we run checkSelectionChanged(). if (getAdapter() != null) { - post(this); + mPendingSelectionNotifier = this; } } else { - fireOnSelected(); - performAccessibilityActionsOnSelected(); + dispatchOnItemSelected(); } } } void selectionChanged() { + // We're about to post or run the selection notifier, so we don't need + // a pending notifier. + mPendingSelectionNotifier = null; + if (mOnItemSelectedListener != null || AccessibilityManager.getInstance(mContext).isEnabled()) { if (mInLayout || mBlockLayoutRequests) { // If we are in a layout traversal, defer notification // by posting. This ensures that the view tree is - // in a consistent state and is able to accomodate + // in a consistent state and is able to accommodate // new layout or invalidate requests. if (mSelectionNotifier == null) { mSelectionNotifier = new SelectionNotifier(); + } else { + removeCallbacks(mSelectionNotifier); } post(mSelectionNotifier); } else { - fireOnSelected(); - performAccessibilityActionsOnSelected(); + dispatchOnItemSelected(); } } } + private void dispatchOnItemSelected() { + fireOnSelected(); + performAccessibilityActionsOnSelected(); + } + private void fireOnSelected() { if (mOnItemSelectedListener == null) { return; @@ -1042,12 +1058,22 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { notifySubtreeAccessibilityStateChangedIfNeeded(); } + /** + * Called after layout to determine whether the selection position needs to + * be updated. Also used to fire any pending selection events. + */ void checkSelectionChanged() { if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { selectionChanged(); mOldSelectedPosition = mSelectedPosition; mOldSelectedRowId = mSelectedRowId; } + + // If we have a pending selection notification -- and we won't if we + // just fired one in selectionChanged() -- run it now. + if (mPendingSelectionNotifier != null) { + mPendingSelectionNotifier.run(); + } } /** diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index f90a9fe..75dfcca 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -1120,6 +1120,9 @@ public class ImageView extends View { /** @hide */ public void animateTransform(Matrix matrix) { + if (mDrawable == null) { + return; + } if (matrix == null) { mDrawable.setBounds(0, 0, getWidth(), getHeight()); } else { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 41d3e320..54a7940 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -97,9 +97,11 @@ public class PopupWindow { private boolean mAllowScrollingAnchorParent = true; private boolean mLayoutInsetDecor = false; private boolean mNotTouchModal; + private boolean mAttachedInDecor = true; + private boolean mAttachedInDecorSet = false; private OnTouchListener mTouchInterceptor; - + private int mWidthMode; private int mWidth; private int mLastWidth; @@ -316,6 +318,7 @@ public class PopupWindow { mContext = contentView.getContext(); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } + setContentView(contentView); setWidth(width); setHeight(height); @@ -373,16 +376,16 @@ public class PopupWindow { public int getAnimationStyle() { return mAnimationStyle; } - + /** - * Set the flag on popup to ignore cheek press eventt; by default this flag + * Set the flag on popup to ignore cheek press event; by default this flag * is set to false * which means the pop wont ignore cheek press dispatch events. - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> - * + * * @see #update() */ public void setIgnoreCheekPress() { @@ -443,6 +446,19 @@ public class PopupWindow { if (mWindowManager == null && mContentView != null) { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } + + // Setting the default for attachedInDecor based on SDK version here + // instead of in the constructor since we might not have the context + // object in the constructor. We only want to set default here if the + // app hasn't already set the attachedInDecor. + if (mContext != null && !mAttachedInDecorSet) { + // Attach popup window in decor frame of parent window by default for + // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current + // behavior of not attaching to decor frame for older SDKs. + setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.LOLLIPOP_MR1); + } + } /** @@ -452,7 +468,7 @@ public class PopupWindow { public void setTouchInterceptor(OnTouchListener l) { mTouchInterceptor = l; } - + /** * <p>Indicate whether the popup window can grab the focus.</p> * @@ -702,6 +718,36 @@ public class PopupWindow { } /** + * <p>Indicates whether the popup window will be attached in the decor frame of its parent + * window. + * + * @return true if the window will be attached to the decor frame of its parent window. + * + * @see #setAttachedInDecor(boolean) + * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR + */ + public boolean isAttachedInDecor() { + return mAttachedInDecor; + } + + /** + * <p>This will attach the popup window to the decor frame of the parent window to avoid + * overlaping with screen decorations like the navigation bar. Overrides the default behavior of + * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}. + * + * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or + * greater and cleared on lesser SDK versions. + * + * @param enabled true if the popup should be attached to the decor frame of its parent window. + * + * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR + */ + public void setAttachedInDecor(boolean enabled) { + mAttachedInDecor = enabled; + mAttachedInDecorSet = true; + } + + /** * Allows the popup window to force the flag * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. * This will cause the popup to inset its content to account for system windows overlaying @@ -1140,9 +1186,12 @@ public class PopupWindow { if (mNotTouchModal) { curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; } + if (mAttachedInDecor) { + curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; + } return curFlags; } - + private int computeAnimationResource() { if (mAnimationStyle == -1) { if (mIsDropdown) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 5cdee53..0917b32 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8518,6 +8518,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } return false; case AccessibilityNodeInfo.ACTION_SET_SELECTION: { if (isFocused() && canSelectText()) { + ensureIterableTextForAccessibilitySelectable(); CharSequence text = getIterableTextForAccessibility(); if (text == null) { return false; @@ -8543,6 +8544,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } return false; + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { + ensureIterableTextForAccessibilitySelectable(); + return super.performAccessibilityAction(action, arguments); + } default: { return super.performAccessibilityAction(action, arguments); } @@ -9032,10 +9038,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public CharSequence getIterableTextForAccessibility() { + return mText; + } + + private void ensureIterableTextForAccessibilitySelectable() { if (!(mText instanceof Spannable)) { setText(mText, BufferType.SPANNABLE); } - return mText; } /** diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 85cf67b..26e02f8 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -86,12 +86,12 @@ public class TimePicker extends FrameLayout { switch (mode) { case MODE_CLOCK: - mDelegate = new TimePickerSpinnerDelegate( + mDelegate = new TimePickerClockDelegate( this, context, attrs, defStyleAttr, defStyleRes); break; case MODE_SPINNER: default: - mDelegate = new TimePickerClockDelegate( + mDelegate = new TimePickerSpinnerDelegate( this, context, attrs, defStyleAttr, defStyleRes); break; } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 6dfea92..eca3048 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -17,365 +17,376 @@ package android.widget; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.HapticFeedbackConstants; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; + import com.android.internal.R; -import java.text.DateFormatSymbols; +import java.util.ArrayList; import java.util.Calendar; import java.util.Locale; -import libcore.icu.LocaleData; - -import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; -import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; - /** - * A delegate implementing the basic spinner-based TimePicker. + * A delegate implementing the radial clock-based TimePicker. */ -class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { +class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements + RadialTimePickerView.OnValueSelectedListener { + + private static final String TAG = "TimePickerClockDelegate"; + + // Index used by RadialPickerLayout + private static final int HOUR_INDEX = 0; + private static final int MINUTE_INDEX = 1; + + // NOT a real index for the purpose of what's showing. + private static final int AMPM_INDEX = 2; + + // Also NOT a real index, just used for keyboard mode. + private static final int ENABLE_PICKER_INDEX = 3; + + static final int AM = 0; + static final int PM = 1; + private static final boolean DEFAULT_ENABLED_STATE = true; + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + private static final int HOURS_IN_HALF_DAY = 12; - // state - private boolean mIs24HourView; - private boolean mIsAm; + private final View mHeaderView; + private final TextView mHourView; + private final TextView mMinuteView; + private final View mAmPmLayout; + private final CheckedTextView mAmLabel; + private final CheckedTextView mPmLabel; + private final RadialTimePickerView mRadialTimePickerView; + private final TextView mSeparatorView; - // ui components - private final NumberPicker mHourSpinner; - private final NumberPicker mMinuteSpinner; - private final NumberPicker mAmPmSpinner; - private final EditText mHourSpinnerInput; - private final EditText mMinuteSpinnerInput; - private final EditText mAmPmSpinnerInput; - private final TextView mDivider; + private final String mAmText; + private final String mPmText; - // Note that the legacy implementation of the TimePicker is - // using a button for toggling between AM/PM while the new - // version uses a NumberPicker spinner. Therefore the code - // accommodates these two cases to be backwards compatible. - private final Button mAmPmButton; + private final float mDisabledAlpha; - private final String[] mAmPmStrings; + private boolean mAllowAutoAdvance; + private int mInitialHourOfDay; + private int mInitialMinute; + private boolean mIs24HourView; + + // For hardware IME input. + private char mPlaceholderText; + private String mDoublePlaceholderText; + private String mDeletedKeyFormat; + private boolean mInKbMode; + private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>(); + private Node mLegalTimesTree; + private int mAmKeyCode; + private int mPmKeyCode; + + // Accessibility strings. + private String mHourPickerDescription; + private String mSelectHours; + private String mMinutePickerDescription; + private String mSelectMinutes; + + // Most recent time announcement values for accessibility. + private CharSequence mLastAnnouncedText; + private boolean mLastAnnouncedIsHour; - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; private Calendar mTempCalendar; - private boolean mHourWithTwoDigit; - private char mHourFormat; public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(delegator, context); // process style attributes - final TypedArray a = mContext.obtainStyledAttributes( - attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes); - final int layoutResourceId = a.getResourceId( - R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy); - a.recycle(); + final TypedArray a = mContext.obtainStyledAttributes(attrs, + R.styleable.TimePicker, defStyleAttr, defStyleRes); + final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + final Resources res = mContext.getResources(); + + mHourPickerDescription = res.getString(R.string.hour_picker_description); + mSelectHours = res.getString(R.string.select_hours); + mMinutePickerDescription = res.getString(R.string.minute_picker_description); + mSelectMinutes = res.getString(R.string.select_minutes); + + String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context); + mAmText = amPmStrings[0]; + mPmText = amPmStrings[1]; + + final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout, + R.layout.time_picker_holo); + final View mainView = inflater.inflate(layoutResourceId, delegator); + + mHeaderView = mainView.findViewById(R.id.time_header); + mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground)); + + // Set up hour/minute labels. + mHourView = (TextView) mHeaderView.findViewById(R.id.hours); + mHourView.setOnClickListener(mClickListener); + mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator); + mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes); + mMinuteView.setOnClickListener(mClickListener); + + final int headerTimeTextAppearance = a.getResourceId( + R.styleable.TimePicker_headerTimeTextAppearance, 0); + if (headerTimeTextAppearance != 0) { + mHourView.setTextAppearance(context, headerTimeTextAppearance); + mSeparatorView.setTextAppearance(context, headerTimeTextAppearance); + mMinuteView.setTextAppearance(context, headerTimeTextAppearance); + } - final LayoutInflater inflater = LayoutInflater.from(mContext); - inflater.inflate(layoutResourceId, mDelegator, true); - - // hour - mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour); - mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { - updateInputState(); - if (!is24HourView()) { - if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || - (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - } - onTimeChanged(); - } - }); - mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); - mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - - // divider (only for the new widget style) - mDivider = (TextView) mDelegator.findViewById(R.id.divider); - if (mDivider != null) { - setDividerText(); - } - - // minute - mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute); - mMinuteSpinner.setMinValue(0); - mMinuteSpinner.setMaxValue(59); - mMinuteSpinner.setOnLongPressUpdateInterval(100); - mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); - mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { - updateInputState(); - int minValue = mMinuteSpinner.getMinValue(); - int maxValue = mMinuteSpinner.getMaxValue(); - if (oldVal == maxValue && newVal == minValue) { - int newHour = mHourSpinner.getValue() + 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - mHourSpinner.setValue(newHour); - } else if (oldVal == minValue && newVal == maxValue) { - int newHour = mHourSpinner.getValue() - 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - mHourSpinner.setValue(newHour); - } - onTimeChanged(); - } - }); - mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - - // Get the localized am/pm strings and use them in the spinner. - mAmPmStrings = getAmPmStrings(context); - - // am/pm - final View amPmView = mDelegator.findViewById(R.id.amPm); - if (amPmView instanceof Button) { - mAmPmSpinner = null; - mAmPmSpinnerInput = null; - mAmPmButton = (Button) amPmView; - mAmPmButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View button) { - button.requestFocus(); - mIsAm = !mIsAm; - updateAmPmControl(); - onTimeChanged(); - } - }); - } else { - mAmPmButton = null; - mAmPmSpinner = (NumberPicker) amPmView; - mAmPmSpinner.setMinValue(0); - mAmPmSpinner.setMaxValue(1); - mAmPmSpinner.setDisplayedValues(mAmPmStrings); - mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - updateInputState(); - picker.requestFocus(); - mIsAm = !mIsAm; - updateAmPmControl(); - onTimeChanged(); - } - }); - mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); - mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); - } - - if (isAmPmAtStart()) { - // Move the am/pm view to the beginning - ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout); - amPmParent.removeView(amPmView); - amPmParent.addView(amPmView, 0); - // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme - // for example and not for Holo Theme) - ViewGroup.MarginLayoutParams lp = - (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); - final int startMargin = lp.getMarginStart(); - final int endMargin = lp.getMarginEnd(); - if (startMargin != endMargin) { - lp.setMarginStart(endMargin); - lp.setMarginEnd(startMargin); - } + // TODO: This can be removed once we support themed color state lists. + final int headerSelectedTextColor = a.getColor( + R.styleable.TimePicker_headerSelectedTextColor, + res.getColor(R.color.timepicker_default_selector_color_material)); + mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(), + R.attr.state_selected, headerSelectedTextColor)); + mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(), + R.attr.state_selected, headerSelectedTextColor)); + + // Set up AM/PM labels. + mAmPmLayout = mHeaderView.findViewById(R.id.ampm_layout); + mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label); + mAmLabel.setText(amPmStrings[0]); + mAmLabel.setOnClickListener(mClickListener); + mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label); + mPmLabel.setText(amPmStrings[1]); + mPmLabel.setOnClickListener(mClickListener); + + final int headerAmPmTextAppearance = a.getResourceId( + R.styleable.TimePicker_headerAmPmTextAppearance, 0); + if (headerAmPmTextAppearance != 0) { + mAmLabel.setTextAppearance(context, headerAmPmTextAppearance); + mPmLabel.setTextAppearance(context, headerAmPmTextAppearance); } - getHourFormatData(); + a.recycle(); + + // Pull disabled alpha from theme. + final TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); + mDisabledAlpha = outValue.getFloat(); - // update controls to initial state - updateHourControl(); - updateMinuteControl(); - updateAmPmControl(); + mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById( + R.id.radial_picker); - // set to current time - setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); - setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); + setupListeners(); - if (!isEnabled()) { - setEnabled(false); - } + mAllowAutoAdvance = true; - // set the content descriptions - setContentDescriptions(); + // Set up for keyboard mode. + mDoublePlaceholderText = res.getString(R.string.time_placeholder); + mDeletedKeyFormat = res.getString(R.string.deleted_key); + mPlaceholderText = mDoublePlaceholderText.charAt(0); + mAmKeyCode = mPmKeyCode = -1; + generateLegalTimesTree(); - // If not explicitly specified this view is important for accessibility. - if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } + // Initialize with current time + final Calendar calendar = Calendar.getInstance(mCurrentLocale); + final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); + final int currentMinute = calendar.get(Calendar.MINUTE); + initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX); } - private void getHourFormatData() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - (mIs24HourView) ? "Hm" : "hm"); - final int lengthPattern = bestDateTimePattern.length(); - mHourWithTwoDigit = false; - char hourFormat = '\0'; - // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save - // the hour format that we found. - for (int i = 0; i < lengthPattern; i++) { - final char c = bestDateTimePattern.charAt(i); - if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { - mHourFormat = c; - if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { - mHourWithTwoDigit = true; - } - break; - } - } + private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) { + mInitialHourOfDay = hourOfDay; + mInitialMinute = minute; + mIs24HourView = is24HourView; + mInKbMode = false; + updateUI(index); } - private boolean isAmPmAtStart() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - "hm" /* skeleton */); + private void setupListeners() { + mHeaderView.setOnKeyListener(mKeyListener); + mHeaderView.setOnFocusChangeListener(mFocusListener); + mHeaderView.setFocusable(true); - return bestDateTimePattern.startsWith("a"); + mRadialTimePickerView.setOnValueSelectedListener(this); } - /** - * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". - * - * See http://unicode.org/cldr/trac/browser/trunk/common/main - * - * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the - * separator as the character which is just after the hour marker in the returned pattern. - */ - private void setDividerText() { - final String skeleton = (mIs24HourView) ? "Hm" : "hm"; - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - skeleton); - final String separatorText; - int hourIndex = bestDateTimePattern.lastIndexOf('H'); - if (hourIndex == -1) { - hourIndex = bestDateTimePattern.lastIndexOf('h'); + private void updateUI(int index) { + // Update RadialPicker values + updateRadialPicker(index); + // Enable or disable the AM/PM view. + updateHeaderAmPm(); + // Update Hour and Minutes + updateHeaderHour(mInitialHourOfDay, false); + // Update time separator + updateHeaderSeparator(); + // Update Minutes + updateHeaderMinute(mInitialMinute, false); + // Invalidate everything + mDelegator.invalidate(); + } + + private void updateRadialPicker(int index) { + mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView); + setCurrentItemShowing(index, false, true); + } + + private int computeMaxWidthOfNumbers(int max) { + TextView tempView = new TextView(mContext); + tempView.setTextAppearance(mContext, R.style.TextAppearance_Material_TimePicker_TimeLabel); + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + tempView.setLayoutParams(lp); + int maxWidth = 0; + for (int minutes = 0; minutes < max; minutes++) { + final String text = String.format("%02d", minutes); + tempView.setText(text); + tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth()); } - if (hourIndex == -1) { - // Default case - separatorText = ":"; + return maxWidth; + } + + private void updateHeaderAmPm() { + if (mIs24HourView) { + mAmPmLayout.setVisibility(View.GONE); } else { - int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); - if (minuteIndex == -1) { - separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); + final String bestDateTimePattern = DateFormat.getBestDateTimePattern( + mCurrentLocale, "hm"); + boolean amPmOnLeft = bestDateTimePattern.startsWith("a"); + if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) == + View.LAYOUT_DIRECTION_RTL) { + amPmOnLeft = !amPmOnLeft; + } + + final ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) mAmPmLayout.getLayoutParams(); + + if (amPmOnLeft) { + params.leftMargin = 0; + params.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */); } else { - separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); + params.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */); + params.rightMargin = 0; } + + mAmPmLayout.setLayoutParams(params); + mAmPmLayout.setVisibility(View.VISIBLE); + + updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM); } - mDivider.setText(separatorText); } + /** + * Set the current hour. + */ @Override public void setCurrentHour(Integer currentHour) { - setCurrentHour(currentHour, true); - } - - private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { - // why was Integer used in the first place? - if (currentHour == null || currentHour == getCurrentHour()) { + if (mInitialHourOfDay == currentHour) { return; } - if (!is24HourView()) { - // convert [0,23] ordinal to wall clock display - if (currentHour >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (currentHour > HOURS_IN_HALF_DAY) { - currentHour = currentHour - HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (currentHour == 0) { - currentHour = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - mHourSpinner.setValue(currentHour); - if (notifyTimeChanged) { - onTimeChanged(); - } + mInitialHourOfDay = currentHour; + updateHeaderHour(currentHour, true); + updateHeaderAmPm(); + mRadialTimePickerView.setCurrentHour(currentHour); + mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM); + mDelegator.invalidate(); + onTimeChanged(); } + /** + * @return The current hour in the range (0-23). + */ @Override public Integer getCurrentHour() { - int currentHour = mHourSpinner.getValue(); - if (is24HourView()) { + int currentHour = mRadialTimePickerView.getCurrentHour(); + if (mIs24HourView) { return currentHour; - } else if (mIsAm) { - return currentHour % HOURS_IN_HALF_DAY; } else { - return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; + switch(mRadialTimePickerView.getAmOrPm()) { + case PM: + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; + case AM: + default: + return currentHour % HOURS_IN_HALF_DAY; + } } } + /** + * Set the current minute (0-59). + */ @Override public void setCurrentMinute(Integer currentMinute) { - if (currentMinute == getCurrentMinute()) { + if (mInitialMinute == currentMinute) { return; } - mMinuteSpinner.setValue(currentMinute); + mInitialMinute = currentMinute; + updateHeaderMinute(currentMinute, true); + mRadialTimePickerView.setCurrentMinute(currentMinute); + mDelegator.invalidate(); onTimeChanged(); } + /** + * @return The current minute. + */ @Override public Integer getCurrentMinute() { - return mMinuteSpinner.getValue(); + return mRadialTimePickerView.getCurrentMinute(); } + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True = 24 hour mode. False = AM/PM. + */ @Override public void setIs24HourView(Boolean is24HourView) { - if (mIs24HourView == is24HourView) { + if (is24HourView == mIs24HourView) { return; } - // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! - int currentHour = getCurrentHour(); - // Order is important here. mIs24HourView = is24HourView; - getHourFormatData(); - updateHourControl(); - // set value after spinner range is updated - setCurrentHour(currentHour, false); - updateMinuteControl(); - updateAmPmControl(); + generateLegalTimesTree(); + int hour = mRadialTimePickerView.getCurrentHour(); + mInitialHourOfDay = hour; + updateHeaderHour(hour, false); + updateHeaderAmPm(); + updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing()); + mDelegator.invalidate(); } + /** + * @return true if this is in 24 hour view else false. + */ @Override public boolean is24HourView() { return mIs24HourView; } @Override - public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) { - mOnTimeChangedListener = onTimeChangedListener; + public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) { + mOnTimeChangedListener = callback; } @Override public void setEnabled(boolean enabled) { - mMinuteSpinner.setEnabled(enabled); - if (mDivider != null) { - mDivider.setEnabled(enabled); - } - mHourSpinner.setEnabled(enabled); - if (mAmPmSpinner != null) { - mAmPmSpinner.setEnabled(enabled); - } else { - mAmPmButton.setEnabled(enabled); - } + mHourView.setEnabled(enabled); + mMinuteView.setEnabled(enabled); + mAmLabel.setEnabled(enabled); + mPmLabel.setEnabled(enabled); + mRadialTimePickerView.setEnabled(enabled); mIsEnabled = enabled; } @@ -386,24 +397,38 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { @Override public int getBaseline() { - return mHourSpinner.getBaseline(); + // does not support baseline alignment + return -1; } @Override public void onConfigurationChanged(Configuration newConfig) { - setCurrentLocale(newConfig.locale); + updateUI(mRadialTimePickerView.getCurrentItemShowing()); } @Override public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getCurrentHour(), getCurrentMinute()); + return new SavedState(superState, getCurrentHour(), getCurrentMinute(), + is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing()); } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; - setCurrentHour(ss.getHour()); - setCurrentMinute(ss.getMinute()); + setInKbMode(ss.inKbMode()); + setTypedTimes(ss.getTypesTimes()); + initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing()); + mRadialTimePickerView.invalidate(); + if (mInKbMode) { + tryStartingKbMode(-1); + mHourView.invalidate(); + } + } + + @Override + public void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + mTempCalendar = Calendar.getInstance(locale); } @Override @@ -422,9 +447,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { } mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); - String selectedDateUtterance = DateUtils.formatDateTime(mContext, + String selectedDate = DateUtils.formatDateTime(mContext, mTempCalendar.getTimeInMillis(), flags); - event.getText().add(selectedDateUtterance); + event.getText().add(selectedDate); } @Override @@ -437,121 +462,48 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { info.setClassName(TimePicker.class.getName()); } - private void updateInputState() { - // Make sure that if the user changes the value and the IME is active - // for one of the inputs if this widget, the IME is closed. If the user - // changed the value via the IME and there is a next input the IME will - // be shown, otherwise the user chose another means of changing the - // value and having the IME up makes no sense. - InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); - if (inputMethodManager != null) { - if (inputMethodManager.isActive(mHourSpinnerInput)) { - mHourSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { - mMinuteSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { - mAmPmSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } - } - } - - private void updateAmPmControl() { - if (is24HourView()) { - if (mAmPmSpinner != null) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - mAmPmButton.setVisibility(View.GONE); - } - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - if (mAmPmSpinner != null) { - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } else { - mAmPmButton.setText(mAmPmStrings[index]); - mAmPmButton.setVisibility(View.VISIBLE); - } - } - mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - /** - * Sets the current locale. + * Set whether in keyboard mode or not. * - * @param locale The current locale. + * @param inKbMode True means in keyboard mode. */ - @Override - public void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - mTempCalendar = Calendar.getInstance(locale); + private void setInKbMode(boolean inKbMode) { + mInKbMode = inKbMode; } - private void onTimeChanged() { - mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), - getCurrentMinute()); - } + /** + * @return true if in keyboard mode + */ + private boolean inKbMode() { + return mInKbMode; } - private void updateHourControl() { - if (is24HourView()) { - // 'k' means 1-24 hour - if (mHourFormat == 'k') { - mHourSpinner.setMinValue(1); - mHourSpinner.setMaxValue(24); - } else { - mHourSpinner.setMinValue(0); - mHourSpinner.setMaxValue(23); - } - } else { - // 'K' means 0-11 hour - if (mHourFormat == 'K') { - mHourSpinner.setMinValue(0); - mHourSpinner.setMaxValue(11); - } else { - mHourSpinner.setMinValue(1); - mHourSpinner.setMaxValue(12); - } - } - mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); + private void setTypedTimes(ArrayList<Integer> typeTimes) { + mTypedTimes = typeTimes; } - private void updateMinuteControl() { - if (is24HourView()) { - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); - } else { - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - } + /** + * @return an array of typed times + */ + private ArrayList<Integer> getTypedTimes() { + return mTypedTimes; } - private void setContentDescriptions() { - // Minute - trySetContentDescription(mMinuteSpinner, R.id.increment, - R.string.time_picker_increment_minute_button); - trySetContentDescription(mMinuteSpinner, R.id.decrement, - R.string.time_picker_decrement_minute_button); - // Hour - trySetContentDescription(mHourSpinner, R.id.increment, - R.string.time_picker_increment_hour_button); - trySetContentDescription(mHourSpinner, R.id.decrement, - R.string.time_picker_decrement_hour_button); - // AM/PM - if (mAmPmSpinner != null) { - trySetContentDescription(mAmPmSpinner, R.id.increment, - R.string.time_picker_increment_set_pm_button); - trySetContentDescription(mAmPmSpinner, R.id.decrement, - R.string.time_picker_decrement_set_am_button); - } + /** + * @return the index of the current item showing + */ + private int getCurrentItemShowing() { + return mRadialTimePickerView.getCurrentItemShowing(); } - private void trySetContentDescription(View root, int viewId, int contDescResId) { - View target = root.findViewById(viewId); - if (target != null) { - target.setContentDescription(mContext.getString(contDescResId)); + /** + * Propagate the time change + */ + private void onTimeChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, + getCurrentHour(), getCurrentMinute()); } } @@ -559,19 +511,34 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { * Used to save / restore state of time picker */ private static class SavedState extends View.BaseSavedState { + private final int mHour; private final int mMinute; - - private SavedState(Parcelable superState, int hour, int minute) { + private final boolean mIs24HourMode; + private final boolean mInKbMode; + private final ArrayList<Integer> mTypedTimes; + private final int mCurrentItemShowing; + + private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, + boolean isKbMode, ArrayList<Integer> typedTimes, + int currentItemShowing) { super(superState); mHour = hour; mMinute = minute; + mIs24HourMode = is24HourMode; + mInKbMode = isKbMode; + mTypedTimes = typedTimes; + mCurrentItemShowing = currentItemShowing; } private SavedState(Parcel in) { super(in); mHour = in.readInt(); mMinute = in.readInt(); + mIs24HourMode = (in.readInt() == 1); + mInKbMode = (in.readInt() == 1); + mTypedTimes = in.readArrayList(getClass().getClassLoader()); + mCurrentItemShowing = in.readInt(); } public int getHour() { @@ -582,11 +549,31 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { return mMinute; } + public boolean is24HourMode() { + return mIs24HourMode; + } + + public boolean inKbMode() { + return mInKbMode; + } + + public ArrayList<Integer> getTypesTimes() { + return mTypedTimes; + } + + public int getCurrentItemShowing() { + return mCurrentItemShowing; + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mHour); dest.writeInt(mMinute); + dest.writeInt(mIs24HourMode ? 1 : 0); + dest.writeInt(mInKbMode ? 1 : 0); + dest.writeList(mTypedTimes); + dest.writeInt(mCurrentItemShowing); } @SuppressWarnings({"unused", "hiding"}) @@ -601,11 +588,706 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { }; } - public static String[] getAmPmStrings(Context context) { - String[] result = new String[2]; - LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); - result[0] = d.amPm[0].length() > 2 ? d.narrowAm : d.amPm[0]; - result[1] = d.amPm[1].length() > 2 ? d.narrowPm : d.amPm[1]; - return result; + private void tryVibrate() { + mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + } + + private void updateAmPmLabelStates(int amOrPm) { + final boolean isAm = amOrPm == AM; + mAmLabel.setChecked(isAm); + mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha); + + final boolean isPm = amOrPm == PM; + mPmLabel.setChecked(isPm); + mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha); + } + + /** + * Called by the picker for updating the header display. + */ + @Override + public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) { + if (pickerIndex == HOUR_INDEX) { + if (mAllowAutoAdvance && autoAdvance) { + updateHeaderHour(newValue, false); + setCurrentItemShowing(MINUTE_INDEX, true, false); + mRadialTimePickerView.announceForAccessibility(newValue + ". " + mSelectMinutes); + } else { + updateHeaderHour(newValue, true); + mRadialTimePickerView.setContentDescription( + mHourPickerDescription + ": " + newValue); + } + } else if (pickerIndex == MINUTE_INDEX){ + updateHeaderMinute(newValue, true); + mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue); + } else if (pickerIndex == AMPM_INDEX) { + updateAmPmLabelStates(newValue); + } else if (pickerIndex == ENABLE_PICKER_INDEX) { + if (!isTypedTimeFullyLegal()) { + mTypedTimes.clear(); + } + finishKbMode(); + } } + + private void updateHeaderHour(int value, boolean announce) { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final int lengthPattern = bestDateTimePattern.length(); + boolean hourWithTwoDigit = false; + char hourFormat = '\0'; + // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save + // the hour format that we found. + for (int i = 0; i < lengthPattern; i++) { + final char c = bestDateTimePattern.charAt(i); + if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { + hourFormat = c; + if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { + hourWithTwoDigit = true; + } + break; + } + } + final String format; + if (hourWithTwoDigit) { + format = "%02d"; + } else { + format = "%d"; + } + if (mIs24HourView) { + // 'k' means 1-24 hour + if (hourFormat == 'k' && value == 0) { + value = 24; + } + } else { + // 'K' means 0-11 hour + value = modulo12(value, hourFormat == 'K'); + } + CharSequence text = String.format(format, value); + mHourView.setText(text); + if (announce) { + tryAnnounceForAccessibility(text, true); + } + } + + private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) { + if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) { + // TODO: Find a better solution, potentially live regions? + mDelegator.announceForAccessibility(text); + mLastAnnouncedText = text; + mLastAnnouncedIsHour = isHour; + } + } + + private static int modulo12(int n, boolean startWithZero) { + int value = n % 12; + if (value == 0 && !startWithZero) { + value = 12; + } + return value; + } + + /** + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the + * separator as the character which is just after the hour marker in the returned pattern. + */ + private void updateHeaderSeparator() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final String separatorText; + // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats + final char[] hourFormats = {'H', 'h', 'K', 'k'}; + int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats); + if (hIndex == -1) { + // Default case + separatorText = ":"; + } else { + separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); + } + mSeparatorView.setText(separatorText); + } + + static private int lastIndexOfAny(String str, char[] any) { + final int lengthAny = any.length; + if (lengthAny > 0) { + for (int i = str.length() - 1; i >= 0; i--) { + char c = str.charAt(i); + for (int j = 0; j < lengthAny; j++) { + if (c == any[j]) { + return i; + } + } + } + } + return -1; + } + + private void updateHeaderMinute(int value, boolean announceForAccessibility) { + if (value == 60) { + value = 0; + } + final CharSequence text = String.format(mCurrentLocale, "%02d", value); + mMinuteView.setText(text); + if (announceForAccessibility) { + tryAnnounceForAccessibility(text, false); + } + } + + /** + * Show either Hours or Minutes. + */ + private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) { + mRadialTimePickerView.setCurrentItemShowing(index, animateCircle); + + if (index == HOUR_INDEX) { + int hours = mRadialTimePickerView.getCurrentHour(); + if (!mIs24HourView) { + hours = hours % 12; + } + mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours); + if (announce) { + mRadialTimePickerView.announceForAccessibility(mSelectHours); + } + } else { + int minutes = mRadialTimePickerView.getCurrentMinute(); + mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes); + if (announce) { + mRadialTimePickerView.announceForAccessibility(mSelectMinutes); + } + } + + mHourView.setSelected(index == HOUR_INDEX); + mMinuteView.setSelected(index == MINUTE_INDEX); + } + + private void setAmOrPm(int amOrPm) { + updateAmPmLabelStates(amOrPm); + mRadialTimePickerView.setAmOrPm(amOrPm); + } + + /** + * For keyboard mode, processes key events. + * + * @param keyCode the pressed key. + * + * @return true if the key was successfully processed, false otherwise. + */ + private boolean processKeyUp(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + if (mInKbMode) { + if (!mTypedTimes.isEmpty()) { + int deleted = deleteLastTypedKey(); + String deletedKeyStr; + if (deleted == getAmOrPmKeyCode(AM)) { + deletedKeyStr = mAmText; + } else if (deleted == getAmOrPmKeyCode(PM)) { + deletedKeyStr = mPmText; + } else { + deletedKeyStr = String.format("%d", getValFromKeyCode(deleted)); + } + mRadialTimePickerView.announceForAccessibility( + String.format(mDeletedKeyFormat, deletedKeyStr)); + updateDisplay(true); + } + } + } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1 + || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3 + || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5 + || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 + || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 + || (!mIs24HourView && + (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { + if (!mInKbMode) { + if (mRadialTimePickerView == null) { + // Something's wrong, because time picker should definitely not be null. + Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null."); + return true; + } + mTypedTimes.clear(); + tryStartingKbMode(keyCode); + return true; + } + // We're already in keyboard mode. + if (addKeyIfLegal(keyCode)) { + updateDisplay(false); + } + return true; + } + return false; + } + + /** + * Try to start keyboard mode with the specified key. + * + * @param keyCode The key to use as the first press. Keyboard mode will not be started if the + * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting + * key. + */ + private void tryStartingKbMode(int keyCode) { + if (keyCode == -1 || addKeyIfLegal(keyCode)) { + mInKbMode = true; + onValidationChanged(false); + updateDisplay(false); + mRadialTimePickerView.setInputEnabled(false); + } + } + + private boolean addKeyIfLegal(int keyCode) { + // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode, + // we'll need to see if AM/PM have been typed. + if ((mIs24HourView && mTypedTimes.size() == 4) || + (!mIs24HourView && isTypedTimeFullyLegal())) { + return false; + } + + mTypedTimes.add(keyCode); + if (!isTypedTimeLegalSoFar()) { + deleteLastTypedKey(); + return false; + } + + int val = getValFromKeyCode(keyCode); + mRadialTimePickerView.announceForAccessibility(String.format("%d", val)); + // Automatically fill in 0's if AM or PM was legally entered. + if (isTypedTimeFullyLegal()) { + if (!mIs24HourView && mTypedTimes.size() <= 3) { + mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); + mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); + } + onValidationChanged(true); + } + + return true; + } + + /** + * Traverse the tree to see if the keys that have been typed so far are legal as is, + * or may become legal as more keys are typed (excluding backspace). + */ + private boolean isTypedTimeLegalSoFar() { + Node node = mLegalTimesTree; + for (int keyCode : mTypedTimes) { + node = node.canReach(keyCode); + if (node == null) { + return false; + } + } + return true; + } + + /** + * Check if the time that has been typed so far is completely legal, as is. + */ + private boolean isTypedTimeFullyLegal() { + if (mIs24HourView) { + // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note: + // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode. + int[] values = getEnteredTime(null); + return (values[0] >= 0 && values[1] >= 0 && values[1] < 60); + } else { + // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be + // legally added at specific times based on the tree's algorithm. + return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) || + mTypedTimes.contains(getAmOrPmKeyCode(PM))); + } + } + + private int deleteLastTypedKey() { + int deleted = mTypedTimes.remove(mTypedTimes.size() - 1); + if (!isTypedTimeFullyLegal()) { + onValidationChanged(false); + } + return deleted; + } + + /** + * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. + */ + private void finishKbMode() { + mInKbMode = false; + if (!mTypedTimes.isEmpty()) { + int values[] = getEnteredTime(null); + mRadialTimePickerView.setCurrentHour(values[0]); + mRadialTimePickerView.setCurrentMinute(values[1]); + if (!mIs24HourView) { + mRadialTimePickerView.setAmOrPm(values[2]); + } + mTypedTimes.clear(); + } + updateDisplay(false); + mRadialTimePickerView.setInputEnabled(true); + } + + /** + * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is + * empty, either show an empty display (filled with the placeholder text), or update from the + * timepicker's values. + * + * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. + * Otherwise, revert to the timepicker's values. + */ + private void updateDisplay(boolean allowEmptyDisplay) { + if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { + int hour = mRadialTimePickerView.getCurrentHour(); + int minute = mRadialTimePickerView.getCurrentMinute(); + updateHeaderHour(hour, false); + updateHeaderMinute(minute, false); + if (!mIs24HourView) { + updateAmPmLabelStates(hour < 12 ? AM : PM); + } + setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true); + onValidationChanged(true); + } else { + boolean[] enteredZeros = {false, false}; + int[] values = getEnteredTime(enteredZeros); + String hourFormat = enteredZeros[0] ? "%02d" : "%2d"; + String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d"; + String hourStr = (values[0] == -1) ? mDoublePlaceholderText : + String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); + String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : + String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); + mHourView.setText(hourStr); + mHourView.setSelected(false); + mMinuteView.setText(minuteStr); + mMinuteView.setSelected(false); + if (!mIs24HourView) { + updateAmPmLabelStates(values[2]); + } + } + } + + private int getValFromKeyCode(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_0: + return 0; + case KeyEvent.KEYCODE_1: + return 1; + case KeyEvent.KEYCODE_2: + return 2; + case KeyEvent.KEYCODE_3: + return 3; + case KeyEvent.KEYCODE_4: + return 4; + case KeyEvent.KEYCODE_5: + return 5; + case KeyEvent.KEYCODE_6: + return 6; + case KeyEvent.KEYCODE_7: + return 7; + case KeyEvent.KEYCODE_8: + return 8; + case KeyEvent.KEYCODE_9: + return 9; + default: + return -1; + } + } + + /** + * Get the currently-entered time, as integer values of the hours and minutes typed. + * + * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which + * may then be used for the caller to know whether zeros had been explicitly entered as either + * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. + * + * @return A size-3 int array. The first value will be the hours, the second value will be the + * minutes, and the third will be either AM or PM. + */ + private int[] getEnteredTime(boolean[] enteredZeros) { + int amOrPm = -1; + int startIndex = 1; + if (!mIs24HourView && isTypedTimeFullyLegal()) { + int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); + if (keyCode == getAmOrPmKeyCode(AM)) { + amOrPm = AM; + } else if (keyCode == getAmOrPmKeyCode(PM)){ + amOrPm = PM; + } + startIndex = 2; + } + int minute = -1; + int hour = -1; + for (int i = startIndex; i <= mTypedTimes.size(); i++) { + int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i)); + if (i == startIndex) { + minute = val; + } else if (i == startIndex+1) { + minute += 10 * val; + if (enteredZeros != null && val == 0) { + enteredZeros[1] = true; + } + } else if (i == startIndex+2) { + hour = val; + } else if (i == startIndex+3) { + hour += 10 * val; + if (enteredZeros != null && val == 0) { + enteredZeros[0] = true; + } + } + } + + return new int[] { hour, minute, amOrPm }; + } + + /** + * Get the keycode value for AM and PM in the current language. + */ + private int getAmOrPmKeyCode(int amOrPm) { + // Cache the codes. + if (mAmKeyCode == -1 || mPmKeyCode == -1) { + // Find the first character in the AM/PM text that is unique. + KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + char amChar; + char pmChar; + for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) { + amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i); + pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i); + if (amChar != pmChar) { + KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar}); + // There should be 4 events: a down and up for both AM and PM. + if (events != null && events.length == 4) { + mAmKeyCode = events[0].getKeyCode(); + mPmKeyCode = events[2].getKeyCode(); + } else { + Log.e(TAG, "Unable to find keycodes for AM and PM."); + } + break; + } + } + } + if (amOrPm == AM) { + return mAmKeyCode; + } else if (amOrPm == PM) { + return mPmKeyCode; + } + + return -1; + } + + /** + * Create a tree for deciding what keys can legally be typed. + */ + private void generateLegalTimesTree() { + // Create a quick cache of numbers to their keycodes. + final int k0 = KeyEvent.KEYCODE_0; + final int k1 = KeyEvent.KEYCODE_1; + final int k2 = KeyEvent.KEYCODE_2; + final int k3 = KeyEvent.KEYCODE_3; + final int k4 = KeyEvent.KEYCODE_4; + final int k5 = KeyEvent.KEYCODE_5; + final int k6 = KeyEvent.KEYCODE_6; + final int k7 = KeyEvent.KEYCODE_7; + final int k8 = KeyEvent.KEYCODE_8; + final int k9 = KeyEvent.KEYCODE_9; + + // The root of the tree doesn't contain any numbers. + mLegalTimesTree = new Node(); + if (mIs24HourView) { + // We'll be re-using these nodes, so we'll save them. + Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5); + Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + // The first digit must be followed by the second digit. + minuteFirstDigit.addChild(minuteSecondDigit); + + // The first digit may be 0-1. + Node firstDigit = new Node(k0, k1); + mLegalTimesTree.addChild(firstDigit); + + // When the first digit is 0-1, the second digit may be 0-5. + Node secondDigit = new Node(k0, k1, k2, k3, k4, k5); + firstDigit.addChild(secondDigit); + // We may now be followed by the first minute digit. E.g. 00:09, 15:58. + secondDigit.addChild(minuteFirstDigit); + + // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9. + Node thirdDigit = new Node(k6, k7, k8, k9); + // The time must now be finished. E.g. 0:55, 1:08. + secondDigit.addChild(thirdDigit); + + // When the first digit is 0-1, the second digit may be 6-9. + secondDigit = new Node(k6, k7, k8, k9); + firstDigit.addChild(secondDigit); + // We must now be followed by the first minute digit. E.g. 06:50, 18:20. + secondDigit.addChild(minuteFirstDigit); + + // The first digit may be 2. + firstDigit = new Node(k2); + mLegalTimesTree.addChild(firstDigit); + + // When the first digit is 2, the second digit may be 0-3. + secondDigit = new Node(k0, k1, k2, k3); + firstDigit.addChild(secondDigit); + // We must now be followed by the first minute digit. E.g. 20:50, 23:09. + secondDigit.addChild(minuteFirstDigit); + + // When the first digit is 2, the second digit may be 4-5. + secondDigit = new Node(k4, k5); + firstDigit.addChild(secondDigit); + // We must now be followd by the last minute digit. E.g. 2:40, 2:53. + secondDigit.addChild(minuteSecondDigit); + + // The first digit may be 3-9. + firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9); + mLegalTimesTree.addChild(firstDigit); + // We must now be followed by the first minute digit. E.g. 3:57, 8:12. + firstDigit.addChild(minuteFirstDigit); + } else { + // We'll need to use the AM/PM node a lot. + // Set up AM and PM to respond to "a" and "p". + Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM)); + + // The first hour digit may be 1. + Node firstDigit = new Node(k1); + mLegalTimesTree.addChild(firstDigit); + // We'll allow quick input of on-the-hour times. E.g. 1pm. + firstDigit.addChild(ampm); + + // When the first digit is 1, the second digit may be 0-2. + Node secondDigit = new Node(k0, k1, k2); + firstDigit.addChild(secondDigit); + // Also for quick input of on-the-hour times. E.g. 10pm, 12am. + secondDigit.addChild(ampm); + + // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5. + Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5); + secondDigit.addChild(thirdDigit); + // The time may be finished now. E.g. 1:02pm, 1:25am. + thirdDigit.addChild(ampm); + + // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5, + // the fourth digit may be 0-9. + Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + thirdDigit.addChild(fourthDigit); + // The time must be finished now. E.g. 10:49am, 12:40pm. + fourthDigit.addChild(ampm); + + // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9. + thirdDigit = new Node(k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 1:08am, 1:26pm. + thirdDigit.addChild(ampm); + + // When the first digit is 1, the second digit may be 3-5. + secondDigit = new Node(k3, k4, k5); + firstDigit.addChild(secondDigit); + + // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9. + thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 1:39am, 1:50pm. + thirdDigit.addChild(ampm); + + // The hour digit may be 2-9. + firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9); + mLegalTimesTree.addChild(firstDigit); + // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm. + firstDigit.addChild(ampm); + + // When the first digit is 2-9, the second digit may be 0-5. + secondDigit = new Node(k0, k1, k2, k3, k4, k5); + firstDigit.addChild(secondDigit); + + // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9. + thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 2:57am, 9:30pm. + thirdDigit.addChild(ampm); + } + } + + /** + * Simple node class to be used for traversal to check for legal times. + * mLegalKeys represents the keys that can be typed to get to the node. + * mChildren are the children that can be reached from this node. + */ + private class Node { + private int[] mLegalKeys; + private ArrayList<Node> mChildren; + + public Node(int... legalKeys) { + mLegalKeys = legalKeys; + mChildren = new ArrayList<Node>(); + } + + public void addChild(Node child) { + mChildren.add(child); + } + + public boolean containsKey(int key) { + for (int i = 0; i < mLegalKeys.length; i++) { + if (mLegalKeys[i] == key) { + return true; + } + } + return false; + } + + public Node canReach(int key) { + if (mChildren == null) { + return null; + } + for (Node child : mChildren) { + if (child.containsKey(key)) { + return child; + } + } + return null; + } + } + + private final View.OnClickListener mClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + + final int amOrPm; + switch (v.getId()) { + case R.id.am_label: + setAmOrPm(AM); + break; + case R.id.pm_label: + setAmOrPm(PM); + break; + case R.id.hours: + setCurrentItemShowing(HOUR_INDEX, true, true); + break; + case R.id.minutes: + setCurrentItemShowing(MINUTE_INDEX, true, true); + break; + default: + // Failed to handle this click, don't vibrate. + return; + } + + tryVibrate(); + } + }; + + private final View.OnKeyListener mKeyListener = new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + return processKeyUp(keyCode); + } + return false; + } + }; + + private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) { + finishKbMode(); + + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, + mRadialTimePickerView.getCurrentHour(), + mRadialTimePickerView.getCurrentMinute()); + } + } + } + }; } diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java index d9c4114..e162f4a 100644 --- a/core/java/android/widget/TimePickerSpinnerDelegate.java +++ b/core/java/android/widget/TimePickerSpinnerDelegate.java @@ -17,376 +17,365 @@ package android.widget; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Configuration; -import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.HapticFeedbackConstants; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; - +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; import com.android.internal.R; -import java.util.ArrayList; +import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Locale; -/** - * A delegate implementing the radial clock-based TimePicker. - */ -class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate implements - RadialTimePickerView.OnValueSelectedListener { - - private static final String TAG = "TimePickerDelegate"; - - // Index used by RadialPickerLayout - private static final int HOUR_INDEX = 0; - private static final int MINUTE_INDEX = 1; - - // NOT a real index for the purpose of what's showing. - private static final int AMPM_INDEX = 2; +import libcore.icu.LocaleData; - // Also NOT a real index, just used for keyboard mode. - private static final int ENABLE_PICKER_INDEX = 3; - - static final int AM = 0; - static final int PM = 1; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; +/** + * A delegate implementing the basic spinner-based TimePicker. + */ +class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { private static final boolean DEFAULT_ENABLED_STATE = true; - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; - private static final int HOURS_IN_HALF_DAY = 12; - private final View mHeaderView; - private final TextView mHourView; - private final TextView mMinuteView; - private final View mAmPmLayout; - private final CheckedTextView mAmLabel; - private final CheckedTextView mPmLabel; - private final RadialTimePickerView mRadialTimePickerView; - private final TextView mSeparatorView; - - private final String mAmText; - private final String mPmText; + // state + private boolean mIs24HourView; + private boolean mIsAm; - private final float mDisabledAlpha; + // ui components + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + private final EditText mHourSpinnerInput; + private final EditText mMinuteSpinnerInput; + private final EditText mAmPmSpinnerInput; + private final TextView mDivider; - private boolean mAllowAutoAdvance; - private int mInitialHourOfDay; - private int mInitialMinute; - private boolean mIs24HourView; + // Note that the legacy implementation of the TimePicker is + // using a button for toggling between AM/PM while the new + // version uses a NumberPicker spinner. Therefore the code + // accommodates these two cases to be backwards compatible. + private final Button mAmPmButton; - // For hardware IME input. - private char mPlaceholderText; - private String mDoublePlaceholderText; - private String mDeletedKeyFormat; - private boolean mInKbMode; - private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>(); - private Node mLegalTimesTree; - private int mAmKeyCode; - private int mPmKeyCode; - - // Accessibility strings. - private String mHourPickerDescription; - private String mSelectHours; - private String mMinutePickerDescription; - private String mSelectMinutes; - - // Most recent time announcement values for accessibility. - private CharSequence mLastAnnouncedText; - private boolean mLastAnnouncedIsHour; + private final String[] mAmPmStrings; + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; private Calendar mTempCalendar; + private boolean mHourWithTwoDigit; + private char mHourFormat; public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(delegator, context); // process style attributes - final TypedArray a = mContext.obtainStyledAttributes(attrs, - R.styleable.TimePicker, defStyleAttr, defStyleRes); - final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - final Resources res = mContext.getResources(); - - mHourPickerDescription = res.getString(R.string.hour_picker_description); - mSelectHours = res.getString(R.string.select_hours); - mMinutePickerDescription = res.getString(R.string.minute_picker_description); - mSelectMinutes = res.getString(R.string.select_minutes); - - String[] amPmStrings = TimePickerClockDelegate.getAmPmStrings(context); - mAmText = amPmStrings[0]; - mPmText = amPmStrings[1]; - - final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout, - R.layout.time_picker_holo); - final View mainView = inflater.inflate(layoutResourceId, delegator); - - mHeaderView = mainView.findViewById(R.id.time_header); - mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground)); - - // Set up hour/minute labels. - mHourView = (TextView) mHeaderView.findViewById(R.id.hours); - mHourView.setOnClickListener(mClickListener); - mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator); - mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes); - mMinuteView.setOnClickListener(mClickListener); - - final int headerTimeTextAppearance = a.getResourceId( - R.styleable.TimePicker_headerTimeTextAppearance, 0); - if (headerTimeTextAppearance != 0) { - mHourView.setTextAppearance(context, headerTimeTextAppearance); - mSeparatorView.setTextAppearance(context, headerTimeTextAppearance); - mMinuteView.setTextAppearance(context, headerTimeTextAppearance); - } - - // TODO: This can be removed once we support themed color state lists. - final int headerSelectedTextColor = a.getColor( - R.styleable.TimePicker_headerSelectedTextColor, - res.getColor(R.color.timepicker_default_selector_color_material)); - mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(), - R.attr.state_selected, headerSelectedTextColor)); - mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(), - R.attr.state_selected, headerSelectedTextColor)); - - // Set up AM/PM labels. - mAmPmLayout = mHeaderView.findViewById(R.id.ampm_layout); - mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label); - mAmLabel.setText(amPmStrings[0]); - mAmLabel.setOnClickListener(mClickListener); - mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label); - mPmLabel.setText(amPmStrings[1]); - mPmLabel.setOnClickListener(mClickListener); - - final int headerAmPmTextAppearance = a.getResourceId( - R.styleable.TimePicker_headerAmPmTextAppearance, 0); - if (headerAmPmTextAppearance != 0) { - mAmLabel.setTextAppearance(context, headerAmPmTextAppearance); - mPmLabel.setTextAppearance(context, headerAmPmTextAppearance); - } - + final TypedArray a = mContext.obtainStyledAttributes( + attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes); + final int layoutResourceId = a.getResourceId( + R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy); a.recycle(); - // Pull disabled alpha from theme. - final TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); - mDisabledAlpha = outValue.getFloat(); + final LayoutInflater inflater = LayoutInflater.from(mContext); + inflater.inflate(layoutResourceId, mDelegator, true); + + // hour + mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { + updateInputState(); + if (!is24HourView()) { + if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || + (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } + onTimeChanged(); + } + }); + mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); + mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + + // divider (only for the new widget style) + mDivider = (TextView) mDelegator.findViewById(R.id.divider); + if (mDivider != null) { + setDividerText(); + } + + // minute + mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute); + mMinuteSpinner.setMinValue(0); + mMinuteSpinner.setMaxValue(59); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); + mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { + updateInputState(); + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + if (oldVal == maxValue && newVal == minValue) { + int newHour = mHourSpinner.getValue() + 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + mHourSpinner.setValue(newHour); + } else if (oldVal == minValue && newVal == maxValue) { + int newHour = mHourSpinner.getValue() - 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + mHourSpinner.setValue(newHour); + } + onTimeChanged(); + } + }); + mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + + // Get the localized am/pm strings and use them in the spinner. + mAmPmStrings = getAmPmStrings(context); + + // am/pm + final View amPmView = mDelegator.findViewById(R.id.amPm); + if (amPmView instanceof Button) { + mAmPmSpinner = null; + mAmPmSpinnerInput = null; + mAmPmButton = (Button) amPmView; + mAmPmButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View button) { + button.requestFocus(); + mIsAm = !mIsAm; + updateAmPmControl(); + onTimeChanged(); + } + }); + } else { + mAmPmButton = null; + mAmPmSpinner = (NumberPicker) amPmView; + mAmPmSpinner.setMinValue(0); + mAmPmSpinner.setMaxValue(1); + mAmPmSpinner.setDisplayedValues(mAmPmStrings); + mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateInputState(); + picker.requestFocus(); + mIsAm = !mIsAm; + updateAmPmControl(); + onTimeChanged(); + } + }); + mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); + mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + } + + if (isAmPmAtStart()) { + // Move the am/pm view to the beginning + ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout); + amPmParent.removeView(amPmView); + amPmParent.addView(amPmView, 0); + // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme + // for example and not for Holo Theme) + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); + final int startMargin = lp.getMarginStart(); + final int endMargin = lp.getMarginEnd(); + if (startMargin != endMargin) { + lp.setMarginStart(endMargin); + lp.setMarginEnd(startMargin); + } + } - mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById( - R.id.radial_picker); + getHourFormatData(); - setupListeners(); + // update controls to initial state + updateHourControl(); + updateMinuteControl(); + updateAmPmControl(); - mAllowAutoAdvance = true; + // set to current time + setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); + setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); - // Set up for keyboard mode. - mDoublePlaceholderText = res.getString(R.string.time_placeholder); - mDeletedKeyFormat = res.getString(R.string.deleted_key); - mPlaceholderText = mDoublePlaceholderText.charAt(0); - mAmKeyCode = mPmKeyCode = -1; - generateLegalTimesTree(); + if (!isEnabled()) { + setEnabled(false); + } - // Initialize with current time - final Calendar calendar = Calendar.getInstance(mCurrentLocale); - final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); - final int currentMinute = calendar.get(Calendar.MINUTE); - initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX); - } + // set the content descriptions + setContentDescriptions(); - private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) { - mInitialHourOfDay = hourOfDay; - mInitialMinute = minute; - mIs24HourView = is24HourView; - mInKbMode = false; - updateUI(index); + // If not explicitly specified this view is important for accessibility. + if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + } } - private void setupListeners() { - mHeaderView.setOnKeyListener(mKeyListener); - mHeaderView.setOnFocusChangeListener(mFocusListener); - mHeaderView.setFocusable(true); - - mRadialTimePickerView.setOnValueSelectedListener(this); + private void getHourFormatData() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final int lengthPattern = bestDateTimePattern.length(); + mHourWithTwoDigit = false; + char hourFormat = '\0'; + // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save + // the hour format that we found. + for (int i = 0; i < lengthPattern; i++) { + final char c = bestDateTimePattern.charAt(i); + if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { + mHourFormat = c; + if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { + mHourWithTwoDigit = true; + } + break; + } + } } - private void updateUI(int index) { - // Update RadialPicker values - updateRadialPicker(index); - // Enable or disable the AM/PM view. - updateHeaderAmPm(); - // Update Hour and Minutes - updateHeaderHour(mInitialHourOfDay, false); - // Update time separator - updateHeaderSeparator(); - // Update Minutes - updateHeaderMinute(mInitialMinute, false); - // Invalidate everything - mDelegator.invalidate(); - } + private boolean isAmPmAtStart() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + "hm" /* skeleton */); - private void updateRadialPicker(int index) { - mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView); - setCurrentItemShowing(index, false, true); + return bestDateTimePattern.startsWith("a"); } - private int computeMaxWidthOfNumbers(int max) { - TextView tempView = new TextView(mContext); - tempView.setTextAppearance(mContext, R.style.TextAppearance_Material_TimePicker_TimeLabel); - ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - tempView.setLayoutParams(lp); - int maxWidth = 0; - for (int minutes = 0; minutes < max; minutes++) { - final String text = String.format("%02d", minutes); - tempView.setText(text); - tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth()); + /** + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the + * separator as the character which is just after the hour marker in the returned pattern. + */ + private void setDividerText() { + final String skeleton = (mIs24HourView) ? "Hm" : "hm"; + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + skeleton); + final String separatorText; + int hourIndex = bestDateTimePattern.lastIndexOf('H'); + if (hourIndex == -1) { + hourIndex = bestDateTimePattern.lastIndexOf('h'); } - return maxWidth; - } - - private void updateHeaderAmPm() { - if (mIs24HourView) { - mAmPmLayout.setVisibility(View.GONE); + if (hourIndex == -1) { + // Default case + separatorText = ":"; } else { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern( - mCurrentLocale, "hm"); - boolean amPmOnLeft = bestDateTimePattern.startsWith("a"); - if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) == - View.LAYOUT_DIRECTION_RTL) { - amPmOnLeft = !amPmOnLeft; - } - - final ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) mAmPmLayout.getLayoutParams(); - - if (amPmOnLeft) { - params.leftMargin = 0; - params.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */); + int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); + if (minuteIndex == -1) { + separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); } else { - params.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */); - params.rightMargin = 0; + separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); } - - mAmPmLayout.setLayoutParams(params); - mAmPmLayout.setVisibility(View.VISIBLE); - - updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM); } + mDivider.setText(separatorText); } - /** - * Set the current hour. - */ @Override public void setCurrentHour(Integer currentHour) { - if (mInitialHourOfDay == currentHour) { + setCurrentHour(currentHour, true); + } + + private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { + // why was Integer used in the first place? + if (currentHour == null || currentHour == getCurrentHour()) { return; } - mInitialHourOfDay = currentHour; - updateHeaderHour(currentHour, true); - updateHeaderAmPm(); - mRadialTimePickerView.setCurrentHour(currentHour); - mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM); - mDelegator.invalidate(); - onTimeChanged(); + if (!is24HourView()) { + // convert [0,23] ordinal to wall clock display + if (currentHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (currentHour > HOURS_IN_HALF_DAY) { + currentHour = currentHour - HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (currentHour == 0) { + currentHour = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(currentHour); + if (notifyTimeChanged) { + onTimeChanged(); + } } - /** - * @return The current hour in the range (0-23). - */ @Override public Integer getCurrentHour() { - int currentHour = mRadialTimePickerView.getCurrentHour(); - if (mIs24HourView) { + int currentHour = mHourSpinner.getValue(); + if (is24HourView()) { return currentHour; + } else if (mIsAm) { + return currentHour % HOURS_IN_HALF_DAY; } else { - switch(mRadialTimePickerView.getAmOrPm()) { - case PM: - return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; - case AM: - default: - return currentHour % HOURS_IN_HALF_DAY; - } + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; } } - /** - * Set the current minute (0-59). - */ @Override public void setCurrentMinute(Integer currentMinute) { - if (mInitialMinute == currentMinute) { + if (currentMinute == getCurrentMinute()) { return; } - mInitialMinute = currentMinute; - updateHeaderMinute(currentMinute, true); - mRadialTimePickerView.setCurrentMinute(currentMinute); - mDelegator.invalidate(); + mMinuteSpinner.setValue(currentMinute); onTimeChanged(); } - /** - * @return The current minute. - */ @Override public Integer getCurrentMinute() { - return mRadialTimePickerView.getCurrentMinute(); + return mMinuteSpinner.getValue(); } - /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True = 24 hour mode. False = AM/PM. - */ @Override public void setIs24HourView(Boolean is24HourView) { - if (is24HourView == mIs24HourView) { + if (mIs24HourView == is24HourView) { return; } + // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! + int currentHour = getCurrentHour(); + // Order is important here. mIs24HourView = is24HourView; - generateLegalTimesTree(); - int hour = mRadialTimePickerView.getCurrentHour(); - mInitialHourOfDay = hour; - updateHeaderHour(hour, false); - updateHeaderAmPm(); - updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing()); - mDelegator.invalidate(); + getHourFormatData(); + updateHourControl(); + // set value after spinner range is updated + setCurrentHour(currentHour, false); + updateMinuteControl(); + updateAmPmControl(); } - /** - * @return true if this is in 24 hour view else false. - */ @Override public boolean is24HourView() { return mIs24HourView; } @Override - public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) { - mOnTimeChangedListener = callback; + public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) { + mOnTimeChangedListener = onTimeChangedListener; } @Override public void setEnabled(boolean enabled) { - mHourView.setEnabled(enabled); - mMinuteView.setEnabled(enabled); - mAmLabel.setEnabled(enabled); - mPmLabel.setEnabled(enabled); - mRadialTimePickerView.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + if (mDivider != null) { + mDivider.setEnabled(enabled); + } + mHourSpinner.setEnabled(enabled); + if (mAmPmSpinner != null) { + mAmPmSpinner.setEnabled(enabled); + } else { + mAmPmButton.setEnabled(enabled); + } mIsEnabled = enabled; } @@ -397,38 +386,24 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im @Override public int getBaseline() { - // does not support baseline alignment - return -1; + return mHourSpinner.getBaseline(); } @Override public void onConfigurationChanged(Configuration newConfig) { - updateUI(mRadialTimePickerView.getCurrentItemShowing()); + setCurrentLocale(newConfig.locale); } @Override public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getCurrentHour(), getCurrentMinute(), - is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing()); + return new SavedState(superState, getCurrentHour(), getCurrentMinute()); } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; - setInKbMode(ss.inKbMode()); - setTypedTimes(ss.getTypesTimes()); - initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing()); - mRadialTimePickerView.invalidate(); - if (mInKbMode) { - tryStartingKbMode(-1); - mHourView.invalidate(); - } - } - - @Override - public void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - mTempCalendar = Calendar.getInstance(locale); + setCurrentHour(ss.getHour()); + setCurrentMinute(ss.getMinute()); } @Override @@ -447,9 +422,9 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im } mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); - String selectedDate = DateUtils.formatDateTime(mContext, + String selectedDateUtterance = DateUtils.formatDateTime(mContext, mTempCalendar.getTimeInMillis(), flags); - event.getText().add(selectedDate); + event.getText().add(selectedDateUtterance); } @Override @@ -462,48 +437,121 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im info.setClassName(TimePicker.class.getName()); } + private void updateInputState() { + // Make sure that if the user changes the value and the IME is active + // for one of the inputs if this widget, the IME is closed. If the user + // changed the value via the IME and there is a next input the IME will + // be shown, otherwise the user chose another means of changing the + // value and having the IME up makes no sense. + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (inputMethodManager.isActive(mHourSpinnerInput)) { + mHourSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { + mMinuteSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { + mAmPmSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } + } + } + + private void updateAmPmControl() { + if (is24HourView()) { + if (mAmPmSpinner != null) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + mAmPmButton.setVisibility(View.GONE); + } + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + if (mAmPmSpinner != null) { + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } else { + mAmPmButton.setText(mAmPmStrings[index]); + mAmPmButton.setVisibility(View.VISIBLE); + } + } + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + /** - * Set whether in keyboard mode or not. + * Sets the current locale. * - * @param inKbMode True means in keyboard mode. + * @param locale The current locale. */ - private void setInKbMode(boolean inKbMode) { - mInKbMode = inKbMode; + @Override + public void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + mTempCalendar = Calendar.getInstance(locale); } - /** - * @return true if in keyboard mode - */ - private boolean inKbMode() { - return mInKbMode; + private void onTimeChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), + getCurrentMinute()); + } } - private void setTypedTimes(ArrayList<Integer> typeTimes) { - mTypedTimes = typeTimes; + private void updateHourControl() { + if (is24HourView()) { + // 'k' means 1-24 hour + if (mHourFormat == 'k') { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(24); + } else { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(23); + } + } else { + // 'K' means 0-11 hour + if (mHourFormat == 'K') { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(11); + } else { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(12); + } + } + mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); } - /** - * @return an array of typed times - */ - private ArrayList<Integer> getTypedTimes() { - return mTypedTimes; + private void updateMinuteControl() { + if (is24HourView()) { + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + } else { + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + } } - /** - * @return the index of the current item showing - */ - private int getCurrentItemShowing() { - return mRadialTimePickerView.getCurrentItemShowing(); + private void setContentDescriptions() { + // Minute + trySetContentDescription(mMinuteSpinner, R.id.increment, + R.string.time_picker_increment_minute_button); + trySetContentDescription(mMinuteSpinner, R.id.decrement, + R.string.time_picker_decrement_minute_button); + // Hour + trySetContentDescription(mHourSpinner, R.id.increment, + R.string.time_picker_increment_hour_button); + trySetContentDescription(mHourSpinner, R.id.decrement, + R.string.time_picker_decrement_hour_button); + // AM/PM + if (mAmPmSpinner != null) { + trySetContentDescription(mAmPmSpinner, R.id.increment, + R.string.time_picker_increment_set_pm_button); + trySetContentDescription(mAmPmSpinner, R.id.decrement, + R.string.time_picker_decrement_set_am_button); + } } - /** - * Propagate the time change - */ - private void onTimeChanged() { - mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, - getCurrentHour(), getCurrentMinute()); + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); } } @@ -511,34 +559,19 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im * Used to save / restore state of time picker */ private static class SavedState extends View.BaseSavedState { - private final int mHour; private final int mMinute; - private final boolean mIs24HourMode; - private final boolean mInKbMode; - private final ArrayList<Integer> mTypedTimes; - private final int mCurrentItemShowing; - - private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, - boolean isKbMode, ArrayList<Integer> typedTimes, - int currentItemShowing) { + + private SavedState(Parcelable superState, int hour, int minute) { super(superState); mHour = hour; mMinute = minute; - mIs24HourMode = is24HourMode; - mInKbMode = isKbMode; - mTypedTimes = typedTimes; - mCurrentItemShowing = currentItemShowing; } private SavedState(Parcel in) { super(in); mHour = in.readInt(); mMinute = in.readInt(); - mIs24HourMode = (in.readInt() == 1); - mInKbMode = (in.readInt() == 1); - mTypedTimes = in.readArrayList(getClass().getClassLoader()); - mCurrentItemShowing = in.readInt(); } public int getHour() { @@ -549,31 +582,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im return mMinute; } - public boolean is24HourMode() { - return mIs24HourMode; - } - - public boolean inKbMode() { - return mInKbMode; - } - - public ArrayList<Integer> getTypesTimes() { - return mTypedTimes; - } - - public int getCurrentItemShowing() { - return mCurrentItemShowing; - } - @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mHour); dest.writeInt(mMinute); - dest.writeInt(mIs24HourMode ? 1 : 0); - dest.writeInt(mInKbMode ? 1 : 0); - dest.writeList(mTypedTimes); - dest.writeInt(mCurrentItemShowing); } @SuppressWarnings({"unused", "hiding"}) @@ -588,706 +601,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im }; } - private void tryVibrate() { - mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); - } - - private void updateAmPmLabelStates(int amOrPm) { - final boolean isAm = amOrPm == AM; - mAmLabel.setChecked(isAm); - mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha); - - final boolean isPm = amOrPm == PM; - mPmLabel.setChecked(isPm); - mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha); - } - - /** - * Called by the picker for updating the header display. - */ - @Override - public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) { - if (pickerIndex == HOUR_INDEX) { - if (mAllowAutoAdvance && autoAdvance) { - updateHeaderHour(newValue, false); - setCurrentItemShowing(MINUTE_INDEX, true, false); - mRadialTimePickerView.announceForAccessibility(newValue + ". " + mSelectMinutes); - } else { - updateHeaderHour(newValue, true); - mRadialTimePickerView.setContentDescription( - mHourPickerDescription + ": " + newValue); - } - } else if (pickerIndex == MINUTE_INDEX){ - updateHeaderMinute(newValue, true); - mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue); - } else if (pickerIndex == AMPM_INDEX) { - updateAmPmLabelStates(newValue); - } else if (pickerIndex == ENABLE_PICKER_INDEX) { - if (!isTypedTimeFullyLegal()) { - mTypedTimes.clear(); - } - finishKbMode(); - } + public static String[] getAmPmStrings(Context context) { + String[] result = new String[2]; + LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); + result[0] = d.amPm[0].length() > 2 ? d.narrowAm : d.amPm[0]; + result[1] = d.amPm[1].length() > 2 ? d.narrowPm : d.amPm[1]; + return result; } - - private void updateHeaderHour(int value, boolean announce) { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - (mIs24HourView) ? "Hm" : "hm"); - final int lengthPattern = bestDateTimePattern.length(); - boolean hourWithTwoDigit = false; - char hourFormat = '\0'; - // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save - // the hour format that we found. - for (int i = 0; i < lengthPattern; i++) { - final char c = bestDateTimePattern.charAt(i); - if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { - hourFormat = c; - if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { - hourWithTwoDigit = true; - } - break; - } - } - final String format; - if (hourWithTwoDigit) { - format = "%02d"; - } else { - format = "%d"; - } - if (mIs24HourView) { - // 'k' means 1-24 hour - if (hourFormat == 'k' && value == 0) { - value = 24; - } - } else { - // 'K' means 0-11 hour - value = modulo12(value, hourFormat == 'K'); - } - CharSequence text = String.format(format, value); - mHourView.setText(text); - if (announce) { - tryAnnounceForAccessibility(text, true); - } - } - - private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) { - if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) { - // TODO: Find a better solution, potentially live regions? - mDelegator.announceForAccessibility(text); - mLastAnnouncedText = text; - mLastAnnouncedIsHour = isHour; - } - } - - private static int modulo12(int n, boolean startWithZero) { - int value = n % 12; - if (value == 0 && !startWithZero) { - value = 12; - } - return value; - } - - /** - * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". - * - * See http://unicode.org/cldr/trac/browser/trunk/common/main - * - * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the - * separator as the character which is just after the hour marker in the returned pattern. - */ - private void updateHeaderSeparator() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - (mIs24HourView) ? "Hm" : "hm"); - final String separatorText; - // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats - final char[] hourFormats = {'H', 'h', 'K', 'k'}; - int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats); - if (hIndex == -1) { - // Default case - separatorText = ":"; - } else { - separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); - } - mSeparatorView.setText(separatorText); - } - - static private int lastIndexOfAny(String str, char[] any) { - final int lengthAny = any.length; - if (lengthAny > 0) { - for (int i = str.length() - 1; i >= 0; i--) { - char c = str.charAt(i); - for (int j = 0; j < lengthAny; j++) { - if (c == any[j]) { - return i; - } - } - } - } - return -1; - } - - private void updateHeaderMinute(int value, boolean announceForAccessibility) { - if (value == 60) { - value = 0; - } - final CharSequence text = String.format(mCurrentLocale, "%02d", value); - mMinuteView.setText(text); - if (announceForAccessibility) { - tryAnnounceForAccessibility(text, false); - } - } - - /** - * Show either Hours or Minutes. - */ - private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) { - mRadialTimePickerView.setCurrentItemShowing(index, animateCircle); - - if (index == HOUR_INDEX) { - int hours = mRadialTimePickerView.getCurrentHour(); - if (!mIs24HourView) { - hours = hours % 12; - } - mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours); - if (announce) { - mRadialTimePickerView.announceForAccessibility(mSelectHours); - } - } else { - int minutes = mRadialTimePickerView.getCurrentMinute(); - mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes); - if (announce) { - mRadialTimePickerView.announceForAccessibility(mSelectMinutes); - } - } - - mHourView.setSelected(index == HOUR_INDEX); - mMinuteView.setSelected(index == MINUTE_INDEX); - } - - private void setAmOrPm(int amOrPm) { - updateAmPmLabelStates(amOrPm); - mRadialTimePickerView.setAmOrPm(amOrPm); - } - - /** - * For keyboard mode, processes key events. - * - * @param keyCode the pressed key. - * - * @return true if the key was successfully processed, false otherwise. - */ - private boolean processKeyUp(int keyCode) { - if (keyCode == KeyEvent.KEYCODE_DEL) { - if (mInKbMode) { - if (!mTypedTimes.isEmpty()) { - int deleted = deleteLastTypedKey(); - String deletedKeyStr; - if (deleted == getAmOrPmKeyCode(AM)) { - deletedKeyStr = mAmText; - } else if (deleted == getAmOrPmKeyCode(PM)) { - deletedKeyStr = mPmText; - } else { - deletedKeyStr = String.format("%d", getValFromKeyCode(deleted)); - } - mRadialTimePickerView.announceForAccessibility( - String.format(mDeletedKeyFormat, deletedKeyStr)); - updateDisplay(true); - } - } - } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1 - || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3 - || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5 - || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 - || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 - || (!mIs24HourView && - (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { - if (!mInKbMode) { - if (mRadialTimePickerView == null) { - // Something's wrong, because time picker should definitely not be null. - Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null."); - return true; - } - mTypedTimes.clear(); - tryStartingKbMode(keyCode); - return true; - } - // We're already in keyboard mode. - if (addKeyIfLegal(keyCode)) { - updateDisplay(false); - } - return true; - } - return false; - } - - /** - * Try to start keyboard mode with the specified key. - * - * @param keyCode The key to use as the first press. Keyboard mode will not be started if the - * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting - * key. - */ - private void tryStartingKbMode(int keyCode) { - if (keyCode == -1 || addKeyIfLegal(keyCode)) { - mInKbMode = true; - onValidationChanged(false); - updateDisplay(false); - mRadialTimePickerView.setInputEnabled(false); - } - } - - private boolean addKeyIfLegal(int keyCode) { - // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode, - // we'll need to see if AM/PM have been typed. - if ((mIs24HourView && mTypedTimes.size() == 4) || - (!mIs24HourView && isTypedTimeFullyLegal())) { - return false; - } - - mTypedTimes.add(keyCode); - if (!isTypedTimeLegalSoFar()) { - deleteLastTypedKey(); - return false; - } - - int val = getValFromKeyCode(keyCode); - mRadialTimePickerView.announceForAccessibility(String.format("%d", val)); - // Automatically fill in 0's if AM or PM was legally entered. - if (isTypedTimeFullyLegal()) { - if (!mIs24HourView && mTypedTimes.size() <= 3) { - mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); - mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); - } - onValidationChanged(true); - } - - return true; - } - - /** - * Traverse the tree to see if the keys that have been typed so far are legal as is, - * or may become legal as more keys are typed (excluding backspace). - */ - private boolean isTypedTimeLegalSoFar() { - Node node = mLegalTimesTree; - for (int keyCode : mTypedTimes) { - node = node.canReach(keyCode); - if (node == null) { - return false; - } - } - return true; - } - - /** - * Check if the time that has been typed so far is completely legal, as is. - */ - private boolean isTypedTimeFullyLegal() { - if (mIs24HourView) { - // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note: - // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode. - int[] values = getEnteredTime(null); - return (values[0] >= 0 && values[1] >= 0 && values[1] < 60); - } else { - // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be - // legally added at specific times based on the tree's algorithm. - return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) || - mTypedTimes.contains(getAmOrPmKeyCode(PM))); - } - } - - private int deleteLastTypedKey() { - int deleted = mTypedTimes.remove(mTypedTimes.size() - 1); - if (!isTypedTimeFullyLegal()) { - onValidationChanged(false); - } - return deleted; - } - - /** - * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. - */ - private void finishKbMode() { - mInKbMode = false; - if (!mTypedTimes.isEmpty()) { - int values[] = getEnteredTime(null); - mRadialTimePickerView.setCurrentHour(values[0]); - mRadialTimePickerView.setCurrentMinute(values[1]); - if (!mIs24HourView) { - mRadialTimePickerView.setAmOrPm(values[2]); - } - mTypedTimes.clear(); - } - updateDisplay(false); - mRadialTimePickerView.setInputEnabled(true); - } - - /** - * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is - * empty, either show an empty display (filled with the placeholder text), or update from the - * timepicker's values. - * - * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. - * Otherwise, revert to the timepicker's values. - */ - private void updateDisplay(boolean allowEmptyDisplay) { - if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { - int hour = mRadialTimePickerView.getCurrentHour(); - int minute = mRadialTimePickerView.getCurrentMinute(); - updateHeaderHour(hour, false); - updateHeaderMinute(minute, false); - if (!mIs24HourView) { - updateAmPmLabelStates(hour < 12 ? AM : PM); - } - setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true); - onValidationChanged(true); - } else { - boolean[] enteredZeros = {false, false}; - int[] values = getEnteredTime(enteredZeros); - String hourFormat = enteredZeros[0] ? "%02d" : "%2d"; - String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d"; - String hourStr = (values[0] == -1) ? mDoublePlaceholderText : - String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); - String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : - String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); - mHourView.setText(hourStr); - mHourView.setSelected(false); - mMinuteView.setText(minuteStr); - mMinuteView.setSelected(false); - if (!mIs24HourView) { - updateAmPmLabelStates(values[2]); - } - } - } - - private int getValFromKeyCode(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_0: - return 0; - case KeyEvent.KEYCODE_1: - return 1; - case KeyEvent.KEYCODE_2: - return 2; - case KeyEvent.KEYCODE_3: - return 3; - case KeyEvent.KEYCODE_4: - return 4; - case KeyEvent.KEYCODE_5: - return 5; - case KeyEvent.KEYCODE_6: - return 6; - case KeyEvent.KEYCODE_7: - return 7; - case KeyEvent.KEYCODE_8: - return 8; - case KeyEvent.KEYCODE_9: - return 9; - default: - return -1; - } - } - - /** - * Get the currently-entered time, as integer values of the hours and minutes typed. - * - * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which - * may then be used for the caller to know whether zeros had been explicitly entered as either - * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. - * - * @return A size-3 int array. The first value will be the hours, the second value will be the - * minutes, and the third will be either AM or PM. - */ - private int[] getEnteredTime(boolean[] enteredZeros) { - int amOrPm = -1; - int startIndex = 1; - if (!mIs24HourView && isTypedTimeFullyLegal()) { - int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); - if (keyCode == getAmOrPmKeyCode(AM)) { - amOrPm = AM; - } else if (keyCode == getAmOrPmKeyCode(PM)){ - amOrPm = PM; - } - startIndex = 2; - } - int minute = -1; - int hour = -1; - for (int i = startIndex; i <= mTypedTimes.size(); i++) { - int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i)); - if (i == startIndex) { - minute = val; - } else if (i == startIndex+1) { - minute += 10 * val; - if (enteredZeros != null && val == 0) { - enteredZeros[1] = true; - } - } else if (i == startIndex+2) { - hour = val; - } else if (i == startIndex+3) { - hour += 10 * val; - if (enteredZeros != null && val == 0) { - enteredZeros[0] = true; - } - } - } - - return new int[] { hour, minute, amOrPm }; - } - - /** - * Get the keycode value for AM and PM in the current language. - */ - private int getAmOrPmKeyCode(int amOrPm) { - // Cache the codes. - if (mAmKeyCode == -1 || mPmKeyCode == -1) { - // Find the first character in the AM/PM text that is unique. - KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); - char amChar; - char pmChar; - for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) { - amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i); - pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i); - if (amChar != pmChar) { - KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar}); - // There should be 4 events: a down and up for both AM and PM. - if (events != null && events.length == 4) { - mAmKeyCode = events[0].getKeyCode(); - mPmKeyCode = events[2].getKeyCode(); - } else { - Log.e(TAG, "Unable to find keycodes for AM and PM."); - } - break; - } - } - } - if (amOrPm == AM) { - return mAmKeyCode; - } else if (amOrPm == PM) { - return mPmKeyCode; - } - - return -1; - } - - /** - * Create a tree for deciding what keys can legally be typed. - */ - private void generateLegalTimesTree() { - // Create a quick cache of numbers to their keycodes. - final int k0 = KeyEvent.KEYCODE_0; - final int k1 = KeyEvent.KEYCODE_1; - final int k2 = KeyEvent.KEYCODE_2; - final int k3 = KeyEvent.KEYCODE_3; - final int k4 = KeyEvent.KEYCODE_4; - final int k5 = KeyEvent.KEYCODE_5; - final int k6 = KeyEvent.KEYCODE_6; - final int k7 = KeyEvent.KEYCODE_7; - final int k8 = KeyEvent.KEYCODE_8; - final int k9 = KeyEvent.KEYCODE_9; - - // The root of the tree doesn't contain any numbers. - mLegalTimesTree = new Node(); - if (mIs24HourView) { - // We'll be re-using these nodes, so we'll save them. - Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5); - Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); - // The first digit must be followed by the second digit. - minuteFirstDigit.addChild(minuteSecondDigit); - - // The first digit may be 0-1. - Node firstDigit = new Node(k0, k1); - mLegalTimesTree.addChild(firstDigit); - - // When the first digit is 0-1, the second digit may be 0-5. - Node secondDigit = new Node(k0, k1, k2, k3, k4, k5); - firstDigit.addChild(secondDigit); - // We may now be followed by the first minute digit. E.g. 00:09, 15:58. - secondDigit.addChild(minuteFirstDigit); - - // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9. - Node thirdDigit = new Node(k6, k7, k8, k9); - // The time must now be finished. E.g. 0:55, 1:08. - secondDigit.addChild(thirdDigit); - - // When the first digit is 0-1, the second digit may be 6-9. - secondDigit = new Node(k6, k7, k8, k9); - firstDigit.addChild(secondDigit); - // We must now be followed by the first minute digit. E.g. 06:50, 18:20. - secondDigit.addChild(minuteFirstDigit); - - // The first digit may be 2. - firstDigit = new Node(k2); - mLegalTimesTree.addChild(firstDigit); - - // When the first digit is 2, the second digit may be 0-3. - secondDigit = new Node(k0, k1, k2, k3); - firstDigit.addChild(secondDigit); - // We must now be followed by the first minute digit. E.g. 20:50, 23:09. - secondDigit.addChild(minuteFirstDigit); - - // When the first digit is 2, the second digit may be 4-5. - secondDigit = new Node(k4, k5); - firstDigit.addChild(secondDigit); - // We must now be followd by the last minute digit. E.g. 2:40, 2:53. - secondDigit.addChild(minuteSecondDigit); - - // The first digit may be 3-9. - firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9); - mLegalTimesTree.addChild(firstDigit); - // We must now be followed by the first minute digit. E.g. 3:57, 8:12. - firstDigit.addChild(minuteFirstDigit); - } else { - // We'll need to use the AM/PM node a lot. - // Set up AM and PM to respond to "a" and "p". - Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM)); - - // The first hour digit may be 1. - Node firstDigit = new Node(k1); - mLegalTimesTree.addChild(firstDigit); - // We'll allow quick input of on-the-hour times. E.g. 1pm. - firstDigit.addChild(ampm); - - // When the first digit is 1, the second digit may be 0-2. - Node secondDigit = new Node(k0, k1, k2); - firstDigit.addChild(secondDigit); - // Also for quick input of on-the-hour times. E.g. 10pm, 12am. - secondDigit.addChild(ampm); - - // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5. - Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5); - secondDigit.addChild(thirdDigit); - // The time may be finished now. E.g. 1:02pm, 1:25am. - thirdDigit.addChild(ampm); - - // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5, - // the fourth digit may be 0-9. - Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); - thirdDigit.addChild(fourthDigit); - // The time must be finished now. E.g. 10:49am, 12:40pm. - fourthDigit.addChild(ampm); - - // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9. - thirdDigit = new Node(k6, k7, k8, k9); - secondDigit.addChild(thirdDigit); - // The time must be finished now. E.g. 1:08am, 1:26pm. - thirdDigit.addChild(ampm); - - // When the first digit is 1, the second digit may be 3-5. - secondDigit = new Node(k3, k4, k5); - firstDigit.addChild(secondDigit); - - // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9. - thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); - secondDigit.addChild(thirdDigit); - // The time must be finished now. E.g. 1:39am, 1:50pm. - thirdDigit.addChild(ampm); - - // The hour digit may be 2-9. - firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9); - mLegalTimesTree.addChild(firstDigit); - // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm. - firstDigit.addChild(ampm); - - // When the first digit is 2-9, the second digit may be 0-5. - secondDigit = new Node(k0, k1, k2, k3, k4, k5); - firstDigit.addChild(secondDigit); - - // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9. - thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); - secondDigit.addChild(thirdDigit); - // The time must be finished now. E.g. 2:57am, 9:30pm. - thirdDigit.addChild(ampm); - } - } - - /** - * Simple node class to be used for traversal to check for legal times. - * mLegalKeys represents the keys that can be typed to get to the node. - * mChildren are the children that can be reached from this node. - */ - private class Node { - private int[] mLegalKeys; - private ArrayList<Node> mChildren; - - public Node(int... legalKeys) { - mLegalKeys = legalKeys; - mChildren = new ArrayList<Node>(); - } - - public void addChild(Node child) { - mChildren.add(child); - } - - public boolean containsKey(int key) { - for (int i = 0; i < mLegalKeys.length; i++) { - if (mLegalKeys[i] == key) { - return true; - } - } - return false; - } - - public Node canReach(int key) { - if (mChildren == null) { - return null; - } - for (Node child : mChildren) { - if (child.containsKey(key)) { - return child; - } - } - return null; - } - } - - private final View.OnClickListener mClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - - final int amOrPm; - switch (v.getId()) { - case R.id.am_label: - setAmOrPm(AM); - break; - case R.id.pm_label: - setAmOrPm(PM); - break; - case R.id.hours: - setCurrentItemShowing(HOUR_INDEX, true, true); - break; - case R.id.minutes: - setCurrentItemShowing(MINUTE_INDEX, true, true); - break; - default: - // Failed to handle this click, don't vibrate. - return; - } - - tryVibrate(); - } - }; - - private final View.OnKeyListener mKeyListener = new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_UP) { - return processKeyUp(keyCode); - } - return false; - } - }; - - private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) { - finishKbMode(); - - if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, - mRadialTimePickerView.getCurrentHour(), - mRadialTimePickerView.getCurrentMinute()); - } - } - } - }; } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 91e5330..b9a85e5 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -19,8 +19,6 @@ package com.android.internal.widget; import android.animation.LayoutTransition; import android.app.ActionBar; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; @@ -29,9 +27,7 @@ import android.os.Parcelable; import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.CollapsibleActionView; -import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -111,10 +107,10 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar { private int mProgressBarPadding; private int mItemPadding; - private int mTitleStyleRes; - private int mSubtitleStyleRes; - private int mProgressStyle; - private int mIndeterminateProgressStyle; + private final int mTitleStyleRes; + private final int mSubtitleStyleRes; + private final int mProgressStyle; + private final int mIndeterminateProgressStyle; private boolean mUserTitle; private boolean mIncludeTabs; diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 97b1634..99b1bae 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -17,6 +17,7 @@ package com.android.internal.widget; import android.animation.TimeInterpolator; +import android.app.Activity; import android.content.Context; import android.util.AttributeSet; import android.util.Log; @@ -102,6 +103,13 @@ public class SwipeDismissLayout extends FrameLayout { android.R.integer.config_shortAnimTime); mCancelInterpolator = new DecelerateInterpolator(1.5f); mDismissInterpolator = new AccelerateInterpolator(1.5f); + // SwipeDismissLayout assumes that the host Activity is translucent + // and temporarily disables translucency when it is fully visible. + // As soon as the user starts swiping, we will re-enable + // translucency. + if (context instanceof Activity) { + ((Activity) context).convertFromTranslucent(); + } } public void setOnDismissedListener(OnDismissedListener listener) { @@ -197,6 +205,9 @@ public class SwipeDismissLayout extends FrameLayout { mLastX = ev.getRawX(); updateSwiping(ev); if (mSwiping) { + if (getContext() instanceof Activity) { + ((Activity) getContext()).convertToTranslucent(null, null); + } setProgress(ev.getRawX() - mDownX); break; } @@ -218,6 +229,9 @@ public class SwipeDismissLayout extends FrameLayout { } protected void cancel() { + if (getContext() instanceof Activity) { + ((Activity) getContext()).convertFromTranslucent(); + } if (mProgressListener != null) { mProgressListener.onSwipeCancelled(this); } diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index e0abc24..a578b5d 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -315,7 +315,8 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } SkBitmap decodingBitmap; - if (!decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)) { + if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode) + != SkImageDecoder::kSuccess) { return nullObjectReturn("decoder->decode returned false"); } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index b3d9890..a0b2ca8 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -374,6 +374,7 @@ static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfaceP ContextFactory factory; RenderProxy* proxy = new RenderProxy(false, rootNode, &factory); proxy->loadSystemProperties(); + proxy->setSwapBehavior(kSwap_discardBuffer); proxy->initialize(surface); // Shadows can't be used via this interface, so just set the light source // to all 0s. (and width & height are unused, TODO remove them) diff --git a/core/res/res/values-mcc310-mnc120/config.xml b/core/res/res/values-mcc310-mnc120/config.xml index 24e55b1..774732d 100644 --- a/core/res/res/values-mcc310-mnc120/config.xml +++ b/core/res/res/values-mcc310-mnc120/config.xml @@ -27,4 +27,8 @@ <!-- Sprint need a 70 ms delay for 3way call --> <integer name="config_cdma_3waycall_flash_delay">70</integer> + + <!-- If this value is true, The mms content-disposition field is supported correctly. + If false, Content-disposition fragments are ignored --> + <bool name="config_mms_content_disposition_support">false</bool> </resources> diff --git a/core/res/res/values-mcc310-mnc160/config.xml b/core/res/res/values-mcc310-mnc160/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc160/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc200/config.xml b/core/res/res/values-mcc310-mnc200/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc200/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc210/config.xml b/core/res/res/values-mcc310-mnc210/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc210/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc220/config.xml b/core/res/res/values-mcc310-mnc220/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc220/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc230/config.xml b/core/res/res/values-mcc310-mnc230/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc230/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc240/config.xml b/core/res/res/values-mcc310-mnc240/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc240/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc250/config.xml b/core/res/res/values-mcc310-mnc250/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc250/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc260/config.xml b/core/res/res/values-mcc310-mnc260/config.xml index 28cd695..6bfc3d1 100644 --- a/core/res/res/values-mcc310-mnc260/config.xml +++ b/core/res/res/values-mcc310-mnc260/config.xml @@ -25,8 +25,8 @@ --> <integer name="config_mobile_mtu">1440</integer> - <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + <!-- Flag specifying whether VoLTE should be available for carrier: independent of carrier provisioning. If false: hard disabled. If true: then depends on carrier provisioning, availability etc --> - <bool name="config_carrier_volte_vt_available">true</bool> + <bool name="config_carrier_volte_available">true</bool> </resources> diff --git a/core/res/res/values-mcc310-mnc270/config.xml b/core/res/res/values-mcc310-mnc270/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc270/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc300/config.xml b/core/res/res/values-mcc310-mnc300/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc300/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc310/config.xml b/core/res/res/values-mcc310-mnc310/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc310/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc490/config.xml b/core/res/res/values-mcc310-mnc490/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc490/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc530/config.xml b/core/res/res/values-mcc310-mnc530/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc530/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc580/config.xml b/core/res/res/values-mcc310-mnc580/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc580/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc590/config.xml b/core/res/res/values-mcc310-mnc590/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc590/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc640/config.xml b/core/res/res/values-mcc310-mnc640/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc640/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc660/config.xml b/core/res/res/values-mcc310-mnc660/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc660/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc310-mnc800/config.xml b/core/res/res/values-mcc310-mnc800/config.xml new file mode 100644 index 0000000..28cd695 --- /dev/null +++ b/core/res/res/values-mcc310-mnc800/config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Configure mobile network MTU. Carrier specific value is set here. + --> + <integer name="config_mobile_mtu">1440</integer> + + <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_vt_available">true</bool> +</resources> diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml index 820cc2e..d0a57b3 100644 --- a/core/res/res/values-mcc311-mnc480/config.xml +++ b/core/res/res/values-mcc311-mnc480/config.xml @@ -38,10 +38,10 @@ be disabled) but individual Features can be disabled using ImsConfig.setFeatureValue() --> <bool name="imsServiceAllowTurnOff">false</bool> - <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + <!-- Flag specifying whether VoLTE should be available for carrier: independent of carrier provisioning. If false: hard disabled. If true: then depends on carrier provisioning, availability etc --> - <bool name="config_carrier_volte_vt_available">true</bool> + <bool name="config_carrier_volte_available">true</bool> <bool name="config_auto_attach_data_on_creation">false</bool> <!-- service number convert map in roaming network. --> diff --git a/core/res/res/values-mcc530-mnc05/config.xml b/core/res/res/values-mcc530-mnc05/config.xml new file mode 100644 index 0000000..893afe5 --- /dev/null +++ b/core/res/res/values-mcc530-mnc05/config.xml @@ -0,0 +1,26 @@ +<?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 my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- If this value is true, The mms content-disposition field is supported correctly. + If false, Content-disposition fragments are ignored --> + <bool name="config_mms_content_disposition_support">false</bool> +</resources> diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 5773b94..46ec838 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -22,7 +22,7 @@ <color name="background_floating_material_light">#ffeeeeee</color> <color name="primary_material_dark">#ff212121</color> - <color name="primary_material_light">#ffbdbdbd</color> + <color name="primary_material_light">#ffe0e0e0</color> <color name="primary_dark_material_dark">#ff000000</color> <color name="primary_dark_material_light">#ff757575</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 59b4e9c..e50eb0c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -453,6 +453,26 @@ <!-- If this is true, key chords can be used to take a screenshot on the device. --> <bool name="config_enableScreenshotChord">true</bool> + <!-- If this is true, allow wake from theater mode when plugged in or unplugged. --> + <bool name="config_allowTheaterModeWakeFromUnplug">false</bool> + <!-- If this is true, allow wake from theater mode from gesture. --> + <bool name="config_allowTheaterModeWakeFromGesture">false</bool> + <!-- If this is true, allow wake from theater mode from camera lens cover is switched. --> + <bool name="config_allowTheaterModeWakeFromCameraLens">false</bool> + <!-- If this is true, allow wake from theater mode from power key press. --> + <bool name="config_allowTheaterModeWakeFromPowerKey">true</bool> + <!-- If this is true, allow wake from theater mode from regular key press. Setting this value to + true implies config_allowTheaterModeWakeFromPowerKey is also true--> + <bool name="config_allowTheaterModeWakeFromKey">false</bool> + <!-- If this is true, allow wake from theater mode from motion. --> + <bool name="config_allowTheaterModeWakeFromMotion">false</bool> + <!-- If this is true, allow wake from theater mode from lid switch. --> + <bool name="config_allowTheaterModeWakeFromLidSwitch">false</bool> + <!-- If this is true, allow wake from theater mode when docked. --> + <bool name="config_allowTheaterModeWakeFromDock">false</bool> + <!-- If this is true, allow wake from theater mode from window layout flag. --> + <bool name="config_allowTheaterModeWakeFromWindowLayout">false</bool> + <!-- Auto-rotation behavior --> <!-- If true, enables auto-rotation features using the accelerometer. @@ -1767,13 +1787,21 @@ be disabled) but individual Features can be disabled using ImsConfig.setFeatureValue() --> <bool name="imsServiceAllowTurnOff">true</bool> - <!-- Flag specifying whether VoLTE & VT is availasble on device --> - <bool name="config_device_volte_vt_available">false</bool> + <!-- Flag specifying whether VoLTE is available on device --> + <bool name="config_device_volte_available">false</bool> + + <!-- Flag specifying whether VoLTE should be available for carrier: independent of + carrier provisioning. If false: hard disabled. If true: then depends on carrier + provisioning, availability etc --> + <bool name="config_carrier_volte_available">false</bool> + + <!-- Flag specifying whether VT is available on device --> + <bool name="config_device_vt_available">false</bool> - <!-- Flag specifying whether VoLTE & VT should be available for carrier: independent of + <!-- Flag specifying whether VT should be available for carrier: independent of carrier provisioning. If false: hard disabled. If true: then depends on carrier provisioning, availability etc --> - <bool name="config_carrier_volte_vt_available">false</bool> + <bool name="config_carrier_vt_available">false</bool> <bool name="config_networkSamplingWakesDevice">true</bool> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3d30792..56cf56d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1449,9 +1449,8 @@ <string name="permlab_bodySensors">body sensors (like heart rate monitors) </string> <!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensors" product="default">Allows the app to - access data from sensors you use to measure what’s happening inside your - body, such as heart rate.</string> + <string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors + that monitor your physical condition, such as your heart rate.</string> <!-- Title of the read social stream permission, listed so the user can decide whether to allow the application to read information from the user's social stream. [CHAR LIMIT=30] --> <string name="permlab_readSocialStream" product="default">read your social stream</string> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index f9fca00..6e03b3d 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -320,50 +320,48 @@ please see styles_device_defaults.xml. <item name="textColor">?attr/textColorPrimaryDisableOnly</item> </style> - <style name="TextAppearance.Material.Widget.ActionMode"/> - <style name="TextAppearance.Material.Widget.ActionMode.Title" - parent="TextAppearance.Material.Title"> - <item name="textSize">@dimen/text_size_title_material_toolbar</item> - </style> - <style name="TextAppearance.Material.Widget.ActionMode.Title.Inverse" - parent="TextAppearance.Material.Title.Inverse"> - <item name="textSize">@dimen/text_size_title_material_toolbar</item> - </style> - <style name="TextAppearance.Material.Widget.ActionMode.Subtitle" - parent="TextAppearance.Material.Subhead"> - <item name="textSize">@dimen/text_size_subtitle_material_toolbar</item> - </style> - <style name="TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse" - parent="TextAppearance.Material.Subhead.Inverse"> - <item name="textSize">@dimen/text_size_subtitle_material_toolbar</item> - </style> <style name="TextAppearance.Material.Widget.ActionBar.Title" parent="TextAppearance.Material.Title"> <item name="textSize">@dimen/text_size_title_material_toolbar</item> + <item name="textColor">?attr/textColorPrimary</item> </style> <style name="TextAppearance.Material.Widget.ActionBar.Title.Inverse" parent="TextAppearance.Material.Title.Inverse"> <item name="textSize">@dimen/text_size_title_material_toolbar</item> + <item name="textColor">?attr/textColorPrimaryInverse</item> </style> <style name="TextAppearance.Material.Widget.ActionBar.Subtitle" parent="TextAppearance.Material.Subhead"> <item name="textSize">@dimen/text_size_subtitle_material_toolbar</item> + <item name="textColor">?attr/textColorSecondary</item> </style> <style name="TextAppearance.Material.Widget.ActionBar.Subtitle.Inverse" parent="TextAppearance.Material.Subhead.Inverse"> <item name="textSize">@dimen/text_size_subtitle_material_toolbar</item> + <item name="textColor">?attr/textColorSecondaryInverse</item> </style> - <style name="TextAppearance.Material.Widget.ActionBar.Menu" parent="TextAppearance.Material.Menu"> + <style name="TextAppearance.Material.Widget.ActionBar.Menu" + parent="TextAppearance.Material.Menu"> <item name="textColor">?attr/actionMenuTextColor</item> <item name="textAllCaps">@bool/config_actionMenuItemAllCaps</item> </style> - - <style name="TextAppearance.Material.Widget.ActionBar.Menu.Inverse" parent="TextAppearance.Material.Menu.Inverse"> + <style name="TextAppearance.Material.Widget.ActionBar.Menu.Inverse" + parent="TextAppearance.Material.Menu.Inverse"> <item name="textColor">?attr/actionMenuTextColor</item> <item name="textAllCaps">@bool/config_actionMenuItemAllCaps</item> </style> + <style name="TextAppearance.Material.Widget.ActionMode"/> + <style name="TextAppearance.Material.Widget.ActionMode.Title" + parent="TextAppearance.Material.Widget.ActionBar.Title" /> + <style name="TextAppearance.Material.Widget.ActionMode.Title.Inverse" + parent="TextAppearance.Material.Widget.ActionBar.Title.Inverse" /> + <style name="TextAppearance.Material.Widget.ActionMode.Subtitle" + parent="TextAppearance.Material.Widget.ActionBar.Subtitle" /> + <style name="TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse" + parent="TextAppearance.Material.Widget.ActionBar.Subtitle.Inverse" /> + <style name="TextAppearance.Material.Widget.Toolbar.Title" parent="TextAppearance.Material.Widget.ActionBar.Title" /> <style name="TextAppearance.Material.Widget.Toolbar.Subtitle" diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 71f0642..a11fdbc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1571,6 +1571,15 @@ <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> <java-symbol type="bool" name="config_sf_limitedAlpha" /> <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromUnplug" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromGesture" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromCameraLens" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromPowerKey" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromKey" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromMotion" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromLidSwitch" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" /> + <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" /> <java-symbol type="bool" name="config_wifi_background_scan_support" /> <java-symbol type="bool" name="config_wifi_dual_band_support" /> <java-symbol type="bool" name="config_wimaxEnabled" /> @@ -2031,8 +2040,10 @@ <java-symbol type="attr" name="preferenceFragmentStyle" /> <java-symbol type="bool" name="skipHoldBeforeMerge" /> <java-symbol type="bool" name="imsServiceAllowTurnOff" /> - <java-symbol type="bool" name="config_device_volte_vt_available" /> - <java-symbol type="bool" name="config_carrier_volte_vt_available" /> + <java-symbol type="bool" name="config_device_volte_available" /> + <java-symbol type="bool" name="config_carrier_volte_available" /> + <java-symbol type="bool" name="config_device_vt_available" /> + <java-symbol type="bool" name="config_carrier_vt_available" /> <java-symbol type="bool" name="useImsAlwaysForEmergencyCall" /> <java-symbol type="attr" name="touchscreenBlocksFocus" /> <java-symbol type="layout" name="resolver_list_with_default" /> diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml index 7e0467b..f1bc5da 100644 --- a/core/res/res/values/themes_micro.xml +++ b/core/res/res/values/themes_micro.xml @@ -24,6 +24,8 @@ <item name="windowBackground">@color/black</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> + <!-- We need the windows to be translucent for SwipeToDismiss layout + to work properly. --> <item name="windowIsTranslucent">true</item> <item name="windowSwipeToDismiss">true</item> </style> @@ -38,6 +40,8 @@ <item name="windowBackground">@color/white</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> + <!-- We need the windows to be translucent for SwipeToDismiss layout + to work properly. --> <item name="windowIsTranslucent">true</item> <item name="windowSwipeToDismiss">true</item> </style> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java index 80d5668..64fed7f 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java @@ -49,7 +49,8 @@ import java.util.List; */ public class ConnectivityManagerTestBase extends InstrumentationTestCase { - private static final String PING_IP_ADDR = "8.8.8.8"; + private static final String[] PING_HOST_LIST = { + "www.google.com", "www.yahoo.com", "www.bing.com", "www.facebook.com", "www.ask.com"}; protected static final int WAIT_FOR_SCAN_RESULT = 10 * 1000; //10 seconds protected static final int WIFI_SCAN_TIMEOUT = 50 * 1000; // 50 seconds @@ -281,22 +282,14 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { } /** - * @param pingServerList a list of servers that can be used for ping test, can be null * @return true if the ping test is successful, false otherwise. */ - protected boolean pingTest(String[] pingServerList) { - String[] hostList = {"www.google.com", "www.yahoo.com", - "www.bing.com", "www.facebook.com", "www.ask.com"}; - if (pingServerList != null) { - hostList = pingServerList; - } - + protected boolean pingTest() { long startTime = System.currentTimeMillis(); while ((System.currentTimeMillis() - startTime) < PING_TIMER) { try { // assume the chance that all servers are down is very small - for (int i = 0; i < hostList.length; i++ ) { - String host = hostList[i]; + for (String host : PING_HOST_LIST) { logv("Start ping test, ping " + host); Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + host); int status = p.waitFor(); @@ -312,6 +305,7 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { } catch (InterruptedException e) { logv("Ping test Fail: InterruptedException"); } + SystemClock.sleep(SHORT_TIMEOUT); } // ping test timeout return false; @@ -458,14 +452,7 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { // use ping request against Google public DNS to verify connectivity protected boolean checkNetworkConnectivity() { assertTrue("no active network connection", waitForActiveNetworkConnection(LONG_TIMEOUT)); - try { - Process proc = Runtime.getRuntime().exec(new String[]{ - "/system/bin/ping", "-W", "30", "-c", "1", PING_IP_ADDR}); - return proc.waitFor() == 0; - } catch (InterruptedException | IOException e) { - Log.e(mLogTag, "Ping failed", e); - } - return false; + return pingTest(); } @Override diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java index d5051df..2d291ff 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java @@ -72,16 +72,6 @@ public class ConnectivityManagerMobileTest extends ConnectivityManagerTestBase super.tearDown(); } - // help function to verify 3G connection - public void verifyCellularConnection() { - NetworkInfo extraNetInfo = mCm.getActiveNetworkInfo(); - assertEquals("network type is not MOBILE", ConnectivityManager.TYPE_MOBILE, - extraNetInfo.getType()); - assertTrue("not connected to cellular network", extraNetInfo.isConnected()); - } - - - // Test case 1: Test enabling Wifi without associating with any AP, no broadcast on network // event should be expected. @LargeTest @@ -336,4 +326,12 @@ public class ConnectivityManagerMobileTest extends ConnectivityManagerTestBase assertTrue("wifi state not disabled", waitForWifiState( WifiManager.WIFI_STATE_DISABLED, LONG_TIMEOUT)); } + + // help function to verify 3G connection + private void verifyCellularConnection() { + NetworkInfo extraNetInfo = mCm.getActiveNetworkInfo(); + assertEquals("network type is not MOBILE", ConnectivityManager.TYPE_MOBILE, + extraNetInfo.getType()); + assertTrue("not connected to cellular network", extraNetInfo.isConnected()); + } } diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java index 41f01e6..de934b9 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java @@ -112,7 +112,7 @@ public class WifiApStress extends ConnectivityManagerTestBase { } catch (Exception e) { // ignore } - assertTrue("no uplink data connection after Wi-Fi tethering", pingTest(null)); + assertTrue("no uplink data connection after Wi-Fi tethering", pingTest()); // disable wifi hotspot assertTrue("failed to disable wifi hotspot", mWifiManager.setWifiApEnabled(config, false)); diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java index fbd4669..f3d5c87 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java @@ -216,7 +216,7 @@ public class WifiStressTest extends ConnectivityManagerTestBase { assertTrue("wifi not connected", waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED, WIFI_CONNECTION_TIMEOUT)); // Run ping test to verify the data connection - assertTrue("Wi-Fi is connected, but no data connection.", pingTest(null)); + assertTrue("Wi-Fi is connected, but no data connection.", pingTest()); long i, sum = 0, avgReconnectTime = 0; for (i = 1; i <= mReconnectIterations; i++) { @@ -264,7 +264,7 @@ public class WifiStressTest extends ConnectivityManagerTestBase { } else { assertEquals("mobile not connected", State.CONNECTED, mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState()); - assertTrue("no connectivity over mobile", pingTest(null)); + assertTrue("no connectivity over mobile", pingTest()); } // Turn screen on again @@ -281,7 +281,7 @@ public class WifiStressTest extends ConnectivityManagerTestBase { avgReconnectTime = sum / i; logv("average reconnection time is: " + avgReconnectTime); - assertTrue("Reconnect to Wi-Fi network, but no data connection.", pingTest(null)); + assertTrue("Reconnect to Wi-Fi network, but no data connection.", pingTest()); } Bundle result = new Bundle(); result.putLong("actual-iterations", i - 1); diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index b524177..226717e 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1252,6 +1252,13 @@ </intent-filter> </activity> + <activity android:name="android.content.res.ResourceCacheActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + </application> <instrumentation android:name="android.test.InstrumentationTestRunner" diff --git a/core/tests/coretests/res/anim/reset_state_anim.xml b/core/tests/coretests/res/anim/reset_state_anim.xml index 918d0a3..4bbbe62 100644 --- a/core/tests/coretests/res/anim/reset_state_anim.xml +++ b/core/tests/coretests/res/anim/reset_state_anim.xml @@ -1,4 +1,18 @@ <?xml version="1.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. +--> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/> <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/> diff --git a/core/tests/coretests/res/anim/test_animator.xml b/core/tests/coretests/res/anim/test_animator.xml new file mode 100644 index 0000000..49afc3f --- /dev/null +++ b/core/tests/coretests/res/anim/test_animator.xml @@ -0,0 +1,22 @@ +<?xml version="1.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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- if you change this, you should also change AnimatorInflaterTest#testLoadAnimator--> + <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="1" android:valueType="floatType"/> + <objectAnimator android:propertyName="left" android:duration="100" android:valueTo="2" android:valueType="intType"/> +</set>
\ No newline at end of file diff --git a/core/tests/coretests/res/anim/test_state_anim.xml b/core/tests/coretests/res/anim/test_state_anim.xml index 9e08f68..b6a4822 100644 --- a/core/tests/coretests/res/anim/test_state_anim.xml +++ b/core/tests/coretests/res/anim/test_state_anim.xml @@ -1,4 +1,18 @@ <?xml version="1.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. +--> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <set> diff --git a/core/tests/coretests/res/values-land/dimens.xml b/core/tests/coretests/res/values-land/dimens.xml new file mode 100644 index 0000000..1ee9f1d --- /dev/null +++ b/core/tests/coretests/res/values-land/dimens.xml @@ -0,0 +1,18 @@ +<?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. +--> +<resources> + <dimen name="resource_cache_test_orientation_dependent">3dp</dimen> +</resources>
\ No newline at end of file diff --git a/core/tests/coretests/res/values/dimens.xml b/core/tests/coretests/res/values/dimens.xml new file mode 100644 index 0000000..00fc414 --- /dev/null +++ b/core/tests/coretests/res/values/dimens.xml @@ -0,0 +1,19 @@ +<?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. +--> +<resources> + <dimen name="resource_cache_test_generic">10dp</dimen> + <dimen name="resource_cache_test_orientation_dependent">20dp</dimen> +</resources>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java new file mode 100644 index 0000000..3c81853 --- /dev/null +++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java @@ -0,0 +1,61 @@ +/* +* 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.animation; + +import android.test.ActivityInstrumentationTestCase2; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.android.frameworks.coretests.R; + +public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { + Set<Integer> identityHashes = new HashSet<Integer>(); + + public AnimatorInflaterTest() { + super(BasicAnimatorActivity.class); + } + + private void assertUnique(Object object) { + assertUnique(object, ""); + } + + private void assertUnique(Object object, String msg) { + final int code = System.identityHashCode(object); + assertTrue("object should be unique " + msg + ", obj:" + object, identityHashes.add(code)); + + } + + public void testLoadStateListAnimator() { + StateListAnimator sla1 = AnimatorInflater.loadStateListAnimator(getActivity(), + R.anim.test_state_anim); + sla1.setTarget(getActivity().mAnimatingButton); + StateListAnimator sla2 = AnimatorInflater.loadStateListAnimator(getActivity(), + R.anim.test_state_anim); + assertNull(sla2.getTarget()); + for (StateListAnimator sla : new StateListAnimator[]{sla1, sla2}) { + assertUnique(sla); + assertEquals(3, sla.getTuples().size()); + for (StateListAnimator.Tuple tuple : sla.getTuples()) { + assertUnique(tuple); + assertUnique(tuple.getAnimator()); + } + } + } + +} diff --git a/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java b/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java index 93808d9..6bcf8fc 100644 --- a/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java +++ b/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java @@ -19,11 +19,14 @@ import com.android.frameworks.coretests.R; import android.app.Activity; import android.os.Bundle; +import android.widget.Button; public class BasicAnimatorActivity extends Activity { + public Button mAnimatingButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.animator_basic); + mAnimatingButton = (Button) findViewById(R.id.animatingButton); } } diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java new file mode 100644 index 0000000..e9fd5fb --- /dev/null +++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java @@ -0,0 +1,224 @@ +/* + * 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.res; + +import android.test.ActivityInstrumentationTestCase2; +import android.util.TypedValue; + +import com.android.frameworks.coretests.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ConfigurationBoundResourceCacheTest + extends ActivityInstrumentationTestCase2<ResourceCacheActivity> { + + ConfigurationBoundResourceCache<Float> mCache; + + Method mCalcConfigChanges; + + public ConfigurationBoundResourceCacheTest() { + super(ResourceCacheActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mCache = new ConfigurationBoundResourceCache<Float>(getActivity().getResources()); + } + + public void testGetEmpty() { + assertNull(mCache.get(-1, null)); + } + + public void testSetGet() { + mCache.put(1, null, new DummyFloatConstantState(5f)); + assertEquals(5f, mCache.get(1, null)); + assertNotSame(5f, mCache.get(1, null)); + assertEquals(null, mCache.get(1, getActivity().getTheme())); + } + + public void testSetGetThemed() { + mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); + assertEquals(null, mCache.get(1, null)); + assertEquals(5f, mCache.get(1, getActivity().getTheme())); + assertNotSame(5f, mCache.get(1, getActivity().getTheme())); + } + + public void testMultiThreadPutGet() { + mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); + mCache.put(1, null, new DummyFloatConstantState(10f)); + assertEquals(10f, mCache.get(1, null)); + assertNotSame(10f, mCache.get(1, null)); + assertEquals(5f, mCache.get(1, getActivity().getTheme())); + assertNotSame(5f, mCache.get(1, getActivity().getTheme())); + } + + public void testVoidConfigChange() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + TypedValue staticValue = new TypedValue(); + long key = 3L; + final Resources res = getActivity().getResources(); + res.getValue(R.dimen.resource_cache_test_generic, staticValue, true); + float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics()); + mCache.put(key, getActivity().getTheme(), + new DummyFloatConstantState(staticDim, staticValue.changingConfigurations)); + final Configuration cfg = res.getConfiguration(); + Configuration newCnf = new Configuration(cfg); + newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? + Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + int changes = calcConfigChanges(res, newCnf); + assertEquals(staticDim, mCache.get(key, getActivity().getTheme())); + mCache.onConfigurationChange(changes); + assertEquals(staticDim, mCache.get(key, getActivity().getTheme())); + } + + public void testEffectiveConfigChange() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + TypedValue changingValue = new TypedValue(); + long key = 4L; + final Resources res = getActivity().getResources(); + res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValue, true); + float changingDim = TypedValue.complexToDimension(changingValue.data, + res.getDisplayMetrics()); + mCache.put(key, getActivity().getTheme(), + new DummyFloatConstantState(changingDim, changingValue.changingConfigurations)); + + final Configuration cfg = res.getConfiguration(); + Configuration newCnf = new Configuration(cfg); + newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? + Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + int changes = calcConfigChanges(res, newCnf); + assertEquals(changingDim, mCache.get(key, getActivity().getTheme())); + mCache.onConfigurationChange(changes); + assertNull(mCache.get(key, getActivity().getTheme())); + } + + public void testConfigChangeMultipleResources() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + TypedValue staticValue = new TypedValue(); + TypedValue changingValue = new TypedValue(); + final Resources res = getActivity().getResources(); + res.getValue(R.dimen.resource_cache_test_generic, staticValue, true); + res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValue, true); + float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics()); + float changingDim = TypedValue.complexToDimension(changingValue.data, + res.getDisplayMetrics()); + mCache.put(R.dimen.resource_cache_test_generic, getActivity().getTheme(), + new DummyFloatConstantState(staticDim, staticValue.changingConfigurations)); + mCache.put(R.dimen.resource_cache_test_orientation_dependent, getActivity().getTheme(), + new DummyFloatConstantState(changingDim, changingValue.changingConfigurations)); + final Configuration cfg = res.getConfiguration(); + Configuration newCnf = new Configuration(cfg); + newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? + Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + int changes = calcConfigChanges(res, newCnf); + assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, + getActivity().getTheme())); + assertEquals(changingDim, mCache.get(R.dimen.resource_cache_test_orientation_dependent, + getActivity().getTheme())); + mCache.onConfigurationChange(changes); + assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, + getActivity().getTheme())); + assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, + getActivity().getTheme())); + } + + public void testConfigChangeMultipleThemes() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + TypedValue[] staticValues = new TypedValue[]{new TypedValue(), new TypedValue()}; + TypedValue[] changingValues = new TypedValue[]{new TypedValue(), new TypedValue()}; + float staticDim = 0; + float changingDim = 0; + final Resources res = getActivity().getResources(); + for (int i = 0; i < 2; i++) { + res.getValue(R.dimen.resource_cache_test_generic, staticValues[i], true); + staticDim = TypedValue + .complexToDimension(staticValues[i].data, res.getDisplayMetrics()); + + res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValues[i], + true); + changingDim = TypedValue.complexToDimension(changingValues[i].data, + res.getDisplayMetrics()); + final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; + mCache.put(R.dimen.resource_cache_test_generic, theme, + new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations)); + mCache.put(R.dimen.resource_cache_test_orientation_dependent, theme, + new DummyFloatConstantState(changingDim, + changingValues[i].changingConfigurations)); + } + final Configuration cfg = res.getConfiguration(); + Configuration newCnf = new Configuration(cfg); + newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? + Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + int changes = calcConfigChanges(res, newCnf); + for (int i = 0; i < 2; i++) { + final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; + assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme)); + assertEquals(changingDim, + mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme)); + } + mCache.onConfigurationChange(changes); + for (int i = 0; i < 2; i++) { + final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; + assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme)); + assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme)); + } + } + + private int calcConfigChanges(Resources resources, Configuration configuration) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + if (mCalcConfigChanges == null) { + mCalcConfigChanges = Resources.class.getDeclaredMethod("calcConfigChanges", + Configuration.class); + mCalcConfigChanges.setAccessible(true); + } + return (Integer) mCalcConfigChanges.invoke(resources, configuration); + + } + + static class DummyFloatConstantState extends + ConstantState<Float> { + + final Float mObj; + + int mChangingConf = 0; + + DummyFloatConstantState(Float obj) { + mObj = obj; + } + + DummyFloatConstantState(Float obj, int changingConf) { + mObj = obj; + mChangingConf = changingConf; + } + + @Override + public int getChangingConfigurations() { + return mChangingConf; + } + + @Override + public Float newInstance() { + return new Float(mObj); + } + } +} diff --git a/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java b/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java new file mode 100644 index 0000000..f37e549 --- /dev/null +++ b/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java @@ -0,0 +1,37 @@ +/* +* 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.res; + +import android.annotation.Nullable; +import android.app.Activity; +import android.os.Bundle; + +import java.lang.ref.WeakReference; + +public class ResourceCacheActivity extends Activity { + static WeakReference<ResourceCacheActivity> lastCreatedInstance; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + lastCreatedInstance = new WeakReference<ResourceCacheActivity>(this); + } + + public static ResourceCacheActivity getLastCreatedInstance() { + return lastCreatedInstance == null ? null : lastCreatedInstance.get(); + } +} diff --git a/docs/html/about/versions/android-5.0.jd b/docs/html/about/versions/android-5.0.jd index f8d8ab6..a438420 100644 --- a/docs/html/about/versions/android-5.0.jd +++ b/docs/html/about/versions/android-5.0.jd @@ -23,6 +23,7 @@ sdk.platform.apiLevel=21 <li><a href="#BehaviorGetRecentTasks">If your app uses getRecentTasks()...</a></li> <li><a href="#64BitSupport">If you are using the Android Native Development Kit (NDK)...</a></li> <li><a href="#BindService">If your app binds to a Service...</a></li> +<li><a href="#BehaviorWebView">If your app uses a WebView...</a></li> </ol> </li> <li><a href="#UI">User Interface</a> @@ -234,8 +235,8 @@ the system can present notifications correctly in vibration.</p> <p>Setting the device to -{@link android.media.AudioManager#RINGER_MODE_SILENT RINGER_MODE_SILENT} now -causes the device to enter the new priority mode. The device leaves priority +{@link android.media.AudioManager#RINGER_MODE_SILENT RINGER_MODE_SILENT} causes +the device to enter the new priority mode. The device leaves priority mode if you set it to {@link android.media.AudioManager#RINGER_MODE_NORMAL RINGER_MODE_NORMAL} or {@link android.media.AudioManager#RINGER_MODE_NORMAL RINGER_MODE_VIBRATE}.</p> @@ -366,6 +367,31 @@ and throws an exception if given an implicit intent. To ensure your app is secure, use an explicit intent when starting or binding your {@link android.app.Service}, and do not declare intent filters for the service.</p> +<h3 id="BehaviorWebView">If your app uses WebView...</h3> + +<p>Android 5.0 changes the default behavior for your app.</p> +<ul> +<li><strong>If your app targets API level 21 or higher:</strong> + <ul> + <li>The system + blocks <a href="https://developer.mozilla.org/en-US/docs/Security/MixedContent" + class="external-link">mixed content</a> and third party cookies by default. To allow mixed + content and third party cookies, use the + {@link android.webkit.WebSettings#setMixedContentMode(int) setMixedContentMode()} +and {@link android.webkit.CookieManager#setAcceptThirdPartyCookies(android.webkit.WebView, boolean) setAcceptThirdPartyCookies()} +methods respectively.</li> + <li>The system now intelligently chooses portions of the HTML + document to draw. This new default behavior helps to reduce memory + footprint and increase performance. If you want to + render the whole document at once, disable this optimization by calling + {@link android.webkit.WebView#enableSlowWholeDocumentDraw()}.</li> + </ul> +</li> +<li><strong>If your app targets API levels lower than 21:</strong> The system + allows mixed content and third party cookies, and always renders the whole + document at once.</li> +</ul> + <h2 id="UI">User Interface</h2> <h3 id="MaterialDesign">Material design support</h3> @@ -470,7 +496,7 @@ request the user’s permission by launching a screen capture dialog using an method.</p> <p>For an example of how to use the new APIs, see the {@code MediaProjectionDemo} -class in the {@code ApiDemos} sample project.</p> +class in the sample project.</p> <h2 id="Notifications">Notifications</h2> diff --git a/docs/html/about/versions/lollipop.jd b/docs/html/about/versions/lollipop.jd index 085dc24..3ee0a86 100644 --- a/docs/html/about/versions/lollipop.jd +++ b/docs/html/about/versions/lollipop.jd @@ -206,15 +206,16 @@ video apps and games to display smooth synchronized content.</p> <p>Android 5.0 also adds support for <strong>multimedia tunneling</strong> to provide the best experience for ultra-high definition (4K) content and the ability to play compressed audio and video data together. </p> -<!-- + <div class="figure" style="width:320px; margin:1em 0 0 20px;padding-left:2em;"> <img style="float:right; margin:0 1em 1em 2em" src="{@docRoot}images/android-5.0/managed_apps_launcher@2x.png" srcset="{@docRoot}images/android-5.0/managed_apps_launcher@2x.png 2x" alt="" width="300" /> -<p class="img-caption">Android Work users have a unified view of their personal and work apps, which are badged for easy identification.</p> +<p class="img-caption">Users have a unified view of their personal and work apps, which are +badged for easy identification.</p> </div> ---> + <h2 id="Work">Android in the workplace</h2> diff --git a/docs/html/distribute/essentials/essentials_toc.cs b/docs/html/distribute/essentials/essentials_toc.cs index 4e53468..a1c9575 100644 --- a/docs/html/distribute/essentials/essentials_toc.cs +++ b/docs/html/distribute/essentials/essentials_toc.cs @@ -17,6 +17,12 @@ </div> </li> <li class="nav-section"> + <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/essentials/quality/wear.html"> + <span class="en">Wear App Quality</span> + </a> + </div> + </li> + <li class="nav-section"> <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/essentials/optimizing-your-app.html"> <span class="en">Optimize Your App</span> </a> diff --git a/docs/html/distribute/essentials/quality/tv.jd b/docs/html/distribute/essentials/quality/tv.jd index 8e17157..b13307e 100644 --- a/docs/html/distribute/essentials/quality/tv.jd +++ b/docs/html/distribute/essentials/quality/tv.jd @@ -234,7 +234,7 @@ page.image=/distribute/images/gp-tv-quality.png </td> <td> <p style="margin-bottom:.5em;"> - App does not depend on a remote controller having a menu button to access user interface + App does not depend on a remote controller having a Menu button to access user interface controls. (<a href="{@docRoot}training/tv/start/navigation.html#d-pad-navigation">Learn how</a>) </p> @@ -291,8 +291,8 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - App manifest sets an intent type of {@code ACTION_MAIN} with category - {@code CATEGORY_LEANBACK_LAUNCHER}. + App manifest sets an intent type of {@link android.content.Intent#ACTION_MAIN} with category + {@link android.content.Intent#CATEGORY_LEANBACK_LAUNCHER}. (<a href="{@docRoot}training/tv/start/start.html#tv-activity">Learn how</a>) </p> </td> @@ -321,8 +321,9 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - If the app requires a game controller, the app manifest sets the {@code uses-feature} setting - {@code android.hardware.gamepad} to {@code required="true"}. + If the app uses a game controller as it's primary input method, it declares the appropriate + requirement with the <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html" + >{@code <uses-feature>}</a> manifest tag. (<a href="{@docRoot}training/tv/games/index.html#gamepad">Learn how</a>) </p> </td> @@ -334,9 +335,9 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - If the app provides user instructions for use of game controllers, the instructions - do not include a controller with any branding. - (<a href="{@docRoot}training/tv/games/index.html#generic-controllers">Learn how</a>) + If the app provides visual instructions for using game controllers, the instructions should + be free of branding and show a compatible button layout. + (<a href="{@docRoot}training/tv/games/index.html#ControllerHelp">Learn how</a>) </p> </td> </tr> @@ -351,7 +352,7 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - App enables interaction with any advertising using D-pad controls. + App allows interaction with advertising using D-pad controls. (<a href="{@docRoot}training/tv/start/navigation.html#d-pad-navigation">Learn how</a>) </p> </td> @@ -363,7 +364,7 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - For advertising that uses full-screen, non-video ads, the app allows the user to + For advertising that uses fullscreen, non-video ads, the app allows the user to immediately dismiss the ad with D-pad controls. </p> </td> @@ -375,7 +376,7 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - For advertising that uses clickable, non-full screen, non-video ads, the app does not allow + For advertising that uses clickable, non-fullscreen, non-video ads, the app does not allow ads to link to a web URL. </p> </td> @@ -387,7 +388,7 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </td> <td> <p style="margin-bottom:.5em;"> - For advertising that uses clickable, non-full screen, non-video ads, the app does not allow + For advertising that uses clickable, non-fullscreen, non-video ads, the app does not allow ads to link to another app that is not available on TV devices. </p> </td> diff --git a/docs/html/distribute/essentials/quality/wear.jd b/docs/html/distribute/essentials/quality/wear.jd new file mode 100644 index 0000000..667e945 --- /dev/null +++ b/docs/html/distribute/essentials/quality/wear.jd @@ -0,0 +1,387 @@ +page.title=Wear App Quality +page.tags="wear","wearables","quality","guidelines" +page.metaDescription=Wearables are small factor devices that are built for glanceability and require unique design and functionality. +page.image=/distribute/images/gp-wear-quality.png +@jd:body + +<div id="qv-wrapper"><div id="qv"> +<h2>Quality Criteria</h2> + <ol> + <li><a href="#ux">Design and Interaction</a></li> + <li><a href="#fn">Functionality</a></li> + <li><a href="#faq">Frequently Asked Questions</a></li> + </ol> + + <h2>You Should Also Read</h2> + <ol> + <li><a href="{@docRoot}distribute/essentials/quality/core.html"> + Core App Quality</a></li> + <li><a href="{@docRoot}distribute/essentials/optimizing-your-app.html"> + Optimize Your App</a></li> + <li><a href="{@docRoot}design/patterns/notifications.html"> + Notifications</a></li> + </ol> +</div> +</div> + +<img src="{@docRoot}distribute/images/gp-wear-quality.png" style="width:480px;"> + +<p> + Android Wear aims to provide users with just the right information at just the right time. Great + Android Wear experiences are launched automatically, glanceable, and require zero or low user + interaction. Designing apps for wearables is substantially different than designing for phones or + tablets. There are different strengths and weaknesses, different use cases, and different + ergonomics to take into consideration. +</p> + +<p> + The first step toward creating a great experience for users on Wear is to read the + <a href="{@docRoot}design/wear/index.html">Android Wear design guidelines</a>, which provides + instructions on how to build the best user experience for Wear apps. You should also review the + <a href="{@docRoot}training/building-wearables.html">Building Apps for Wearables</a> training, to + understand the basic implementation requirements for a Wear app. +</p> + +<p class="caution"> + <strong>Important:</strong> To ensure a great user experience, apps for wearables must meet + specific requirements for usability. Only apps that meet the following quality criteria will + qualify as an Android Wear app on Google Play. Qualifying as a Wear app will make it easier for + Android Wear users to discover your app on Google Play. +</p> + +<p class="note"> + <strong>Note:</strong> You will be able to submit your apps for Android Wear review when the + public release of Android 5.0 launches on November 3. Stay tuned for more information about how to + submit your apps for Android Wear review through the <a href="https://play.google.com/apps/publish/signup/">Google Play Developer Console</a>. +</p> + +<div class="headerLine"> + <h2 id="fn"> + Functionality + </h2> + + +</div> + +<p> + These criteria ensure that your app is configured correctly and provides the expected + functional behavior. +</p> + + +<table> +<tr> + <th style="width:2px;"> + Type + </th> + <th style="width:54px;"> + ID + </th> + <th> + Description + </th> +</tr> + +<tr> + <td rowspan="1" id="general"> + General + </td> + + <td id="WR-GL"> + WR-GL + </td> + <td> + <p style="margin-bottom:.5em;"> + Handheld app includes either notifications with wearable-specific functionality or a wearable + app that runs directly on the Wear device. + (<a href="{@docRoot}training/building-wearables.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td rowspan="1" id="packaging"> + Packaging + </td> + + <td id="WR-PK"> + WR-PK + </td> + <td> + <p style="margin-bottom:.5em;"> + Wearable apps that run directly on the device are packaged inside the primary handheld app. + (<a href="{@docRoot}training/wearables/apps/packaging.html">Learn how</a>) + </p> + </td> +</tr> + + +<tr> + <td rowspan="3" id="functional-notifications"> + Notifications + </td> + + <td id="WR-FW"> + WR-FW + </td> + <td> + <p style="margin-bottom:.5em;"> + Notifications with wearable-specific functionality use a {@code RemoteInput} or + {@code WearableExtender}. + (<a href="{@docRoot}training/wearables/notifications/index.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-FR"> + WR-FR + </td> + <td> + <p style="margin-bottom:.5em;"> + Notifications for messaging apps allow users to reply via voice input or quick responses. + (<a href="{@docRoot}training/wearables/notifications/voice-input.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-FG"> + WR-FG + </td> + <td> + <p style="margin-bottom:.5em;"> + Similar notifications are grouped together in a stack. + (<a href="{@docRoot}training/wearables/notifications/stacks.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td rowspan="1" id="gestures"> + Gestures + </td> + + <td id="WR-GP"> + WR-GP + </td> + <td> + <p style="margin-bottom:.5em;"> + Full-screen activities use long press for the sole purpose of prompting to quit. + <br/> + (<a href="{@docRoot}training/wearables/ui/exit.html">Learn how</a>) + </p> + </td> +</tr> + +</table> + + +<h3 class="rel-resources clearfloat">Related resources</h3> + +<div class="resource-widget resource-flow-layout col-13" data-query= +"collection:distribute/essentials/wearqualityguidelines/functionality" +data-sortorder="-timestamp" data-cardsizes="6x2" data-maxresults="6"> +</div> + +<div class="headerLine"> + <h2 id="ux"> + Visual Design and User Interaction + </h2> + + +</div> + +<p> + These criteria ensure that your app follows critical design and interaction patterns to provide a + consistent, intuitive, and enjoyable user experience on wearables. +</p> + +<table> + +<tr> + <th style="width:2px;"> + Type + </th> + <th style="width:54px;"> + ID + </th> + <th> + Description + </th> +</tr> + +<tr> + <td rowspan="2" id="layout"> + Layout + </td> + + <td id="WR-LL"> + WR-LL + </td> + <td> + <p style="margin-bottom:.5em;"> + App user interface is formatted appropriately for both square and round displays. + (<a href="{@docRoot}training/wearables/ui/layouts.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-TC"> + WR-TC + </td> + <td> + <p style="margin-bottom:.5em;"> + App text is large and glanceable with a suggested minimum size of 16sp. + (<a href="{@docRoot}design/wear/style.html#Typography">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td rowspan="1" id="launcher"> + Launcher + </td> + + <td id="WR-LN"> + WR-LN + </td> + <td> + <p style="margin-bottom:.5em;"> + App launcher string is the app name, not a command phrase. + (<a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td rowspan="5" id="notifications"> + Notifications + </td> + + <td id="WR-NC"> + WR-NC + </td> + <td> + <p style="margin-bottom:.5em;"> + App displays confirmation animations when appropriate. + (<a href="{@docRoot}design/wear/patterns.html#Countdown">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-NR"> + WR-NR + </td> + <td> + <p style="margin-bottom:.5em;"> + Notification cards have the app icon visible at the top right edge. The one exception is if the + notification card has single-action controls, for example a media playback card. + <br/> + (<a href="{@docRoot}design/wear/style.html#Assets">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-WI"> + WR-WI + </td> + <td> + <p style="margin-bottom:.5em;"> + Notification actions have a white icon, action title, and transparent background. + <br/> + (<a href="{@docRoot}training/wearables/notifications/creating.html#ActionButtons">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-PB"> + WR-PB + </td> + <td> + <p style="margin-bottom:.5em;"> + Notification photo backgrounds are used only to convey information, not to brand a card. + (<a href="{@docRoot}design/wear/style.html#Branding">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td id="WR-PR"> + WR-PR + </td> + <td> + <p style="margin-bottom:.5em;"> + Notification photo backgrounds have a resolution of at least 400x400. + (<a href="{@docRoot}training/wearables/notifications/creating.html#AddWearableFeatures">Learn how</a>) + </p> + </td> +</tr> + +<tr> + <td rowspan="1" id="googleplay"> + Google Play + </td> + + <td id="WR-GS"> + WR-GS + </td> + <td> + <p style="margin-bottom:.5em;"> + App includes at least one Wear screenshot in its Play Store Listing. + (<a href="https://support.google.com/googleplay/android-developer/answer/1078870?hl=en">Learn how</a>) + </p> + </td> +</tr> + +</table> + + +<h3 class="rel-resources clearfloat">Related resources</h3> + +<div class="resource-widget resource-flow-layout col-13" data-query= +"collection:distribute/essentials/wearqualityguidelines/visualdesign" +data-sortorder="-timestamp" data-cardsizes="6x2" data-maxresults="6"> +</div> + +<div class="headerLine"> + <h2 id="faq"> + Frequently Asked Questions + </h2> +</div> + +<p style="margin-top:30px;"> + <strong>After I submit my app for Android Wear review, how will I find out if my app does not meet + all the requirements for Wear?</strong> +</p> +<p> + If your app does not meet the usability requirements described on this page, the Play Store team + will contact you through the email address specified in the <a href= + "https://play.google.com/apps/publish/">Google Play Developer Console</a> account associated with + the app. +</p> +<p class="caution"> + <strong>Caution:</strong> Make sure your app meets the <a href="#fn">functionality + requirements</a>, otherwise your app will not be considered a Wear app and will not be reviewed + for Wear <a href="#ux">design and interaction</a>. +</p> +<p class="note"> + <strong>Note:</strong> You will be able to submit your apps for additional Android Wear review when + the public release of Android 5.0 launches on November 3. +</p> + + +<p style="margin-top:30px;"> + <strong>If my app does not meet the Wear requirements, will my new or updated app still appear on + Google Play for phones and tablets and still be installable on wearables?</strong> +</p> +<p> + Yes. The requirements described above only determine whether your app will be identified as an + Android Wear app on Google Play and easier for Android Wear users to discover. If your app is not + accepted as a Wear app, it will still be available to other device types, such as phones and + tablets, and it will still be installable on wearables. +</p> diff --git a/docs/html/distribute/images/gp-wear-quality.png b/docs/html/distribute/images/gp-wear-quality.png Binary files differnew file mode 100644 index 0000000..a51a32c --- /dev/null +++ b/docs/html/distribute/images/gp-wear-quality.png diff --git a/docs/html/images/android-5.0/managed_apps_launcher.png b/docs/html/images/android-5.0/managed_apps_launcher.png Binary files differindex 8184556..46e4c74 100644 --- a/docs/html/images/android-5.0/managed_apps_launcher.png +++ b/docs/html/images/android-5.0/managed_apps_launcher.png diff --git a/docs/html/images/android-5.0/managed_apps_launcher@2x.png b/docs/html/images/android-5.0/managed_apps_launcher@2x.png Binary files differindex 66b7be9..d7fdbce 100644 --- a/docs/html/images/android-5.0/managed_apps_launcher@2x.png +++ b/docs/html/images/android-5.0/managed_apps_launcher@2x.png diff --git a/docs/html/images/games/game-controller-buttons.png b/docs/html/images/games/game-controller-buttons.png Binary files differnew file mode 100644 index 0000000..b3e458a --- /dev/null +++ b/docs/html/images/games/game-controller-buttons.png diff --git a/docs/html/images/games/game-controller-buttons_2x.png b/docs/html/images/games/game-controller-buttons_2x.png Binary files differnew file mode 100644 index 0000000..7a0ad0b --- /dev/null +++ b/docs/html/images/games/game-controller-buttons_2x.png diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js index c49f8cc..08c0090 100644 --- a/docs/html/jd_collections.js +++ b/docs/html/jd_collections.js @@ -67,9 +67,9 @@ var RESOURCE_COLLECTIONS = { "distribute/essentials/quality/core.html", "distribute/essentials/quality/tablets.html", "distribute/essentials/quality/tv.html", + "distribute/essentials/quality/wear.html", "https://developers.google.com/edu/guidelines", - "distribute/essentials/optimizing-your-app.html", - "distribute/essentials/best-practices/games.html" + "distribute/essentials/optimizing-your-app.html" ] }, "distribute/users": { @@ -332,6 +332,22 @@ var RESOURCE_COLLECTIONS = { "training/tv/games/index.html" ] }, + "distribute/essentials/wearqualityguidelines/visualdesign": { + "title": "", + "resources": [ + "design/wear/index.html", + "training/building-wearables.html", + "training/wearables/ui/index.html" + ] + }, + "distribute/essentials/wearqualityguidelines/functionality": { + "title": "", + "resources": [ + "training/wearables/notifications/index.html", + "training/wearables/apps/index.html", + "training/wearables/notifications/voice-input.html" + ] + }, "distribute/essentials/core/performance": { "title": "", "resources": [ diff --git a/docs/html/samples/new/index.jd b/docs/html/samples/new/index.jd index 523b922..330caa3 100644 --- a/docs/html/samples/new/index.jd +++ b/docs/html/samples/new/index.jd @@ -348,3 +348,18 @@ AppRestrictionEnforcer sample to toggle the restriction. </p> <p><a href="http://github.com/googlesamples/android-AppRestrictionSchema">Get it on GitHub</a></p> + +<h3 id="SpeedTracker">Speed Tracker (Wear)</h3> + +<p> +This sample uses the FusedLocation APIs of Google Play Services on Android Wear +devices that have a hardware GPS built in. In those cases, this sample provides +a simple screen that shows the current speed of the wearable device. User can +set a speed limit and if the speed approaches that limit, it changes the color +to yellow and if it exceeds the limit, it turns red. User can also enable +recording of coordinates and when it pairs back with the phone, this data +is synced with the phone component of the app and user can see a track +made of those coordinates on a map on the phone. +</p> + +<p><a href="http://github.com/googlesamples/android-SpeedTracker">Get it on GitHub</a></p> diff --git a/docs/html/samples/wearable.jd b/docs/html/samples/wearable.jd new file mode 100644 index 0000000..3114374 --- /dev/null +++ b/docs/html/samples/wearable.jd @@ -0,0 +1,11 @@ +page.title=Wearable +@jd:body + + +<div id="samples" class="wearable"> +</div> + + +<script> + $(document).ready(showSamples); +</script> diff --git a/docs/html/sdk/installing/adding-packages.jd b/docs/html/sdk/installing/adding-packages.jd index e6c0118..22d055c 100644 --- a/docs/html/sdk/installing/adding-packages.jd +++ b/docs/html/sdk/installing/adding-packages.jd @@ -1,5 +1,8 @@ page.title=Adding SDK Packages +page.tags=studio, sdk tools, eclipse adt, sdk manager, google play services, support library +helpoutsWidget=true + @jd:body <style> diff --git a/docs/html/sdk/installing/index.jd b/docs/html/sdk/installing/index.jd index ec0e2f8..6a99952 100644 --- a/docs/html/sdk/installing/index.jd +++ b/docs/html/sdk/installing/index.jd @@ -1,5 +1,8 @@ page.title=Installing the Android SDK +page.tags=studio, sdk tools, eclipse adt +helpoutsWidget=true + @jd:body <style> diff --git a/docs/html/support.jd b/docs/html/support.jd index 4271eee..bbed7df 100644 --- a/docs/html/support.jd +++ b/docs/html/support.jd @@ -3,6 +3,7 @@ page.type=about fullpage=1 page.metaDescription=Resources available to help you report and resolve issues while you are developing apps for Android. page.image=/images/android-support-card.png + @jd:body <div class="wrap" style="width:940px;"> @@ -28,13 +29,20 @@ page.image=/images/android-support-card.png <a href="http://webchat.freenode.net/?channels=android">#android</a>, <a href="http://webchat.freenode.net/?channels=android-dev">#android-dev</a> <span style="color:#888">(IRC via irc.freenode.net)</span><br /> </p> +<p><b> +<a target="_blank" +href="https://helpouts.google.com/partner/ask?vertical=programming&tags=android&origin=http:%2F%2Fdeveloper.android.com%2Fsupport.html">Ask a question in Google Helpouts</a> +</b></p> + <h5>Send Feedback</h5> <p> <a href="http://code.google.com/p/android/issues/entry?template=Developer%20Documentation">Report documentation bug</a><br /> <a href="https://code.google.com/p/android/issues/entry?template=User%20bug%20report">Report device bug</a><br /> <a href="https://code.google.com/p/android/issues/entry?template=Developer%20bug%20report">Report platform bug</a><br /> - +</p> + + </div> diff --git a/docs/html/tools/revisions/build-tools.jd b/docs/html/tools/revisions/build-tools.jd index fe78ce9..6f07755 100644 --- a/docs/html/tools/revisions/build-tools.jd +++ b/docs/html/tools/revisions/build-tools.jd @@ -77,6 +77,28 @@ listing in the Android SDK Manager.</p> <div class="toggle-content opened"> <p><a href="#" onclick="return toggleContent(this)"> <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img" + alt=""/>Build Tools, Revision 21.0.2</a> <em>(October 2014)</em> + </p> + <div class="toggle-content-toggleme"> + <p>Complete updates for Eclipse ADT to solve instability issues on Windows platforms.</p> + </div> +</div> + + +<div class="toggle-content closed"> + <p><a href="#" onclick="return toggleContent(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img" + alt=""/>Build Tools, Revision 21.0.1</a> <em>(October 2014)</em> + </p> + <div class="toggle-content-toggleme"> + <p>Initial updates for Eclipse ADT on Windows. Please use Revision 21.0.2.</p> + </div> +</div> + + +<div class="toggle-content closed"> + <p><a href="#" onclick="return toggleContent(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img" alt=""/>Build Tools, Revision 21.0.0</a> <em>(October 2014)</em> </p> <div class="toggle-content-toggleme"> @@ -96,6 +118,7 @@ listing in the Android SDK Manager.</p> </div> </div> + <div class="toggle-content closed"> <p><a href="#" onclick="return toggleContent(this)"> <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img" diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd index 3fa1b9b..85b9c5e 100644 --- a/docs/html/tools/revisions/platforms.jd +++ b/docs/html/tools/revisions/platforms.jd @@ -80,6 +80,23 @@ class="toggle-content-img" alt="" />Revision 1</a> <em>(October 2014)</em> <h2 id="4.4">Android 4.4W</h2> +<div class="toggle-content open"> + <p><a href="#" onclick="return toggleContent(this)"> + <img src="{@docRoot}assets/images/triangle-open.png" +class="toggle-content-img" alt="" />Revision 2</a> <em>(October 2014)</em> + </p> + + <div class="toggle-content-toggleme"> + + <p>Added location APIs support for Wear.</p> + + <p>Dependencies:</p> + <ul> + <li>Android SDK Platform-tools r20 or higher is required.</li> + <li>Android SDK Tools 23.0 or higher is required.</li> + </ul> + </div> + <div class="toggle-content closed"> <p><a href="#" onclick="return toggleContent(this)"> <img src="{@docRoot}assets/images/triangle-closed.png" diff --git a/docs/html/tools/support-library/features.jd b/docs/html/tools/support-library/features.jd index 8311097..44c5045 100644 --- a/docs/html/tools/support-library/features.jd +++ b/docs/html/tools/support-library/features.jd @@ -139,10 +139,10 @@ page.title=Support Library Features <p>The Gradle build script dependency identifier for this library is as follows:</p> <pre> -com.android.support:support-v4:18.0.+ +com.android.support:support-v4:21.0.+ </pre> -<p>This dependency notation specifies the release version 18.0.0 or higher.</p> +<p>This dependency notation specifies the release version 21.0.0 or higher.</p> <h2 id="v7">v7 Support Libraries</h2> @@ -237,10 +237,10 @@ com.android.support:cardview-v7:21.0.+ <p>The Gradle build script dependency identifier for this library is as follows:</p> <pre> -com.android.support:gridlayout-v7:18.0.+ +com.android.support:gridlayout-v7:21.0.+ </pre> -<p>This dependency notation specifies the release version 18.0.0 or higher.</p> +<p>This dependency notation specifies the release version 21.0.0 or higher.</p> <h3 id="v7-mediarouter">v7 mediarouter library</h3> @@ -271,10 +271,10 @@ both the <code>android-support-v7-mediarouter.jar</code> and <p>If you are using Android Studio, all you need to do is specify the Gradle build script dependency identifier <code>com.android.support:support-v7-mediarouter:<revision></code>, -where "18.0.0" is the minimum revision at which the library is available. For example:</p> +where "<revision>" is the minimum revision at which the library is available. For example:</p> <pre> -com.android.support:mediarouter-v7:18.0.+ +com.android.support:mediarouter-v7:21.0.+ </pre> <p class="caution">The v7 mediarouter library APIs introduced in Support Library diff --git a/docs/html/training/articles/wear-location-detection.jd b/docs/html/training/articles/wear-location-detection.jd new file mode 100644 index 0000000..b0d9755 --- /dev/null +++ b/docs/html/training/articles/wear-location-detection.jd @@ -0,0 +1,375 @@ +page.title=Detecting Location on Android Wear +page.tags="gps" + +page.article=true +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> +<h2>In this document</h2> +<ol class="nolist"> + <li><a href="#Connect">Connect to Google Play Services</a></li> + <li><a href="#Request">Request Location Updates</a></li> + <li><a href="#DetectGPS">Detect On-Board GPS</a></li> + <li><a href="#Disconnection">Handle Disconnection Events</a></li> + <li><a href="#Notify">Handle Location Not Found</a></li> + <li><a href="#Synchronize">Synchronize Data</a></li> +</ol> +<!-- Required platform, tools, add-ons, devices, knowledge, etc. --> +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 4.3 (API Level 18) or higher on the handset device</li> + <li><a href="{@docRoot}google/play-services/index.html">Google Play services</a> 6.1 or higher</li> + <li>An Android Wear device</li> +</ul> +<h2>See also</h2> +<ul> + <li><a href="{@docRoot}training/location/index.html">Making Your App Location-Aware + </a></li> +</ul> +</div></div> + +<p>Location awareness on wearable devices enables you to create apps that give users a better +understanding of their geographic position, movement and what's around them. With the small form +factor and glanceable nature of a wearable device, you can build low-friction apps that record and +respond to location data.</p> + +<p>Some wearable devices include a GPS sensor that can retrieve location data without another +tethered device. However, when you request location data in a wearable app, you don't have to worry +about where the location data originates; the system retrieves the location updates using the most +power-efficient method. Your app should be able to handle loss of location data, in case the wear +device loses connection with its paired device and does not have a built-in GPS sensor.</p> + +<p>This document shows you how to check for on-device location sensors, receive location data, and +monitor tethered data connections.</p> + +<p class="note"><b>Note:</b> The article assumes that you know how to use the Google Play services +API to retrieve location data. For more information, see <a href="{@docRoot}training/ +location/index.html">Making Your App Location-Aware</a>.</p> + +<h2 id="Connect">Connect to Google Play Services</h2> + +<p>Location data on wearable devices is obtained though the Google Play services location APIs. You +use the <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html"> +<code>FusedLocationProviderApi</code></a> and its accompanying classes to obtain this data. +To access location services, create an instance of +<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"> +<code>GoogleApiClient</code></a>, which is +the main entry point for any of the Google Play services APIs. +</p> + +<p class="caution"><b>Caution:</b> Do not use the existing <a href="{@docRoot}reference/android/location/package-summary.html">Location</a> +APIs in the Android framework. The best practice for retrieving location updates is through the +Google Play services API as outlined in this article.</p> + +<p>To connect to Google Play services, configure your app to create an instance of +<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"> +<code>GoogleApiClient</code></a>:</p> + +<ol> + <li>Create an activity that specifies an implementation for the interfaces <a +href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html" +>{@code ConnectionCallbacks}</a>, <a href="{@docRoot}reference/com/google/android/gms/common/api/ +GoogleApiClient.OnConnectionFailedListener.html">{@code OnConnectionFailedListener}</a>, and <a +href="{@docRoot}reference/com/google/android/gms/location/LocationListener.html">{@code +LocationListener}</a>.</li> + <li>In your activity's {@link android.app.Activity#onCreate onCreate()} method, create an instance +of <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code> +GoogleApiClient</code></a> and add the Location service. + </li> + <li>To gracefully manage the lifecycle of the connection, call <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html#connect()"> + {@code connect()}</a> in the {@link android.app.Activity#onResume onResume()} method and + <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html#disconnect()"> + {@code disconnect()}</a> in the {@link android.app.Activity#onPause onPause()} method. + </li> +</ol> + +<p>The following code example shows an implementation of an activity that implements the +<a href="{@docRoot}reference/com/google/android/gms/location/LocationListener.html"> +{@code LocationListener}</a> interface:</p> + +<pre> +public class WearableMainActivity extends Activity implements + GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener, + LocationListener { + + private GoogleApiClient mGoogleApiClient; + ... + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ... + mGoogleApiClient = new GoogleApiClient.Builder(this) + .addApi(LocationServices.API) + .addApi(Wearable.API) // used for data layer API + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .build(); + } + + @Override + protected void onResume() { + super.onResume(); + mGoogleApiClient.connect(); + ... + } + + @Override + protected void onPause() { + super.onPause(); + ... + mGoogleApiClient.disconnect(); + } +} +</pre> + +<p>For more information on connecting to Google Play services, see <a href="{@docRoot}google/auth +/api-client.html">Accessing Google APIs</a>.</p> + +<h2 id="Request">Request Location Updates</h2> + +<p>After your app has connected to the Google Play services API, it is ready to start receiving +location updates. When the system invokes the +<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)"> +<code>onConnected()</code></a> callback for your client, you build the location data request as +follows:</p> + +<ol> + <li>Create a <a +href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html" +>{@code LocationRequest}</a> object and set any options using methods like <a +href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setPriority(int)" +>{@code setPriority()}</a>. + </li> + <li>Request location updates using <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)"> + <code>requestLocationUpdates()</code></a>. + </li> + <li>Remove location updates using <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#removeLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationListener)"> + <code>removeLocationUpdates()</code></a> in the {@link android.app.Activity#onPause + onPause()} method. + </li> +</ol> + +<p>The following example shows how to retrieve and remove location updates:</p> + +<pre> +@Override +public void onConnected(Bundle bundle) { + LocationRequest locationRequest = LocationRequest.create() + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) + .setInterval(UPDATE_INTERVAL_MS) + .setFastestInterval(FASTEST_INTERVAL_MS); + + LocationServices.FusedLocationApi + .requestLocationUpdates(mGoogleApiClient, locationRequest, this) + .setResultCallback(new ResultCallback<Status>() { + + @Override + public void onResult(Status status) { + if (status.getStatus().isSuccess()) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Successfully requested location updates"); + } + } else { + Log.e(TAG, + "Failed in requesting location updates, " + + "status code: " + + status.getStatusCode() + + ", message: " + + status.getStatusMessage()); + } + } + }); +} + +@Override +protected void onPause() { + super.onPause(); + if (mGoogleApiClient.isConnected()) { + LocationServices.FusedLocationApi + .removeLocationUpdates(mGoogleApiClient, this); + } + mGoogleApiClient.disconnect(); +} + +@Override +public void onConnectionSuspended(int i) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "connection to location client suspended"); + } +} + +</pre> + +<p>Now that you have enabled location updates, the system calls the {@link android.location.LocationListener#onLocationChanged +onLocationChanged()} method with the updated location at the interval specified in <a +href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setInterval(long)"> +{@code setInterval()}</a> +</p> + +<h2 id="DetectGPS">Detect On-Board GPS</h2> + +<p>Not all wearables have a GPS sensor. If your user goes out for a run and leaves their phone at +home, your wearable app cannot receive location data through a tethered connection. If the +wearable device does not have a sensor, you should detect this situation and warn the user that +location functionality is not available. + +<p>To determine whether your Android Wear device has a built-in GPS sensor, use the +{@link android.content.pm.PackageManager#hasSystemFeature hasSystemFeature()} +method. The following code detects whether the device has built-in GPS when you start an activity: +</p> + +<pre> + +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.main_activity); + if (!hasGps()) { + Log.d(TAG, "This hardware doesn't have GPS."); + // Fall back to functionality that does not use location or + // warn the user that location function is not available. + } + + ... +} + +private boolean hasGps() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS); +} +</pre> + +<h2 id="Disconnection">Handle Disconnection Events</h2> + +<p>Wearable devices relying on a tethered connection for location data may lose their connections +abruptly. If your wearable app expects a constant stream of data, you must handle the +disconnection based upon where that data is interrupted or unavailable. On a wearable device with no +onboard GPS sensor, loss of location data occurs when the device loses its tethered data connection. +</p> + +<p>In cases where your app depends on a tethered data connection for location data and the wear +device does not have a GPS sensor, you should detect the loss of that connection, warn the user, and +gracefully degrade the functionality of your app.</p> + +<p>To detect the loss of a tethered data connection:</p> + +<ol> + <li>Extend a <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"> + <code>WearableListenerService</code></a> that lets you listen for important data layer events. + </li> + <li>Declare an intent filter in your Android manifest to notify the system about your + <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code> + WearableListenerService</code></a>. + This filter allows the system to bind your service as needed. +<pre> +<service android:name=".NodeListenerService"> + <intent-filter> + <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> + </intent-filter> +</service> +</pre> + </li> + <li>Implement the <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onPeerDisconnected(com.google.android.gms.wearable.Node)"> + <code>onPeerDisconnected()</code></a> method and handle cases of whether or not the device has + built-in + GPS. +<pre> +public class NodeListenerService extends WearableListenerService { + + private static final String TAG = "NodeListenerService"; + + @Override + public void onPeerDisconnected(Node peer) { + Log.d(TAG, "You have been disconnected."); + if(!hasGPS()) { + // Notify user to bring tethered handset + // Fall back to functionality that does not use location + } + } + ... +} +</pre> + </li> +</ol> + +For more information, read the <a href="{@docRoot}training/wearables/data-layer/events.html#Listen"> +Listen for Data Layer Events</a> guide. + +<h2 id="Notify">Handle Location Not Found</h2> + +<p>When the GPS signal is lost, you can still retrieve the last known location using +<a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)"> +<code>getLastLocation()</code></a>. This method can be helpful in situations where you are unable to +get a GPS fix, or when your wearable doesn't have built-in GPS and loses its connection with the +phone.</p> + +<p>The following code uses <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)"> +<code>getLastLocation()</code></a> to retrieve the last known location if available: +</p> + +<pre> +Location location = LocationServices.FusedLocationApi + .getLastLocation(mGoogleApiClient); +</pre> + +<h2 id="Synchronize">Synchronize Data</h2> + +<p>If your wearable app records data using the built-in GPS, you may want to synchronize +the location data with the handset. With the {@link android.location.LocationListener}, you +implement the {@link android.location.LocationListener#onLocationChanged onLocationChanged()} +method to detect and record the location as it changes. + +<p>The following code for wearable apps detects when the location changes and uses the data layer +API to store the data for later retrieval by your phone app:</p> + +<pre> +@Override +public void onLocationChanged(Location location) { + ... + addLocationEntry(location.getLatitude(), location.getLongitude()); + +} + +private void addLocationEntry(double latitude, double longitude) { + if (!mSaveGpsLocation || !mGoogleApiClient.isConnected()) { + return; + } + + mCalendar.setTimeInMillis(System.currentTimeMillis()); + + // Set the path of the data map + String path = Constants.PATH + "/" + mCalendar.getTimeInMillis(); + PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path); + + // Set the location values in the data map + putDataMapRequest.getDataMap() + .putDouble(Constants.KEY_LATITUDE, latitude); + putDataMapRequest.getDataMap() + .putDouble(Constants.KEY_LONGITUDE, longitude); + putDataMapRequest.getDataMap() + .putLong(Constants.KEY_TIME, mCalendar.getTimeInMillis()); + + // Prepare the data map for the request + PutDataRequest request = putDataMapRequest.asPutDataRequest(); + + // Request the system to create the data item + Wearable.DataApi.putDataItem(mGoogleApiClient, request) + .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { + @Override + public void onResult(DataApi.DataItemResult dataItemResult) { + if (!dataItemResult.getStatus().isSuccess()) { + Log.e(TAG, "Failed to set the data, " + + "status: " + dataItemResult.getStatus() + .getStatusCode()); + } + } + }); +} +</pre> + +<p>For more information on how to use the Data Layer API, see the <a href="{@docRoot}training/ +wearables/data-layer/index.html">Sending and Syncing Data</a> +guide.</p> diff --git a/docs/html/training/basics/firstapp/building-ui.jd b/docs/html/training/basics/firstapp/building-ui.jd index 179b3ac..c082642 100644 --- a/docs/html/training/basics/firstapp/building-ui.jd +++ b/docs/html/training/basics/firstapp/building-ui.jd @@ -1,12 +1,8 @@ page.title=Building a Simple User Interface -parent.title=Building Your First App -parent.link=index.html - trainingnavtop=true -previous.title=Running Your App -previous.link=running-app.html -next.title=Starting Another Activity -next.link=starting-activity.html + +page.tags=ui, views, layouts, widgets, string resources +helpoutsWidget=true @jd:body diff --git a/docs/html/training/basics/firstapp/creating-project.jd b/docs/html/training/basics/firstapp/creating-project.jd index c4cb362..418eb68 100644 --- a/docs/html/training/basics/firstapp/creating-project.jd +++ b/docs/html/training/basics/firstapp/creating-project.jd @@ -1,6 +1,7 @@ page.title=Creating an Android Project -parent.title=Building Your First App -parent.link=index.html + +page.tags=eclipse adt, sdk tools, project setup +helpoutsWidget=true trainingnavtop=true next.title=Running Your App diff --git a/docs/html/training/basics/firstapp/index.jd b/docs/html/training/basics/firstapp/index.jd index 1b49096..ac8e64a 100644 --- a/docs/html/training/basics/firstapp/index.jd +++ b/docs/html/training/basics/firstapp/index.jd @@ -3,8 +3,9 @@ page.metaDescription=If you're new to Android app development, this where you sh trainingnavtop=true startpage=true -next.title=Creating an Android Project -next.link=creating-project.html + +page.tags=sdk tools +helpoutsWidget=true @jd:body @@ -47,6 +48,3 @@ not apply to earlier versions.</p> <p>This class uses a tutorial format that incrementally builds a small Android app that teaches you some fundamental concepts about Android development, so it's important that you follow each step.</p> - -<p><strong><a href="creating-project.html">Start the first lesson ›</a></strong></p> - diff --git a/docs/html/training/basics/firstapp/running-app.jd b/docs/html/training/basics/firstapp/running-app.jd index 23cedba..96b7172 100644 --- a/docs/html/training/basics/firstapp/running-app.jd +++ b/docs/html/training/basics/firstapp/running-app.jd @@ -3,10 +3,9 @@ parent.title=Building Your First App parent.link=index.html trainingnavtop=true -previous.title=Creating a Project -previous.link=creating-project.html -next.title=Building a Simple User Interface -next.link=building-ui.html + +page.tags=emulator +helpoutsWidget=true @jd:body diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd index 27d2c10..f9dcba4 100644 --- a/docs/html/training/basics/firstapp/starting-activity.jd +++ b/docs/html/training/basics/firstapp/starting-activity.jd @@ -3,8 +3,9 @@ parent.title=Building Your First App parent.link=index.html trainingnavtop=true -previous.title=Building a Simpler User Interface -previous.link=building-ui.html + +page.tags=input events, intents, activity lifecycle +helpoutsWidget=true @jd:body diff --git a/docs/html/training/building-wearables.jd b/docs/html/training/building-wearables.jd index 0745c93..d751a81 100644 --- a/docs/html/training/building-wearables.jd +++ b/docs/html/training/building-wearables.jd @@ -1,6 +1,6 @@ page.title=Building Apps for Wearables page.trainingcourse=true -page.image=wear/images/notifications.png +page.image=wear/images/02_create.png page.metaDescription=Learn how to build notifications, send and sync data, and use voice actions. @jd:body diff --git a/docs/html/training/material/compatibility.jd b/docs/html/training/material/compatibility.jd index 5e03450..49ef7f7 100644 --- a/docs/html/training/material/compatibility.jd +++ b/docs/html/training/material/compatibility.jd @@ -131,9 +131,9 @@ href="{@docRoot}/sdk/installing/studio-build.html#dependencies">Gradle dependenc <pre> dependencies { - compile 'com.android.support:appcompat-v7:+' - compile 'com.android.support:cardview-v7:+' - compile 'com.android.support:recyclerview-v7:+' + compile 'com.android.support:appcompat-v7:21.0.+' + compile 'com.android.support:cardview-v7:21.0.+' + compile 'com.android.support:recyclerview-v7:21.0.+' } </pre> diff --git a/docs/html/training/material/drawables.jd b/docs/html/training/material/drawables.jd index 8d7f453..fd21e3d 100644 --- a/docs/html/training/material/drawables.jd +++ b/docs/html/training/material/drawables.jd @@ -73,7 +73,7 @@ app's module:</p> <pre> dependencies { ... - compile 'com.android.support:palette-v7:+' + compile 'com.android.support:palette-v7:21.0.+' } </pre> diff --git a/docs/html/training/material/lists-cards.jd b/docs/html/training/material/lists-cards.jd index eb45f0d..e7bdfe0 100644 --- a/docs/html/training/material/lists-cards.jd +++ b/docs/html/training/material/lists-cards.jd @@ -260,7 +260,7 @@ app's module:</p> <pre> dependencies { ... - compile 'com.android.support:cardview-v7:+' - compile 'com.android.support:recyclerview-v7:+' + compile 'com.android.support:cardview-v7:21.0.+' + compile 'com.android.support:recyclerview-v7:21.0.+' } </pre> diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 0fee771..9f06666 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -834,6 +834,12 @@ include the action bar on devices running Android 2.1 or higher." </li> </ul> </li> + <li> + <a href="<?cs var:toroot ?>training/articles/wear-location-detection.html" + description= + "How to detect location data on Android Wear devices." + >Detecting Location</a> + </li> </ul> </li> <!-- End Building for wearables --> diff --git a/docs/html/training/tv/games/index.jd b/docs/html/training/tv/games/index.jd index 29b055b..2f510a9 100644 --- a/docs/html/training/tv/games/index.jd +++ b/docs/html/training/tv/games/index.jd @@ -31,7 +31,7 @@ page.article=true </p> -<h3 id="shared-display">Shared display</h3> +<h3 id="shared-display">Consider the shared display</h3> <p> A living-room TV poses design challenges for multiplayer games, in that all players can see @@ -57,7 +57,7 @@ page.article=true </ul> -<h3 id="landscape-display">Landscape display</h3> +<h3 id="landscape-display">Support landscape display</h3> <p> A TV is always sideways: You can’t turn it, and there is no portrait orientation. Always design @@ -69,19 +69,19 @@ page.article=true <p> TVs don't have touch interfaces, so it's even more important to get your controls right and make - sure that players find them intuitive and fun to use. The separation of controller from device - also introduces some other issues to pay attention to, like keeping track of multiple players' + sure players find them intuitive and fun to use. Handling controllers + also introduces some other issues to pay attention to, like keeping track of multiple controllers, and handling disconnects gracefully. </p> -<h3 id="d-pad">D-pad</h3> +<h3 id="d-pad">Support D-pad controls</h3> <p> Plan your control scheme around a directional pad (D-pad) control, since this control set is the default for Android TV devices. The player needs to be able to use a D-Pad in all aspects of the - game–not just controlling core gameplay, but also navigating menus and ads. For this reason, you - should also ensure that your Android TV game does not refer to a touch interface: For example, an - Android TV game should not tell a player to <strong>Tap here to skip</strong>. + game—not just controlling core gameplay, but also navigating menus and ads. For this reason, you + should also ensure that your Android TV game does not refer to a touch interface. For example, an + Android TV game should not tell a player to "<em>Tap</em> here to continue." </p> <p> @@ -91,35 +91,35 @@ page.article=true <ul> <li> - <strong>Communicate Controller Requirements up Front</strong> - Use your Play Store description + <strong>Communicate Controller Requirements up Front</strong>. Use your Google Play description to communicate to the player any expectations about controllers. If a game is better suited to a gamepad with a joystick than one with only a D-pad, make this fact clear. A player who uses - an ill-suited controller for a game is likely to have a subpar experience–and penalize your + an ill-suited controller for a game is likely to have a subpar experience and penalize your game in the ratings. </li> <li> - <strong>Use Consistent Button Mapping</strong> - Intuitive and flexible button mapping is key - to a good user experience. For example, you can adhere to accepted custom by using the A button - to <code>Accept</code>, and the B button to <code>Cancel</code>. You can also offer flexibility - in the form of remappability. For more information on button mapping, see <a href= + <strong>Use Consistent Button Mapping</strong>. Intuitive and flexible button mapping is key + to a good user experience. For example, you should adhere to accepted customs by using the A button + to <em>Accept</em>, and the B button to <em>Cancel</em>. You can also offer flexibility + in the form of remappability. For more information about button mapping, see <a href= "http://developer.android.com/training/game-controllers/controller-input.html">Handling Controller Actions</a>. </li> <li> - <strong>Detect Controller Capabilities and Adjust Accordingly</strong> - Query the controller + <strong>Detect Controller Capabilities and Adjust Accordingly</strong>. Query the controller about its capabilities in order to optimize the match between controller and game. For example, you may intend for a player to steer an object by waving the controller in the air. If a player's controller lacks accelerometer and gyroscope hardware, however, waving will not work. - When, however, your game queries the controller and discovers that motion detection is not - supported, it can switch over to an alternative, available control scheme. For more information - on querying controller capabilities, see <a href= + So, your game should query the controller and if motion detection is not + supported, switch over to an alternative, available control scheme. For more information + about querying controller capabilities, see <a href= "http://developer.android.com/training/game-controllers/compatibility.html">Supporting Controllers Across Android Versions</a>. </li> </ul> -<h3 id="back-button">Back-button behavior</h3> +<h3 id="back-button">Provide appropriate Back-button behavior</h3> <p> The Back button should never act as a toggle. For example, do not use it to both open and close a @@ -139,18 +139,18 @@ page.article=true </p> -<h3 id="multiple-controllers">Handling multiple controllers</h3> +<h3 id="multiple-controllers">Handle multiple controllers</h3> <p> When multiple players are playing a game, each with his or her own controller, it is important to - map each player-controller pair. For information on how to implement controller-number + map each player-controller pair. For information about how to implement controller-number identification, see <a href= "http://developer.android.com/reference/android/view/InputDevice.html#getControllerNumber">Input Devices</a>. </p> -<h3 id="handle-disconnect">Handling disconnects</h3> +<h3 id="handle-disconnect">Handle controller disconnects</h3> <p> When a controller is disconnected in the middle of gameplay, the game should pause, and a dialog @@ -159,7 +159,7 @@ page.article=true <p> The dialog should also offer troubleshooting tips (for example, a pop-up dialog telling the - player to "Check your Bluetooth connection"). For more information on implementing input-device + player to "Check your Bluetooth connection"). For more information about implementing input-device support, see <a href= "http://developer.android.com/training/game-controllers/controller-input.html">Handling Controller Actions</a>. Specific information about Bluetooth connections is at <a href= @@ -167,25 +167,53 @@ page.article=true </p> +<h3 id="ControllerHelp">Show controller instructions</h3> + +<p>If your game provides visual game control instructions, the +controller image should be free of branding and include only <a +href="{@docRoot}training/game-controllers/controller-input.html#button" +>buttons compatible with Android</a>.</p> + +<p>For sample images of an Android-compatible controller, download the +<a href="http://storage.googleapis.com/androiddevelopers/design/android_tv_gamepad_template-2014-10.zip" +>Android TV Gamepad Template (ZIP)</a>. +It includes a white controller on black background and a black controller on white background +(shown in figure 1), as a PNG file and an Adobe® Illustrator® file.</p> + +<img src="{@docRoot}images/games/game-controller-buttons_2x.png" width="700" + srcset="{@docRoot}images/games/game-controller-buttons_2x.png 2x, + {@docRoot}images/games/game-controller-buttons.png 1x" /> +<p class="img-caption"><b>Figure 1.</b> Example controller instructions using the +<a href="http://storage.googleapis.com/androiddevelopers/design/android_tv_gamepad_template-2014-10.zip" +>Android TV Gamepad Template (ZIP)</a>. + + + + <h2 id="manifest">Manifest</h2> +<p>There are a some special things games should include in the Android manifest.</p> + +<h3 id="Launcher">Show your game in the launcher</h3> <p> - The Android TV launcher home screen displays games in a separate row from regular apps. The TV - framework uses the <code>android:isGame</code> manifest attribute to differentiate games from - non-game apps. Set this value to <code>true</code> in your game's app manifest, as shown in the - following code example: + The Android TV launcher home screen displays games in a separate row from regular apps. + To make your game appear in the list of games, add the + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html" + ><code><meta-data></code></a> tag in your app manifest with <code>android:name</code> + set to <code>"isGame"</code> and <code>android:value</code> + set to <code>"true"</code>. For example: </p> <pre class="fragment"> <application> ... - < meta-data android:name="isGame" android:value="true" > + <meta-data android:name="isGame" android:value="true" > ... </application> </pre> -<h3 id="gamepad">Game Controllers</h3> +<h3 id="gamepad">Declare support for game controllers</h3> <p> Games controllers may not be available or active for users of a TV device. In order to properly @@ -215,7 +243,9 @@ page.article=true <h2 id="gpgs">Google Play Game Services</h2> <p> - If your game integrates Google Play Game Services, you should keep in mind a number of + If your game integrates <a + href="https://developers.google.com/games/services/">Google Play Game services</a>, + you should keep in mind a number of considerations pertaining to achievements, sign-in, saving games, and multiplayer play. </p> @@ -224,7 +254,7 @@ page.article=true <p> Your game should include at least five (earnable) achievements. Only a user controlling gameplay - from a supported input device should be able to earn achievements. For more information on + from a supported input device should be able to earn achievements. For more information about achievements and how to implement them, see <a href= "https://developers.google.com/games/services/android/achievements">Achievements in Android</a>. </p> @@ -262,7 +292,7 @@ page.article=true <p> A game offering a multiplayer experience must allow at least two players to enter a room. For - further information on multiplayer games in Android, see the <a href= + further information about multiplayer games in Android, see the <a href= "https://developers.google.com/games/services/android/realtimeMultiplayer">Real-time Multiplayer</a> and <a href="">Turn-based Multiplayer</a> documentation on the Android developer site. diff --git a/docs/html/training/wearables/apps/index.jd b/docs/html/training/wearables/apps/index.jd index 7d961b7..256205b 100644 --- a/docs/html/training/wearables/apps/index.jd +++ b/docs/html/training/wearables/apps/index.jd @@ -1,5 +1,6 @@ page.title=Creating Wearable Apps -page.image=wear/images/notifications.png +page.tags="wear","wearable","app" +page.image=wear/images/01_create.png @jd:body diff --git a/docs/html/training/wearables/notifications/index.jd b/docs/html/training/wearables/notifications/index.jd index 17f3cb3..a7b6733 100644 --- a/docs/html/training/wearables/notifications/index.jd +++ b/docs/html/training/wearables/notifications/index.jd @@ -1,4 +1,6 @@ page.title=Adding Wearable Features to Notifications +page.tags="wear","notifications","wearables" +page.image=wear/images/01_notifications.png @jd:body <div id="tb-wrapper"> diff --git a/docs/html/training/wearables/notifications/stacks.jd b/docs/html/training/wearables/notifications/stacks.jd index e71e74c..9a528a4 100644 --- a/docs/html/training/wearables/notifications/stacks.jd +++ b/docs/html/training/wearables/notifications/stacks.jd @@ -45,7 +45,7 @@ final static String GROUP_KEY_EMAILS = "group_key_emails"; Notification notif = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender1) .setContentText(subject1) - .setSmallIcon(R.drawable.new_mail); + .setSmallIcon(R.drawable.new_mail) .setGroup(GROUP_KEY_EMAILS) .build(); @@ -65,7 +65,7 @@ instead of as a new card:</p> Notification notif2 = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender2) .setContentText(subject2) - .setSmallIcon(R.drawable.new_mail); + .setSmallIcon(R.drawable.new_mail) .setGroup(GROUP_KEY_EMAILS) .build(); diff --git a/docs/html/training/wearables/ui/index.jd b/docs/html/training/wearables/ui/index.jd index 8ef6fe7..5d97490 100644 --- a/docs/html/training/wearables/ui/index.jd +++ b/docs/html/training/wearables/ui/index.jd @@ -1,4 +1,5 @@ page.title=Creating Custom UIs for Wear Devices +page.image=wear/images/10_uilib.png @jd:body diff --git a/docs/html/wear/images/01_create.png b/docs/html/wear/images/01_create.png Binary files differnew file mode 100644 index 0000000..5a39dde --- /dev/null +++ b/docs/html/wear/images/01_create.png diff --git a/docs/html/wear/images/02_create.png b/docs/html/wear/images/02_create.png Binary files differnew file mode 100644 index 0000000..e722df1 --- /dev/null +++ b/docs/html/wear/images/02_create.png diff --git a/docs/html/wear/images/10_uilib.png b/docs/html/wear/images/10_uilib.png Binary files differnew file mode 100644 index 0000000..de7be57 --- /dev/null +++ b/docs/html/wear/images/10_uilib.png diff --git a/docs/image_sources/distribute/gp-wear-quality.svg b/docs/image_sources/distribute/gp-wear-quality.svg new file mode 100644 index 0000000..2acf81a --- /dev/null +++ b/docs/image_sources/distribute/gp-wear-quality.svg @@ -0,0 +1,2268 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="2824.887" + height="1419.8136" + id="svg3004" + xml:space="preserve" + inkscape:version="0.48.1 " + sodipodi:docname="wear_app_quality.svg"><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1280" + inkscape:window-height="1002" + id="namedview125" + showgrid="false" + inkscape:zoom="0.23675905" + inkscape:cx="1735.4152" + inkscape:cy="-51.97425" + inkscape:window-x="1272" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="layer8" + showguides="true" + inkscape:guide-bbox="true" + fit-margin-top="50" + fit-margin-left="200" + fit-margin-right="200" + fit-margin-bottom="200"><sodipodi:guide + orientation="0,1" + position="1543.7834,1150.3523" + id="guide3963" /><sodipodi:guide + orientation="0,1" + position="1978.6428,757.82274" + id="guide9681" /></sodipodi:namedview><metadata + id="metadata3010"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs3008"><clipPath + id="clipPath3020"><path + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + id="path3022" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3044"><path + d="m 391.754,758.654 452.495,0 0,-452.496 -452.495,0 0,452.496 z" + id="path3046" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3060"><path + d="m 362.683,787.727 510.637,0 0,-510.638 -510.637,0 0,510.638 z" + id="path3062" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3086"><path + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3100"><path + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3116"><path + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3060-8"><path + d="m 362.683,787.727 510.637,0 0,-510.638 -510.637,0 0,510.638 z" + id="path3062-8" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3044-8"><path + d="m 391.754,758.654 452.495,0 0,-452.496 -452.495,0 0,452.496 z" + id="path3046-8" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3116-3"><path + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118-0" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3100-0"><path + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102-0" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3086-0"><path + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088-4" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3086-0-0"><path + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088-4-2" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3100-0-7"><path + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102-0-2" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3116-3-2"><path + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118-0-6" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3060-8-2"><path + d="m 362.683,787.727 510.637,0 0,-510.638 -510.637,0 0,510.638 z" + id="path3062-8-4" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3044-8-3"><path + d="m 391.754,758.654 452.495,0 0,-452.496 -452.495,0 0,452.496 z" + id="path3046-8-8" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3086-4"><path + inkscape:connector-curvature="0" + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088-8" /></clipPath><clipPath + id="clipPath3100-8"><path + inkscape:connector-curvature="0" + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102-2" /></clipPath><clipPath + id="clipPath3116-4"><path + inkscape:connector-curvature="0" + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118-5" /></clipPath><clipPath + id="clipPath3044-8-1"><path + inkscape:connector-curvature="0" + d="m 391.754,758.654 452.495,0 0,-452.496 -452.495,0 0,452.496 z" + id="path3046-8-5" /></clipPath><clipPath + id="clipPath3060-8-27"><path + inkscape:connector-curvature="0" + d="m 362.683,787.727 510.637,0 0,-510.638 -510.637,0 0,510.638 z" + id="path3062-8-6" /></clipPath><clipPath + id="clipPath3086-0-0-4"><path + inkscape:connector-curvature="0" + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088-4-2-1" /></clipPath><clipPath + id="clipPath3100-0-7-1"><path + inkscape:connector-curvature="0" + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102-0-2-5" /></clipPath><clipPath + id="clipPath3116-3-2-9"><path + inkscape:connector-curvature="0" + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118-0-6-5" /></clipPath><clipPath + id="clipPath3044-8-3-2"><path + inkscape:connector-curvature="0" + d="m 391.754,758.654 452.495,0 0,-452.496 -452.495,0 0,452.496 z" + id="path3046-8-8-2" /></clipPath><clipPath + id="clipPath3060-8-2-1"><path + inkscape:connector-curvature="0" + d="m 362.683,787.727 510.637,0 0,-510.638 -510.637,0 0,510.638 z" + id="path3062-8-4-5" /></clipPath><clipPath + id="clipPath3942" + clipPathUnits="userSpaceOnUse"><path + id="path3944" + d="m 1337.7,554.569 67.31,0 0,-64.569 -67.31,0 0,64.569 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3912" + clipPathUnits="userSpaceOnUse"><path + id="path3914" + d="m 1404.777,549.002 12,0 0,11.998 -12,0 0,-11.998 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3908" + clipPathUnits="userSpaceOnUse"><path + id="path3910" + d="m 1404.78,561 12,0 0,-12 -12,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3886" + clipPathUnits="userSpaceOnUse"><path + id="path3888" + d="m 1363,547.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3882" + clipPathUnits="userSpaceOnUse"><path + id="path3884" + d="m 1363,559.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3852" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3854" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAAAAACTooUSAAAAAXNCSVQI5gpbmQAAAXhJREFUWIXt2T9v3DAMBfBHWj4Fzh2CXjsEaZd+/+8VZGiN+v4Y1cni6+ADmqUjnQ7kqEE/ULQXPgEAQLBB8S8lAnE3SZCrKFBVVd8+CTMzA5EAaOr7PrmShC211sWABNGUh8fHnNRTtKVcrzMWY4JoPxw/Hw+585sl2cp5/AkzMgm033/59nIcevUCAavz+Cq3Upvce3z5/nzY+T0r7XZ+w/zrpMJ1jvvj89enB0/x94Rpn5PK/VvNw+HpU+78xFZwGnJSAAkQ0a7f5fzgKSLv+k5FgAQAoqJd1zmK6DqVdWppPZK1vES8u93xj/hHhRhiiCGGGGKIIYYYYoghhhhiiCGGGOL/Jt73ciRJuinvr08AQKO11vzCALbWjMa7SFqrt1LgutEtt9qMXHu0pcznCc5b6/NcFgOQQFvKZXzDyXszP17KYkQCrc7jKyb/9GGcqxGJYvXyg9ctEpZLNXDtEWXaJEWqRgggWyZl/IA08EMST2DTVPcPY4fdi0eNNAgAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3840" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3842"><g + id="g3844" + clip-path="url(#clipPath3836)"><g + id="g3846"><g + id="g3848" + transform="matrix(113,0,0,68,1351,505)"><image + id="image3850" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAYAAAC2ydrOAAAABHNCSVQICAgIfAhkiAAAA1NJREFUeJztnc1uozAURj8HjCMCikq7iNJu+v7vVXXRovJn1RDiWUztMQnttIuQmat7pKugAFn4yDZk8V0BwGIGIcTc18wVsXZWFQROJAohvMDwmLke1lovMDx2xO7AyVqtVmcVnmeWw8k6Ho9nFZ6Pw5tWqxXiOIaU0lccxyzyCoQCD4cDhmHwdTgcvEjgQ6IQwgtUSiFNU2w2G2w2GyilvEiWuBzWWi/QGIOu69B1HbTWAOBFWmunEqWUSNMURVHg9vYWRVEgz3MopRBFEe+RC+H2vXEcYYxB0zQoyxKvr68A/iyv7ro43AullMiyDHd3d3h4eMB+v0dRFEjTFFJKv6wyl+d4PGIYBmitUZYlnp6eIIRA3/cwxmAYBozjCCHE5zNxv9/j8fERu90OeZ4jSRJeUhfCLaV936NpGjw/PwMAtNZ4e3tDXdfexdly6vbELMtQFAV2ux3u7++x3W6xXq9Z4kI4ie/v76iqCgBQVRWyLJt9Rpl9OnUPN3meY7vd4ubmZrIvMpcl3A8BoK5rpGk6ERgyeU90MzKKIkgpkSQJlFJQSmG9XrPEhXASAUAphSRJIKVEFEV+BoYe4tMfcCJDoWGxxMvj3hHdmIc+5sb/TKLD2Z4r5vL8ZNz5nYEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEAn2a7uWji02Iuz0/H/kyiC0x1n+M4+gI4kX8JXFSmq9DHnMw4vDEUNwyDz5t24akclbkMYWitMQZ93/vM7zDA3TGZiWGEv9YaTdP4+GKOj16O0/jopmmgtYYx5qwnBvAh8bQHQ9u2KMvSB4jXdc1B7gsyF+ReliXatp2InHSocTedRvgDvwPEuaXC8sz5KMsSWmsMwzCVaK2FEMLf1LYtXl5eYK1F13Xc3OQKfNXcpG1bL9FdOzsTAcAYg6qquM3QlfiqzdDpTPSt98LeGNzw6/p8p+HXrESAW+/9S3y39R43wfwP+FsTzDOJ/gTL++f47K+3XyWNnXDxB1SBAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3836" + clipPathUnits="userSpaceOnUse"><path + id="path3838" + d="m 1351,573 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3832" + clipPathUnits="userSpaceOnUse"><path + id="path3834" + d="m 1351,573 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3824" + clipPathUnits="userSpaceOnUse"><path + id="path3826" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3798" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3800" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAAAAACTooUSAAAAAXNCSVQI5gpbmQAAAZ9JREFUWIXt2U9v1DAQBfA348lmyXa1FdBSgRDf/4tVe4CI7J+oXtvzOKQIOCOnF8/Ft/lpZCeHeQIAgGCF4h9KBFLdJEEuokBVVevOSbi7OwiDiIZN14WqJOE5pZQdMEBtO+yGPmhN0XO8Xmdkp4mEfv/+w/2u13p3SZZ4Hn/AnTRo2B6evjwd3lnFGT3N47PcYipiIrY9PH77+nHotBpIv52PmH+eVGiQsBnuHz5/2m1qii8TprveVJa3av2wP9xt6j0dlojT0JsCMJAk1LptVRH9pgsqAhjoJedC0YqfBxGCytLfCJZcsgM1/3Qi8ru7gnQv7mQ17t9SgEutBEIBYEXvVVy1mtjEJjaxiU1sYhOb2MQmNrGJTWxiE5v4P2XLUXlt9Xd7AwA6vZRSLwxgKcXpfBVJL+kWI0LNjW68peLkMqPnOJ8nbCvuV/1lOs8xO5aNbo6X8YhTzR2y387H8RKzEwZ6msdnTDWzgCV9GOfkhFE8Xb7zuu9D9YTlkhxcZkScelshRUpOCCBqXdfZOkkZ3yANfJPEE1g11f0FocoCRBRb0tkAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3786" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3788"><g + id="g3790" + clip-path="url(#clipPath3782)"><g + id="g3792"><g + id="g3794" + transform="matrix(113,0,0,68,1351,444)"><image + id="image3796" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAYAAAC2ydrOAAAABHNCSVQICAgIfAhkiAAAA59JREFUeJztnclurDoURTdgmkAhSmlLiaL8/49FGSQodFZM5zd4sstUkdsMQnKPzpKsQgVMWDo2ZrCPB0BjBc/z1v5mvhGtV1XBw4lEz/OsQPeY+T601lage2wQ5sDI8n3/bLjnme0wsuZ5PhvueQEcK873fQRBgCiKEIYhwjBEEAQs8htwBY7jiGEY7BjH0YoEnEr0fR9CCCRJgjRNkWUZ0jRFHMdWJEvcDq21FaiUQtd16LoOUkoAsCK11hCmCoMgQBzHyPMcl5eXuLq6wn6/R5ZliOPYSmSRX49Z96ZpglIKTdOgLEu8vb0BOE6v5joBwE6jSZKgKAocDgc8PDzgcDigKApcXFxACMECN2SeZwzDACklyrLE8/MzPM9D3/dQSmEYBkzTBM/zjpVoptKiKHB7e4unpyc8Pj7i+voaaZoiDEO7NjJfi5lK+75H0zR4eXkBAEgp8f7+jrqu7cxoK9FMp1EUIU1T7Pd73Nzc4P7+Hnd3d8iyDFEUscSNMBI/Pj5QVRUAoKoq7HY7xHEMIcTiHeXs7VQIgTiOkaYp8jxHURTY7XZWIk+pX4+7HgJAXdf2JdMIdBHmJncAx7fVMAyRJAlL3BAjEQDiOLZbPneX4HqwEud5xjRNGMcR4zhimiZorRf7R5a4DaaQgiBYPPfPnr9wP+cYiebX3VAC/BluS8yzdsdn+MBxOjX7j2mazvYizM/FrpCn6+LpGsn8XM72DCzv34M3fgRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQQQn53gdKnv42+f/ZlEk/HmJi+aAXAi/xaYsEQzXB9rMoV7oytuGAabN23CU4MgYIkb4IbWKqXQ973N/F4LTVxUohvhL6VE0zQ2vjhJEs473YjT+OimaSClhFJqNcJ0EVprBLZti7IsbYB4XdecPLwha0HuZVmibduFyEWHGnPTaYQ/8H+AOKfxb8+aj7IsIaXEMAxLiSYi2tzUti1eX1+htUbXdcjz3Hap4eThbfhVc5O2ba1Ec+1qJQKAUgpVVa1G+DNfz6/aDJ1Wom2957ZUMM2+wjBcRPizxO34k4ZfqxIBbr33k/jT1nvcBPMf4HdNMM8k2hMs78fx2ae3/wBR6gt9OOvm0AAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3782" + clipPathUnits="userSpaceOnUse"><path + id="path3784" + d="m 1351,512 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3778" + clipPathUnits="userSpaceOnUse"><path + id="path3780" + d="m 1351,512 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3770" + clipPathUnits="userSpaceOnUse"><path + id="path3772" + d="m 1341,490 129,0 0,89 -129,0 0,-89 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3748" + clipPathUnits="userSpaceOnUse"><path + id="path3750" + d="m 1214,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3726" + clipPathUnits="userSpaceOnUse"><path + id="path3728" + d="m 652.002,520.002 12,0 0,11.998 -12,0 0,-11.998 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3722" + clipPathUnits="userSpaceOnUse"><path + id="path3724" + d="m 652,532 12.002,0 0,-12 -12.002,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3700" + clipPathUnits="userSpaceOnUse"><path + id="path3702" + d="m 592,518.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3696" + clipPathUnits="userSpaceOnUse"><path + id="path3698" + d="m 592,530.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3688" + clipPathUnits="userSpaceOnUse"><path + id="path3690" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3662" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3664" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAAAAAAc13L4AAAAAXNCSVQI5gpbmQAAAdJJREFUaIHt2rFu2zAUheFDirICJUZQt0OQdun7v1fgwRHqylJMkbyng1S0He5aarj/C/gDLWnhcQAAONSNfxTOwdXkkCBXjIP33vtqp0OIiAiIAMCHtm1DLQ0hJaWlCBjgfOj6x8cu+EoYKXGe5nsuCHC+7U+fT8euqfLckBKnH+8DKQgOvn368u311Le+ggVg/rie27KkUraTef3+cjzU+Z8kzZcu3caP5NZn5un08vX5oRJmmQ73S39o3PY2df3x+VPXVHlmZAlz3wXvHALgnG/aQ9c9VML4GDxIrt8ZOO980zR1MM47lpyLbBjArVXAAIDkkgvBOq/zP5EiRYTEDjDgGnaBAUgCe8FsGUbLMFqG0TKMlmG0DKNlGC3DaBlGyzBahtEyjJZhtAyjZRgtw2gZRsswWobRMoyWYbQMo2UYLcNoGUZru0r+fWX5//v7lwMAUCillCprHpZShMINQ0pJS4yoc+NfYlxSEXI9GclxHq+osxKh3K/jHLMACKDkeBvO+FlnP0NZxvNwi1mIAEqahzdcay2LJM3D2zAnIQKdpNuFU73NVYnj8H5LAq4ng3ituEbLcZrmJIQDXPWdXk4p5Q2zmwXj3radwF5Wr78ATrQFVxEFtwQAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3650" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3652"><g + id="g3654" + clip-path="url(#clipPath3646)"><g + id="g3656"><g + id="g3658" + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)"><image + id="image3660" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAABCJJREFUeJzt3UFvskwYheEDAmNQYkq7MLab/v//1bhQUgqMwjDMt3gzBJTmy7Ol50omNSLdcAcfcEEAwGFBEARLb9Mf4dxiFgjwEEwQBGMs09f0Nzjnxlimr73Iv/BhhGH4tKbbaZ18GMMwPK3p9mi6UxiGiKIIcRyPK4oiRrNy01istTDGwBiDrutgrZ1FEwH/QvCxKKWQpil2ux12ux2UUmM0DGadnHNjLG3bQmuNpmmgtcb9fkff97DWAsA8mDiOkaYp8jzH6+sr8jxHlmVQSmGz2XCmWSE/pwzDgLZt0TQNvr+/cb1eURTFbDsARNPZJY5j7Pd7vL294ePjA6fTCXmeI01TxHE8fjXRujjn0Pc9brcbyrLE+XxGHMew1qLrOhhjYK2Ftfb3M8zpdMLn5yeOxyOyLEOSJPxaWqlhGGCMgdYal8sFSikYY1DXNaqqwu12gzEGQRAszzD7/R55nuN4POL9/R2HwwHb7ZbBrNQwDOi6Dk3TIEkS3O93XC4XpGmKJEnGcQT45SrJD75ZluFwOODl5WU2x9B6+Pmk6zpEUQStNdI0fbrYeQrGvxmGITabDeI4RpIkUEpBKYXtdstgVsgHE4Yh2rad3UbxA+/0Bl70+A98NNN4povBrItzbnbMnXOw1qLv+/FyehiG34Px/BlnadF6DcMwhuL/Tn8q4HUyAZjfj/E38fzr6VcSg6HR48zyOL8ADIYWLIXiMRgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBI5NfHEP/fwyJpHaTH+SkY/yha/9daOy4AfG71yvgHm/s1PfZL4UTTHaeRGGPQdR3atkXbtgCAzWbDYFbGB+OPc9d1MMbM4pmGMzvD+Keit20LrTWqqkJZlgCA7XaLMAwZzMr4k8T9fkdZlqiqClprtG2Lvu8xDMPs89F0Jx9LXdcoigLn8xkA8PPzgyRJGMwK+WPfdR2qqsL5fEZRFKjrehaNP8vMgjHGQGuNoijw9fUFACjLEmmaIo5jhCEvqtZo6dgXRQGtNYwx82CccwiCYNyprmtcLhc459A0DbIsg1JqnF94hlkXP6P4OaaqKhRFgev1irqux2D8ZxfPMADQti3KsoRSClEU8etoxR5HkqZp0DTN4hkmAOCAf5fLYRgiiiLEcTwuH4v/DK2Pj8FHY4wZ1+MMMwsGAMIwfFrT7bRO02ge13T7GIw3nVM4s/w90/suS3d9n4IZNzCUP+23nwf+AzNnFIrywW/wAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3646" + clipPathUnits="userSpaceOnUse"><path + id="path3648" + d="m 558.314,543.945 139.351,0 0,-78.634 -139.351,0 0,78.634 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3642" + clipPathUnits="userSpaceOnUse"><path + id="path3644" + d="m 558.314,543.945 139.351,0 0,-78.633 -139.351,0 0,78.633 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3634" + clipPathUnits="userSpaceOnUse"><path + id="path3636" + d="m 570.26,532 c 0,-30.785 24.955,-55.742 55.74,-55.742 l 0,0 c 30.784,0 55.74,24.957 55.74,55.742 l 0,0 c 0,30.784 -24.956,55.74 -55.74,55.74 l 0,0 c -30.785,0 -55.74,-24.956 -55.74,-55.74" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3610" + clipPathUnits="userSpaceOnUse"><path + id="path3612" + d="m 652.333,574.333 101,0 0,-79.333 -101,0 0,79.333 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3592" + clipPathUnits="userSpaceOnUse"><path + id="path3594" + d="m 730.778,549 12,0 0,-12 -12,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3570" + clipPathUnits="userSpaceOnUse"><path + id="path3572" + d="m 679,535.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3566" + clipPathUnits="userSpaceOnUse"><path + id="path3568" + d="m 679,547.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3540" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3542" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAAAAADGw2ZMAAAAAXNCSVQI5gpbmQAAAZJJREFUWIXt2DFv2zAQhuHvTlTkyDaMuBmCtEv//+9q0KKuAtWWZJnkfR0UAwY69jyVXAhoeEQcyOUVAAAEvos3rAjE0ydBfugCVVV1Oz9hZmYgEABoqOs6ePGE5Rgv2UAEiIamXa+boE665Xkch3PKQIBo3e4/7bdN5TJ70i7De6ckjUGg9eb5y+u+rdUBB5jO/Y/G4s3ZX7++bB98RmNp6lpOwzkal7lv9i+fdysvfVzb8XCoVWS5M0273T01lYvOtMJxt2kqBQIgolX90DQrLx1T+9jUlQgCAIiKVlXlpKOqQwgqsrwmALIsDx2iqioqAHxu4V8/+DjoffTrKnrRi170ohe96EUvetGLXvSiF/3/0bkkvWsnIHn98u+ymRmNV51Gyzn7RD2mHFNKxqUYkpbjZZ7h1WfGaZzmmEkEAJbm8djDr1z96vrTnA0IoKX51H3Hb8fq9u1nP0UjA2hx7N7QuxbDt26IRgSKxdOBg3PtfJ8Sl8nEEXPvXWrnbIQAcqfKTN65kN+97t9sfv6y/QG64fFP78GtbgAAAABJRU5ErkJggg==" + height="1" + width="1" /></mask><mask + id="mask3528" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3530"><g + id="g3532" + clip-path="url(#clipPath3524)"><g + id="g3534"><g + id="g3536" + transform="matrix(93,0,0,68,667,493)"><image + id="image3538" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAA51JREFUeJztnEtv4koQRo/BpokBoThZIJJN/v/vCkoUYuSAbYwfPYu57duOzWPm3lEvpo5UwqLbXhyXihaLzwM0A3ieN/S1cCNaD2oFwOObdM/zWuH2tXAbWutWuH1t45sLI3c0GvXKXheGMXKbpumVvQ6WdPgp3Pd9giBoy/d9EX8FW3hd15RlSVmWnE4n6rruiffhp0wjXClFGIbMZjNmsxlKqVa8SB9Ga90KL4qCLMvIsow0TTkej1RVRV3X7f6O9CAICMOQKIp4eHggiiIWiwVKKcbjscz4AczcbpqG0+lEmqbsdjviOGY0GrXrZo/WGt+e5UEQMJ/PeXx85Pn5mfV6TRRFhGFIEATtmBG6aK2pqorj8UiSJLy/v6OUomkayrK8vdPX6zUvLy+sVisWiwWTyURGzBmapqGqKvI8J45jwjBEa02e5+2IKcvy306H/kyfz+dEUcRqteLp6Ynlcsl0OhXpZzDSsyxjNpvRNA37/Z7tdst2u22nhBnPg6cX82O6WCxYLpfc39935rrQxYyX6XQKwH6/Z7lcMp/PW2/2aO6c003Hj8djgiBgMpmglEIpxXQ6FelnMNIB8jwnDEPu7u5QShEEQe8Q4n9/gBFvvwC7RHofc/42zer7flv2WDH0pBvMxqES+pgmtctuYBs5A/4BrjWoSHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASP8D2GlGQ5zNBrBvvPaQvx07LsouO0LKpifd3mjCwEyBJNYNYaJHTFJdVVVtGZeDEYH22zI3n04niqKgKAoAiR45g5GeZRl5nrefRVFQliV1XXfEdzrdxCKZmLv9fk+SJAASJ3UBO07q8/OTOI5JkoTD4UBRFJ1sRvhHuulwI/xwOBDHMW9vbwB8fX1JcNoFvgenvb6+8vHxQZIk5HneCU1rg9OM9LIsybKMOI7ZbDYAJEkiEYFXGIoI3Gw2xHFMmqYd6QC+1hrP81rph8OB7XaL1po0TSUM8wqXwjB3ux15nlNVVf+H9HunAxRFQZIkEvt6A5diX+2ZbsS3Ud52NqMEHP8atwQc26eXjnSQKO/f5VeivCW0/n/mltD6nvR2QWT/Jy79ZfIDbR7YvKqk0OoAAAAASUVORK5CYII=" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3524" + clipPathUnits="userSpaceOnUse"><path + id="path3526" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3520" + clipPathUnits="userSpaceOnUse"><path + id="path3522" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3498" + clipPathUnits="userSpaceOnUse"><path + id="path3500" + d="m 560,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3490" + clipPathUnits="userSpaceOnUse"><path + id="path3492" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3476" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3478" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHcAAAK/CAAAAADGr34kAAAAAXNCSVQI5gpbmQAACLFJREFUeJzt1DEBACAMwDDAv+fhohwkCnp1zwJIndcBwHd8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4DaBSkuBn0AocgUAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><clipPath + id="clipPath3470" + clipPathUnits="userSpaceOnUse"><path + id="path3472" + d="m 538,215 954,0 0,118 -954,0 0,-118 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3460" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3462" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHcAAAK/CAAAAADGr34kAAAAAXNCSVQI5gpbmQAACLFJREFUeJzt1DEBACAMwDDAv+fhohwkCnp1zwJIndcBwHd8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4DaBSkuBn0AocgUAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><clipPath + id="clipPath3454" + clipPathUnits="userSpaceOnUse"><path + id="path3456" + d="m 554,720 958,0 0,204 -958,0 0,-204 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3432" + clipPathUnits="userSpaceOnUse"><path + id="path3434" + d="m 904,587 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3428" + clipPathUnits="userSpaceOnUse"><path + id="path3430" + d="m 904,599 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3420" + clipPathUnits="userSpaceOnUse"><path + id="path3422" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3396" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3398" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAACdCAAAAAAcMECVAAAAAXNCSVQI5gpbmQAAAZdJREFUaIHt27tu20AQheEzy6Vo0BKMKCkMJ03e/70MFzYR3YisljsnBQXETZpg1J0pt/gwGGz7GwAAhtjhX9UMFsmTIFfckFJKKWx7wt3dQWQAKfd9n6N0wpda6+JAhqU8jI+PQ05BuC/lcpmxODMs9eP+6343dCF3J1s5TR9wJ7Mh9dtvP172Y58CbMDrPL3atdRmt81ffj7vNiF3oV9Pb5h/HZNxvfl2//z96SEI/33AYTvkZLffMoy7py9DF4K3guM45AQgA2ap6zfD8BCEY9j0XTIDMgBYstR1XQyOrku2XjivT7ZOAI5PUMz3+8cIFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwv97bkEBSZIR4GcpAwCd3loLaX7YWnM6bzjprV5LQVQhUq61Oblu7kuZTwfEtS2nuSwOIIO+lPP0hmNglTOdy+JEBr3O0ysOoT3RNFcnMs3r+Z2X4BLqXB1cN0c5RDdc1QkD7E71Ge/bzd27+APu1Sr+ATqP3j0zgxPtAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><mask + id="mask3384" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3386"><g + id="g3388" + clip-path="url(#clipPath3380)"><g + id="g3390"><g + id="g3392" + transform="matrix(92,0,0,157,888,469)"><image + id="image3394" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAACdCAYAAAA5Wx9JAAAABHNCSVQICAgIfAhkiAAABIZJREFUeJzt111rs0gYxvHL92AipbYHIe1Jv//3CjlopMYXMmqcPdgdH03s7lOWvWDh+sPQUDWUX4c7Ew+AxUqe5639Wv1m1q6ywsMduOd5E/b8tfq9rLUT9vy1K3QvHKzv+w9rfl2t52DHcXxY8+vh/CHf9xGGIaIomlYYhkL/h+bYwzCg7/tpDcMwoQN/gXueN2EnSYI0TbHdbrHdbpEkyYQu8PWstRO2MQZN06BpGrRtCwATurV2CR5FEdI0RZ7neHl5QZ7nyLIMSZIgCALN9JXcnL7dbjDGoKoqFEWB8/kM4NeIcfeF89kdRRF2ux1eX1/x/v6Ow+GAPM+RpimiKJpGi1o2jiP6vkfbtiiKAsfjEZ7noes6GGPQ9z1utxs8z/t+hx8OB3x8fGC/3yPLMsRxrLGykhsnXdehqiqcTicAQNu2+Pr6wuVymdweRoqb4bvdDnmeY7/f4+3tDU9PT9hsNgJfyYFfr1eUZQkAKMsSu91u9fNv9ZTiPjizLMPT0xOen58Xc1z9aj6/AeByuSBN0wX2vMU53O30IAgQRRHiOEaSJEiSBJvNRuArOXAASJIEcRwjiiIEQTDt7LlZeP8GDn2OP18CX+bO4M5nbrdm9QDucv+ZtaWW/cRI5zxyAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInF353wVq7utSynzo9gFtrMY7j9PN2u00LADzP++/++v9h1tqF0dxuDT6cPzhH7vseXdfBGANjDAAgCAKB3+XAnVPXdej7foE/h1/s8HEcMQwDjDFo2xZVVaEsSwDAZrOB7/sCv8tt0uv1irIsUVUV2raFMQbDMGAcx8X94fwhh13XNYqiwOl0AgBcLhfEcSzwlZxd13Woqgqn0wlFUaCu6wW62+UL8L7v0bYtiqLA8XgEAJRliTRNEUURfF+HmrXW7IqiQNu26Pt+CW6thed500N1XePz8xPWWjRNgyzLkCTJNL+1w5e5Ge3meFVVKIoC5/MZdV1P4O7e1R0OAMYYlGWJJEkQhqHGyd90P5KbpkHTNKs73ANggT+Pe77vIwxDRFE0LYft7lGPOUyH3vf9tO5n+AIcAHzff1jz62q9Ofr9ml+fwF3zOa2Z/fPm5+61b50P4NMFQf+rvvt6/we8Kp4iURf8WgAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3380" + clipPathUnits="userSpaceOnUse"><path + id="path3382" + d="m 888,626 92,0 0,-157 -92,0 0,157 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3376" + clipPathUnits="userSpaceOnUse"><path + id="path3378" + d="m 888,626 92,0 0,-157 -92,0 0,157 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3354" + clipPathUnits="userSpaceOnUse"><path + id="path3356" + d="m 1116,527 26.006,0 0,3 -26.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3350" + clipPathUnits="userSpaceOnUse"><path + id="path3352" + d="m 1116,530 26.01,0 0,-3 -26.01,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3328" + clipPathUnits="userSpaceOnUse"><path + id="path3330" + d="m 1057.994,527 11.006,0 0,3 -11.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3324" + clipPathUnits="userSpaceOnUse"><path + id="path3326" + d="m 1057.99,530 26,0 0,-3 -26,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3302" + clipPathUnits="userSpaceOnUse"><path + id="path3304" + d="m 1000,527 26.006,0 0,3 -26.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3298" + clipPathUnits="userSpaceOnUse"><path + id="path3300" + d="m 1000,530 26.01,0 0,-3 -26.01,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3272" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3274" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3260" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3262"><g + id="g3264" + clip-path="url(#clipPath3256)"><g + id="g3266"><g + id="g3268" + transform="matrix(62,0,0,84,1107,495)"><image + id="image3270" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3256" + clipPathUnits="userSpaceOnUse"><path + id="path3258" + d="m 1107,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3252" + clipPathUnits="userSpaceOnUse"><path + id="path3254" + d="m 1107,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3226" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3228" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3214" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3216"><g + id="g3218" + clip-path="url(#clipPath3210)"><g + id="g3220"><g + id="g3222" + transform="matrix(62,0,0,84,1049,495)"><image + id="image3224" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3210" + clipPathUnits="userSpaceOnUse"><path + id="path3212" + d="m 1049,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3206" + clipPathUnits="userSpaceOnUse"><path + id="path3208" + d="m 1049,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3180" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3182" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3168" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3170"><g + id="g3172" + clip-path="url(#clipPath3164)"><g + id="g3174"><g + id="g3176" + transform="matrix(62,0,0,84,991,495)"><image + id="image3178" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3164" + clipPathUnits="userSpaceOnUse"><path + id="path3166" + d="m 991,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3160" + clipPathUnits="userSpaceOnUse"><path + id="path3162" + d="m 991,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3152" + clipPathUnits="userSpaceOnUse"><path + id="path3154" + d="m 897,482 239.999,0 0,135 -239.999,0 0,-135 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3126" + clipPathUnits="userSpaceOnUse"><path + id="path3128" + d="m 887,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3118" + clipPathUnits="userSpaceOnUse"><path + id="path3120" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3634-8" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3636-7" + d="m 570.26,532 c 0,-30.785 24.955,-55.742 55.74,-55.742 l 0,0 c 30.784,0 55.74,24.957 55.74,55.742 l 0,0 c 0,30.784 -24.956,55.74 -55.74,55.74 l 0,0 c -30.785,0 -55.74,-24.956 -55.74,-55.74" /></clipPath><clipPath + id="clipPath3642-3" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3644-4" + d="m 558.314,543.945 139.351,0 0,-78.633 -139.351,0 0,78.633 z" /></clipPath><mask + id="mask3650-5" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3652-7"><g + id="g3654-7" + clip-path="url(#clipPath3646-3)"><g + id="g3656-5"><g + id="g3658-1" + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)"><image + id="image3660-3" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAABCJJREFUeJzt3UFvskwYheEDAmNQYkq7MLab/v//1bhQUgqMwjDMt3gzBJTmy7Ol50omNSLdcAcfcEEAwGFBEARLb9Mf4dxiFgjwEEwQBGMs09f0Nzjnxlimr73Iv/BhhGH4tKbbaZ18GMMwPK3p9mi6UxiGiKIIcRyPK4oiRrNy01istTDGwBiDrutgrZ1FEwH/QvCxKKWQpil2ux12ux2UUmM0DGadnHNjLG3bQmuNpmmgtcb9fkff97DWAsA8mDiOkaYp8jzH6+sr8jxHlmVQSmGz2XCmWSE/pwzDgLZt0TQNvr+/cb1eURTFbDsARNPZJY5j7Pd7vL294ePjA6fTCXmeI01TxHE8fjXRujjn0Pc9brcbyrLE+XxGHMew1qLrOhhjYK2Ftfb3M8zpdMLn5yeOxyOyLEOSJPxaWqlhGGCMgdYal8sFSikYY1DXNaqqwu12gzEGQRAszzD7/R55nuN4POL9/R2HwwHb7ZbBrNQwDOi6Dk3TIEkS3O93XC4XpGmKJEnGcQT45SrJD75ZluFwOODl5WU2x9B6+Pmk6zpEUQStNdI0fbrYeQrGvxmGITabDeI4RpIkUEpBKYXtdstgVsgHE4Yh2rad3UbxA+/0Bl70+A98NNN4povBrItzbnbMnXOw1qLv+/FyehiG34Px/BlnadF6DcMwhuL/Tn8q4HUyAZjfj/E38fzr6VcSg6HR48zyOL8ADIYWLIXiMRgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBI5NfHEP/fwyJpHaTH+SkY/yha/9daOy4AfG71yvgHm/s1PfZL4UTTHaeRGGPQdR3atkXbtgCAzWbDYFbGB+OPc9d1MMbM4pmGMzvD+Keit20LrTWqqkJZlgCA7XaLMAwZzMr4k8T9fkdZlqiqClprtG2Lvu8xDMPs89F0Jx9LXdcoigLn8xkA8PPzgyRJGMwK+WPfdR2qqsL5fEZRFKjrehaNP8vMgjHGQGuNoijw9fUFACjLEmmaIo5jhCEvqtZo6dgXRQGtNYwx82CccwiCYNyprmtcLhc459A0DbIsg1JqnF94hlkXP6P4OaaqKhRFgev1irqux2D8ZxfPMADQti3KsoRSClEU8etoxR5HkqZp0DTN4hkmAOCAf5fLYRgiiiLEcTwuH4v/DK2Pj8FHY4wZ1+MMMwsGAMIwfFrT7bRO02ge13T7GIw3nVM4s/w90/suS3d9n4IZNzCUP+23nwf+AzNnFIrywW/wAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3646-3" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3648-7" + d="m 558.314,543.945 139.351,0 0,-78.634 -139.351,0 0,78.634 z" /></clipPath><mask + id="mask3662-9" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3664-6" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAAAAAAc13L4AAAAAXNCSVQI5gpbmQAAAdJJREFUaIHt2rFu2zAUheFDirICJUZQt0OQdun7v1fgwRHqylJMkbyng1S0He5aarj/C/gDLWnhcQAAONSNfxTOwdXkkCBXjIP33vtqp0OIiAiIAMCHtm1DLQ0hJaWlCBjgfOj6x8cu+EoYKXGe5nsuCHC+7U+fT8euqfLckBKnH+8DKQgOvn368u311Le+ggVg/rie27KkUraTef3+cjzU+Z8kzZcu3caP5NZn5un08vX5oRJmmQ73S39o3PY2df3x+VPXVHlmZAlz3wXvHALgnG/aQ9c9VML4GDxIrt8ZOO980zR1MM47lpyLbBjArVXAAIDkkgvBOq/zP5EiRYTEDjDgGnaBAUgCe8FsGUbLMFqG0TKMlmG0DKNlGC3DaBlGyzBahtEyjJZhtAyjZRgtw2gZRsswWobRMoyWYbQMo2UYLcNoGUZru0r+fWX5//v7lwMAUCillCprHpZShMINQ0pJS4yoc+NfYlxSEXI9GclxHq+osxKh3K/jHLMACKDkeBvO+FlnP0NZxvNwi1mIAEqahzdcay2LJM3D2zAnIQKdpNuFU73NVYnj8H5LAq4ng3ituEbLcZrmJIQDXPWdXk4p5Q2zmwXj3radwF5Wr78ATrQFVxEFtwQAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><clipPath + id="clipPath3634-8-0" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3636-7-9" + d="m 570.26,532 c 0,-30.785 24.955,-55.742 55.74,-55.742 l 0,0 c 30.784,0 55.74,24.957 55.74,55.742 l 0,0 c 0,30.784 -24.956,55.74 -55.74,55.74 l 0,0 c -30.785,0 -55.74,-24.956 -55.74,-55.74" /></clipPath><clipPath + id="clipPath3642-3-4" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3644-4-2" + d="m 558.314,543.945 139.351,0 0,-78.633 -139.351,0 0,78.633 z" /></clipPath><mask + id="mask3650-5-0" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3652-7-6"><g + id="g3654-7-6" + clip-path="url(#clipPath3646-3-3)"><g + id="g3656-5-4"><g + id="g3658-1-6" + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)"><image + id="image3660-3-1" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAABCJJREFUeJzt3UFvskwYheEDAmNQYkq7MLab/v//1bhQUgqMwjDMt3gzBJTmy7Ol50omNSLdcAcfcEEAwGFBEARLb9Mf4dxiFgjwEEwQBGMs09f0Nzjnxlimr73Iv/BhhGH4tKbbaZ18GMMwPK3p9mi6UxiGiKIIcRyPK4oiRrNy01istTDGwBiDrutgrZ1FEwH/QvCxKKWQpil2ux12ux2UUmM0DGadnHNjLG3bQmuNpmmgtcb9fkff97DWAsA8mDiOkaYp8jzH6+sr8jxHlmVQSmGz2XCmWSE/pwzDgLZt0TQNvr+/cb1eURTFbDsARNPZJY5j7Pd7vL294ePjA6fTCXmeI01TxHE8fjXRujjn0Pc9brcbyrLE+XxGHMew1qLrOhhjYK2Ftfb3M8zpdMLn5yeOxyOyLEOSJPxaWqlhGGCMgdYal8sFSikYY1DXNaqqwu12gzEGQRAszzD7/R55nuN4POL9/R2HwwHb7ZbBrNQwDOi6Dk3TIEkS3O93XC4XpGmKJEnGcQT45SrJD75ZluFwOODl5WU2x9B6+Pmk6zpEUQStNdI0fbrYeQrGvxmGITabDeI4RpIkUEpBKYXtdstgVsgHE4Yh2rad3UbxA+/0Bl70+A98NNN4povBrItzbnbMnXOw1qLv+/FyehiG34Px/BlnadF6DcMwhuL/Tn8q4HUyAZjfj/E38fzr6VcSg6HR48zyOL8ADIYWLIXiMRgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBI5NfHEP/fwyJpHaTH+SkY/yha/9daOy4AfG71yvgHm/s1PfZL4UTTHaeRGGPQdR3atkXbtgCAzWbDYFbGB+OPc9d1MMbM4pmGMzvD+Keit20LrTWqqkJZlgCA7XaLMAwZzMr4k8T9fkdZlqiqClprtG2Lvu8xDMPs89F0Jx9LXdcoigLn8xkA8PPzgyRJGMwK+WPfdR2qqsL5fEZRFKjrehaNP8vMgjHGQGuNoijw9fUFACjLEmmaIo5jhCEvqtZo6dgXRQGtNYwx82CccwiCYNyprmtcLhc459A0DbIsg1JqnF94hlkXP6P4OaaqKhRFgev1irqux2D8ZxfPMADQti3KsoRSClEU8etoxR5HkqZp0DTN4hkmAOCAf5fLYRgiiiLEcTwuH4v/DK2Pj8FHY4wZ1+MMMwsGAMIwfFrT7bRO02ge13T7GIw3nVM4s/w90/suS3d9n4IZNzCUP+23nwf+AzNnFIrywW/wAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3646-3-3" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3648-7-6" + d="m 558.314,543.945 139.351,0 0,-78.634 -139.351,0 0,78.634 z" /></clipPath><mask + id="mask3662-9-4" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3664-6-5" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAAAAAAc13L4AAAAAXNCSVQI5gpbmQAAAdJJREFUaIHt2rFu2zAUheFDirICJUZQt0OQdun7v1fgwRHqylJMkbyng1S0He5aarj/C/gDLWnhcQAAONSNfxTOwdXkkCBXjIP33vtqp0OIiAiIAMCHtm1DLQ0hJaWlCBjgfOj6x8cu+EoYKXGe5nsuCHC+7U+fT8euqfLckBKnH+8DKQgOvn368u311Le+ggVg/rie27KkUraTef3+cjzU+Z8kzZcu3caP5NZn5un08vX5oRJmmQ73S39o3PY2df3x+VPXVHlmZAlz3wXvHALgnG/aQ9c9VML4GDxIrt8ZOO980zR1MM47lpyLbBjArVXAAIDkkgvBOq/zP5EiRYTEDjDgGnaBAUgCe8FsGUbLMFqG0TKMlmG0DKNlGC3DaBlGyzBahtEyjJZhtAyjZRgtw2gZRsswWobRMoyWYbQMo2UYLcNoGUZru0r+fWX5//v7lwMAUCillCprHpZShMINQ0pJS4yoc+NfYlxSEXI9GclxHq+osxKh3K/jHLMACKDkeBvO+FlnP0NZxvNwi1mIAEqahzdcay2LJM3D2zAnIQKdpNuFU73NVYnj8H5LAq4ng3ituEbLcZrmJIQDXPWdXk4p5Q2zmwXj3radwF5Wr78ATrQFVxEFtwQAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><clipPath + id="clipPath3592-5" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3594-9" + d="m 730.778,549 12,0 0,-12 -12,0 0,12 z" /></clipPath><clipPath + id="clipPath3566-8" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3568-8" + d="m 679,547.865 32,0 0,-12.002 -32,0 0,12.002 z" /></clipPath><clipPath + id="clipPath3570-6" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3572-0" + d="m 679,535.865 32,0 0,12 -32,0 0,-12 z" /></clipPath><clipPath + id="clipPath3520-6" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3522-3" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3528-3" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3530-7"><g + id="g3532-3" + clip-path="url(#clipPath3524-5)"><g + id="g3534-0"><g + id="g3536-4" + transform="matrix(93,0,0,68,667,493)"><image + id="image3538-4" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAA51JREFUeJztnEtv4koQRo/BpokBoThZIJJN/v/vCkoUYuSAbYwfPYu57duOzWPm3lEvpo5UwqLbXhyXihaLzwM0A3ieN/S1cCNaD2oFwOObdM/zWuH2tXAbWutWuH1t45sLI3c0GvXKXheGMXKbpumVvQ6WdPgp3Pd9giBoy/d9EX8FW3hd15RlSVmWnE4n6rruiffhp0wjXClFGIbMZjNmsxlKqVa8SB9Ga90KL4qCLMvIsow0TTkej1RVRV3X7f6O9CAICMOQKIp4eHggiiIWiwVKKcbjscz4AczcbpqG0+lEmqbsdjviOGY0GrXrZo/WGt+e5UEQMJ/PeXx85Pn5mfV6TRRFhGFIEATtmBG6aK2pqorj8UiSJLy/v6OUomkayrK8vdPX6zUvLy+sVisWiwWTyURGzBmapqGqKvI8J45jwjBEa02e5+2IKcvy306H/kyfz+dEUcRqteLp6Ynlcsl0OhXpZzDSsyxjNpvRNA37/Z7tdst2u22nhBnPg6cX82O6WCxYLpfc39935rrQxYyX6XQKwH6/Z7lcMp/PW2/2aO6c003Hj8djgiBgMpmglEIpxXQ6FelnMNIB8jwnDEPu7u5QShEEQe8Q4n9/gBFvvwC7RHofc/42zer7flv2WDH0pBvMxqES+pgmtctuYBs5A/4BrjWoSHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASP8D2GlGQ5zNBrBvvPaQvx07LsouO0LKpifd3mjCwEyBJNYNYaJHTFJdVVVtGZeDEYH22zI3n04niqKgKAoAiR45g5GeZRl5nrefRVFQliV1XXfEdzrdxCKZmLv9fk+SJAASJ3UBO07q8/OTOI5JkoTD4UBRFJ1sRvhHuulwI/xwOBDHMW9vbwB8fX1JcNoFvgenvb6+8vHxQZIk5HneCU1rg9OM9LIsybKMOI7ZbDYAJEkiEYFXGIoI3Gw2xHFMmqYd6QC+1hrP81rph8OB7XaL1po0TSUM8wqXwjB3ux15nlNVVf+H9HunAxRFQZIkEvt6A5diX+2ZbsS3Ud52NqMEHP8atwQc26eXjnSQKO/f5VeivCW0/n/mltD6nvR2QWT/Jy79ZfIDbR7YvKqk0OoAAAAASUVORK5CYII=" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3524-5" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3526-4" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3540-4" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3542-1" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAAAAADGw2ZMAAAAAXNCSVQI5gpbmQAAAZJJREFUWIXt2DFv2zAQhuHvTlTkyDaMuBmCtEv//+9q0KKuAtWWZJnkfR0UAwY69jyVXAhoeEQcyOUVAAAEvos3rAjE0ydBfugCVVV1Oz9hZmYgEABoqOs6ePGE5Rgv2UAEiIamXa+boE665Xkch3PKQIBo3e4/7bdN5TJ70i7De6ckjUGg9eb5y+u+rdUBB5jO/Y/G4s3ZX7++bB98RmNp6lpOwzkal7lv9i+fdysvfVzb8XCoVWS5M0273T01lYvOtMJxt2kqBQIgolX90DQrLx1T+9jUlQgCAIiKVlXlpKOqQwgqsrwmALIsDx2iqioqAHxu4V8/+DjoffTrKnrRi170ohe96EUvetGLXvSiF/3/0bkkvWsnIHn98u+ymRmNV51Gyzn7RD2mHFNKxqUYkpbjZZ7h1WfGaZzmmEkEAJbm8djDr1z96vrTnA0IoKX51H3Hb8fq9u1nP0UjA2hx7N7QuxbDt26IRgSKxdOBg3PtfJ8Sl8nEEXPvXWrnbIQAcqfKTN65kN+97t9sfv6y/QG64fFP78GtbgAAAABJRU5ErkJggg==" + height="1" + width="1" /></mask><clipPath + id="clipPath3592-5-6" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3594-9-5" + d="m 730.778,549 12,0 0,-12 -12,0 0,12 z" /></clipPath><clipPath + id="clipPath3566-8-1" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3568-8-2" + d="m 679,547.865 32,0 0,-12.002 -32,0 0,12.002 z" /></clipPath><clipPath + id="clipPath3570-6-5" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3572-0-0" + d="m 679,535.865 32,0 0,12 -32,0 0,-12 z" /></clipPath><clipPath + id="clipPath3520-6-0" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3522-3-3" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3528-3-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3530-7-0"><g + id="g3532-3-9" + clip-path="url(#clipPath3524-5-5)"><g + id="g3534-0-9"><g + id="g3536-4-3" + transform="matrix(93,0,0,68,667,493)"><image + id="image3538-4-0" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAA51JREFUeJztnEtv4koQRo/BpokBoThZIJJN/v/vCkoUYuSAbYwfPYu57duOzWPm3lEvpo5UwqLbXhyXihaLzwM0A3ieN/S1cCNaD2oFwOObdM/zWuH2tXAbWutWuH1t45sLI3c0GvXKXheGMXKbpumVvQ6WdPgp3Pd9giBoy/d9EX8FW3hd15RlSVmWnE4n6rruiffhp0wjXClFGIbMZjNmsxlKqVa8SB9Ga90KL4qCLMvIsow0TTkej1RVRV3X7f6O9CAICMOQKIp4eHggiiIWiwVKKcbjscz4AczcbpqG0+lEmqbsdjviOGY0GrXrZo/WGt+e5UEQMJ/PeXx85Pn5mfV6TRRFhGFIEATtmBG6aK2pqorj8UiSJLy/v6OUomkayrK8vdPX6zUvLy+sVisWiwWTyURGzBmapqGqKvI8J45jwjBEa02e5+2IKcvy306H/kyfz+dEUcRqteLp6Ynlcsl0OhXpZzDSsyxjNpvRNA37/Z7tdst2u22nhBnPg6cX82O6WCxYLpfc39935rrQxYyX6XQKwH6/Z7lcMp/PW2/2aO6c003Hj8djgiBgMpmglEIpxXQ6FelnMNIB8jwnDEPu7u5QShEEQe8Q4n9/gBFvvwC7RHofc/42zer7flv2WDH0pBvMxqES+pgmtctuYBs5A/4BrjWoSHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASP8D2GlGQ5zNBrBvvPaQvx07LsouO0LKpifd3mjCwEyBJNYNYaJHTFJdVVVtGZeDEYH22zI3n04niqKgKAoAiR45g5GeZRl5nrefRVFQliV1XXfEdzrdxCKZmLv9fk+SJAASJ3UBO07q8/OTOI5JkoTD4UBRFJ1sRvhHuulwI/xwOBDHMW9vbwB8fX1JcNoFvgenvb6+8vHxQZIk5HneCU1rg9OM9LIsybKMOI7ZbDYAJEkiEYFXGIoI3Gw2xHFMmqYd6QC+1hrP81rph8OB7XaL1po0TSUM8wqXwjB3ux15nlNVVf+H9HunAxRFQZIkEvt6A5diX+2ZbsS3Ud52NqMEHP8atwQc26eXjnSQKO/f5VeivCW0/n/mltD6nvR2QWT/Jy79ZfIDbR7YvKqk0OoAAAAASUVORK5CYII=" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3524-5-5" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3526-4-5" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3540-4-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3542-1-9" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAAAAADGw2ZMAAAAAXNCSVQI5gpbmQAAAZJJREFUWIXt2DFv2zAQhuHvTlTkyDaMuBmCtEv//+9q0KKuAtWWZJnkfR0UAwY69jyVXAhoeEQcyOUVAAAEvos3rAjE0ydBfugCVVV1Oz9hZmYgEABoqOs6ePGE5Rgv2UAEiIamXa+boE665Xkch3PKQIBo3e4/7bdN5TJ70i7De6ckjUGg9eb5y+u+rdUBB5jO/Y/G4s3ZX7++bB98RmNp6lpOwzkal7lv9i+fdysvfVzb8XCoVWS5M0273T01lYvOtMJxt2kqBQIgolX90DQrLx1T+9jUlQgCAIiKVlXlpKOqQwgqsrwmALIsDx2iqioqAHxu4V8/+DjoffTrKnrRi170ohe96EUvetGLXvSiF/3/0bkkvWsnIHn98u+ymRmNV51Gyzn7RD2mHFNKxqUYkpbjZZ7h1WfGaZzmmEkEAJbm8djDr1z96vrTnA0IoKX51H3Hb8fq9u1nP0UjA2hx7N7QuxbDt26IRgSKxdOBg3PtfJ8Sl8nEEXPvXWrnbIQAcqfKTN65kN+97t9sfv6y/QG64fFP78GtbgAAAABJRU5ErkJggg==" + height="1" + width="1" /></mask><clipPath + id="clipPath3942-2" + clipPathUnits="userSpaceOnUse"><path + id="path3944-1" + d="m 1337.7,554.569 67.31,0 0,-64.569 -67.31,0 0,64.569 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3912-3" + clipPathUnits="userSpaceOnUse"><path + id="path3914-9" + d="m 1404.777,549.002 12,0 0,11.998 -12,0 0,-11.998 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3908-4" + clipPathUnits="userSpaceOnUse"><path + id="path3910-0" + d="m 1404.78,561 12,0 0,-12 -12,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3886-1" + clipPathUnits="userSpaceOnUse"><path + id="path3888-7" + d="m 1363,547.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3882-9" + clipPathUnits="userSpaceOnUse"><path + id="path3884-9" + d="m 1363,559.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3852-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3854-6" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAAAAACTooUSAAAAAXNCSVQI5gpbmQAAAXhJREFUWIXt2T9v3DAMBfBHWj4Fzh2CXjsEaZd+/+8VZGiN+v4Y1cni6+ADmqUjnQ7kqEE/ULQXPgEAQLBB8S8lAnE3SZCrKFBVVd8+CTMzA5EAaOr7PrmShC211sWABNGUh8fHnNRTtKVcrzMWY4JoPxw/Hw+585sl2cp5/AkzMgm033/59nIcevUCAavz+Cq3Upvce3z5/nzY+T0r7XZ+w/zrpMJ1jvvj89enB0/x94Rpn5PK/VvNw+HpU+78xFZwGnJSAAkQ0a7f5fzgKSLv+k5FgAQAoqJd1zmK6DqVdWppPZK1vES8u93xj/hHhRhiiCGGGGKIIYYYYoghhhhiiCGGGOL/Jt73ciRJuinvr08AQKO11vzCALbWjMa7SFqrt1LgutEtt9qMXHu0pcznCc5b6/NcFgOQQFvKZXzDyXszP17KYkQCrc7jKyb/9GGcqxGJYvXyg9ctEpZLNXDtEWXaJEWqRgggWyZl/IA08EMST2DTVPcPY4fdi0eNNAgAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3840-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3842-8"><g + id="g3844-1" + clip-path="url(#clipPath3836-1)"><g + id="g3846-7"><g + id="g3848-5" + transform="matrix(113,0,0,68,1351,505)"><image + id="image3850-5" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAYAAAC2ydrOAAAABHNCSVQICAgIfAhkiAAAA1NJREFUeJztnc1uozAURj8HjCMCikq7iNJu+v7vVXXRovJn1RDiWUztMQnttIuQmat7pKugAFn4yDZk8V0BwGIGIcTc18wVsXZWFQROJAohvMDwmLke1lovMDx2xO7AyVqtVmcVnmeWw8k6Ho9nFZ6Pw5tWqxXiOIaU0lccxyzyCoQCD4cDhmHwdTgcvEjgQ6IQwgtUSiFNU2w2G2w2GyilvEiWuBzWWi/QGIOu69B1HbTWAOBFWmunEqWUSNMURVHg9vYWRVEgz3MopRBFEe+RC+H2vXEcYYxB0zQoyxKvr68A/iyv7ro43AullMiyDHd3d3h4eMB+v0dRFEjTFFJKv6wyl+d4PGIYBmitUZYlnp6eIIRA3/cwxmAYBozjCCHE5zNxv9/j8fERu90OeZ4jSRJeUhfCLaV936NpGjw/PwMAtNZ4e3tDXdfexdly6vbELMtQFAV2ux3u7++x3W6xXq9Z4kI4ie/v76iqCgBQVRWyLJt9Rpl9OnUPN3meY7vd4ubmZrIvMpcl3A8BoK5rpGk6ERgyeU90MzKKIkgpkSQJlFJQSmG9XrPEhXASAUAphSRJIKVEFEV+BoYe4tMfcCJDoWGxxMvj3hHdmIc+5sb/TKLD2Z4r5vL8ZNz5nYEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEALJEAn2a7uWji02Iuz0/H/kyiC0x1n+M4+gI4kX8JXFSmq9DHnMw4vDEUNwyDz5t24akclbkMYWitMQZ93/vM7zDA3TGZiWGEv9YaTdP4+GKOj16O0/jopmmgtYYx5qwnBvAh8bQHQ9u2KMvSB4jXdc1B7gsyF+ReliXatp2InHSocTedRvgDvwPEuaXC8sz5KMsSWmsMwzCVaK2FEMLf1LYtXl5eYK1F13Xc3OQKfNXcpG1bL9FdOzsTAcAYg6qquM3QlfiqzdDpTPSt98LeGNzw6/p8p+HXrESAW+/9S3y39R43wfwP+FsTzDOJ/gTL++f47K+3XyWNnXDxB1SBAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3836-1" + clipPathUnits="userSpaceOnUse"><path + id="path3838-6" + d="m 1351,573 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3832-3" + clipPathUnits="userSpaceOnUse"><path + id="path3834-3" + d="m 1351,573 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3824-4" + clipPathUnits="userSpaceOnUse"><path + id="path3826-5" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3798-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3800-3" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAAAAACTooUSAAAAAXNCSVQI5gpbmQAAAZ9JREFUWIXt2U9v1DAQBfA348lmyXa1FdBSgRDf/4tVe4CI7J+oXtvzOKQIOCOnF8/Ft/lpZCeHeQIAgGCF4h9KBFLdJEEuokBVVevOSbi7OwiDiIZN14WqJOE5pZQdMEBtO+yGPmhN0XO8Xmdkp4mEfv/+w/2u13p3SZZ4Hn/AnTRo2B6evjwd3lnFGT3N47PcYipiIrY9PH77+nHotBpIv52PmH+eVGiQsBnuHz5/2m1qii8TprveVJa3av2wP9xt6j0dlojT0JsCMJAk1LptVRH9pgsqAhjoJedC0YqfBxGCytLfCJZcsgM1/3Qi8ru7gnQv7mQ17t9SgEutBEIBYEXvVVy1mtjEJjaxiU1sYhOb2MQmNrGJTWxiE5v4P2XLUXlt9Xd7AwA6vZRSLwxgKcXpfBVJL+kWI0LNjW68peLkMqPnOJ8nbCvuV/1lOs8xO5aNbo6X8YhTzR2y387H8RKzEwZ6msdnTDWzgCV9GOfkhFE8Xb7zuu9D9YTlkhxcZkScelshRUpOCCBqXdfZOkkZ3yANfJPEE1g11f0FocoCRBRb0tkAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3786-0" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3788-7"><g + id="g3790-3" + clip-path="url(#clipPath3782-0)"><g + id="g3792-9"><g + id="g3794-3" + transform="matrix(113,0,0,68,1351,444)"><image + id="image3796-9" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAABECAYAAAC2ydrOAAAABHNCSVQICAgIfAhkiAAAA59JREFUeJztnclurDoURTdgmkAhSmlLiaL8/49FGSQodFZM5zd4sstUkdsMQnKPzpKsQgVMWDo2ZrCPB0BjBc/z1v5mvhGtV1XBw4lEz/OsQPeY+T601lage2wQ5sDI8n3/bLjnme0wsuZ5PhvueQEcK873fQRBgCiKEIYhwjBEEAQs8htwBY7jiGEY7BjH0YoEnEr0fR9CCCRJgjRNkWUZ0jRFHMdWJEvcDq21FaiUQtd16LoOUkoAsCK11hCmCoMgQBzHyPMcl5eXuLq6wn6/R5ZliOPYSmSRX49Z96ZpglIKTdOgLEu8vb0BOE6v5joBwE6jSZKgKAocDgc8PDzgcDigKApcXFxACMECN2SeZwzDACklyrLE8/MzPM9D3/dQSmEYBkzTBM/zjpVoptKiKHB7e4unpyc8Pj7i+voaaZoiDEO7NjJfi5lK+75H0zR4eXkBAEgp8f7+jrqu7cxoK9FMp1EUIU1T7Pd73Nzc4P7+Hnd3d8iyDFEUscSNMBI/Pj5QVRUAoKoq7HY7xHEMIcTiHeXs7VQIgTiOkaYp8jxHURTY7XZWIk+pX4+7HgJAXdf2JdMIdBHmJncAx7fVMAyRJAlL3BAjEQDiOLZbPneX4HqwEud5xjRNGMcR4zhimiZorRf7R5a4DaaQgiBYPPfPnr9wP+cYiebX3VAC/BluS8yzdsdn+MBxOjX7j2mazvYizM/FrpCn6+LpGsn8XM72DCzv34M3fgRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQRgiQQQn53gdKnv42+f/ZlEk/HmJi+aAXAi/xaYsEQzXB9rMoV7oytuGAabN23CU4MgYIkb4IbWKqXQ973N/F4LTVxUohvhL6VE0zQ2vjhJEs473YjT+OimaSClhFJqNcJ0EVprBLZti7IsbYB4XdecPLwha0HuZVmibduFyEWHGnPTaYQ/8H+AOKfxb8+aj7IsIaXEMAxLiSYi2tzUti1eX1+htUbXdcjz3Hap4eThbfhVc5O2ba1Ec+1qJQKAUgpVVa1G+DNfz6/aDJ1Wom2957ZUMM2+wjBcRPizxO34k4ZfqxIBbr33k/jT1nvcBPMf4HdNMM8k2hMs78fx2ae3/wBR6gt9OOvm0AAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3782-0" + clipPathUnits="userSpaceOnUse"><path + id="path3784-8" + d="m 1351,512 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3778-1" + clipPathUnits="userSpaceOnUse"><path + id="path3780-0" + d="m 1351,512 113,0 0,-68 -113,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3770-5" + clipPathUnits="userSpaceOnUse"><path + id="path3772-0" + d="m 1341,490 129,0 0,89 -129,0 0,-89 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3748-6" + clipPathUnits="userSpaceOnUse"><path + id="path3750-3" + d="m 1214,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3726-6" + clipPathUnits="userSpaceOnUse"><path + id="path3728-1" + d="m 652.002,520.002 12,0 0,11.998 -12,0 0,-11.998 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3722-1" + clipPathUnits="userSpaceOnUse"><path + id="path3724-4" + d="m 652,532 12.002,0 0,-12 -12.002,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3700-1" + clipPathUnits="userSpaceOnUse"><path + id="path3702-8" + d="m 592,518.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3696-9" + clipPathUnits="userSpaceOnUse"><path + id="path3698-6" + d="m 592,530.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3688-4" + clipPathUnits="userSpaceOnUse"><path + id="path3690-8" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3662-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3664-9" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAAAAAAc13L4AAAAAXNCSVQI5gpbmQAAAdJJREFUaIHt2rFu2zAUheFDirICJUZQt0OQdun7v1fgwRHqylJMkbyng1S0He5aarj/C/gDLWnhcQAAONSNfxTOwdXkkCBXjIP33vtqp0OIiAiIAMCHtm1DLQ0hJaWlCBjgfOj6x8cu+EoYKXGe5nsuCHC+7U+fT8euqfLckBKnH+8DKQgOvn368u311Le+ggVg/rie27KkUraTef3+cjzU+Z8kzZcu3caP5NZn5un08vX5oRJmmQ73S39o3PY2df3x+VPXVHlmZAlz3wXvHALgnG/aQ9c9VML4GDxIrt8ZOO980zR1MM47lpyLbBjArVXAAIDkkgvBOq/zP5EiRYTEDjDgGnaBAUgCe8FsGUbLMFqG0TKMlmG0DKNlGC3DaBlGyzBahtEyjJZhtAyjZRgtw2gZRsswWobRMoyWYbQMo2UYLcNoGUZru0r+fWX5//v7lwMAUCillCprHpZShMINQ0pJS4yoc+NfYlxSEXI9GclxHq+osxKh3K/jHLMACKDkeBvO+FlnP0NZxvNwi1mIAEqahzdcay2LJM3D2zAnIQKdpNuFU73NVYnj8H5LAq4ng3ituEbLcZrmJIQDXPWdXk4p5Q2zmwXj3radwF5Wr78ATrQFVxEFtwQAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3650-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3652-6"><g + id="g3654-8" + clip-path="url(#clipPath3646-6)"><g + id="g3656-3"><g + id="g3658-9" + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)"><image + id="image3660-2" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAABCJJREFUeJzt3UFvskwYheEDAmNQYkq7MLab/v//1bhQUgqMwjDMt3gzBJTmy7Ol50omNSLdcAcfcEEAwGFBEARLb9Mf4dxiFgjwEEwQBGMs09f0Nzjnxlimr73Iv/BhhGH4tKbbaZ18GMMwPK3p9mi6UxiGiKIIcRyPK4oiRrNy01istTDGwBiDrutgrZ1FEwH/QvCxKKWQpil2ux12ux2UUmM0DGadnHNjLG3bQmuNpmmgtcb9fkff97DWAsA8mDiOkaYp8jzH6+sr8jxHlmVQSmGz2XCmWSE/pwzDgLZt0TQNvr+/cb1eURTFbDsARNPZJY5j7Pd7vL294ePjA6fTCXmeI01TxHE8fjXRujjn0Pc9brcbyrLE+XxGHMew1qLrOhhjYK2Ftfb3M8zpdMLn5yeOxyOyLEOSJPxaWqlhGGCMgdYal8sFSikYY1DXNaqqwu12gzEGQRAszzD7/R55nuN4POL9/R2HwwHb7ZbBrNQwDOi6Dk3TIEkS3O93XC4XpGmKJEnGcQT45SrJD75ZluFwOODl5WU2x9B6+Pmk6zpEUQStNdI0fbrYeQrGvxmGITabDeI4RpIkUEpBKYXtdstgVsgHE4Yh2rad3UbxA+/0Bl70+A98NNN4povBrItzbnbMnXOw1qLv+/FyehiG34Px/BlnadF6DcMwhuL/Tn8q4HUyAZjfj/E38fzr6VcSg6HR48zyOL8ADIYWLIXiMRgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBIhMGQCIMhEQZDIgyGRBgMiTAYEmEwJMJgSITBkAiDIREGQyIMhkQYDIkwGBJhMCTCYEiEwZAIgyERBkMiDIZEGAyJMBgSYTAkwmBI5NfHEP/fwyJpHaTH+SkY/yha/9daOy4AfG71yvgHm/s1PfZL4UTTHaeRGGPQdR3atkXbtgCAzWbDYFbGB+OPc9d1MMbM4pmGMzvD+Keit20LrTWqqkJZlgCA7XaLMAwZzMr4k8T9fkdZlqiqClprtG2Lvu8xDMPs89F0Jx9LXdcoigLn8xkA8PPzgyRJGMwK+WPfdR2qqsL5fEZRFKjrehaNP8vMgjHGQGuNoijw9fUFACjLEmmaIo5jhCEvqtZo6dgXRQGtNYwx82CccwiCYNyprmtcLhc459A0DbIsg1JqnF94hlkXP6P4OaaqKhRFgev1irqux2D8ZxfPMADQti3KsoRSClEU8etoxR5HkqZp0DTN4hkmAOCAf5fLYRgiiiLEcTwuH4v/DK2Pj8FHY4wZ1+MMMwsGAMIwfFrT7bRO02ge13T7GIw3nVM4s/w90/suS3d9n4IZNzCUP+23nwf+AzNnFIrywW/wAAAAAElFTkSuQmCC" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3646-6" + clipPathUnits="userSpaceOnUse"><path + id="path3648-8" + d="m 558.314,543.945 139.351,0 0,-78.634 -139.351,0 0,78.634 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3642-7" + clipPathUnits="userSpaceOnUse"><path + id="path3644-3" + d="m 558.314,543.945 139.351,0 0,-78.633 -139.351,0 0,78.633 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3634-3" + clipPathUnits="userSpaceOnUse"><path + id="path3636-8" + d="m 570.26,532 c 0,-30.785 24.955,-55.742 55.74,-55.742 l 0,0 c 30.784,0 55.74,24.957 55.74,55.742 l 0,0 c 0,30.784 -24.956,55.74 -55.74,55.74 l 0,0 c -30.785,0 -55.74,-24.956 -55.74,-55.74" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3610-5" + clipPathUnits="userSpaceOnUse"><path + id="path3612-4" + d="m 652.333,574.333 101,0 0,-79.333 -101,0 0,79.333 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3592-6" + clipPathUnits="userSpaceOnUse"><path + id="path3594-98" + d="m 730.778,549 12,0 0,-12 -12,0 0,12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3570-0" + clipPathUnits="userSpaceOnUse"><path + id="path3572-8" + d="m 679,535.865 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3566-5" + clipPathUnits="userSpaceOnUse"><path + id="path3568-3" + d="m 679,547.865 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3540-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3542-0" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAAAAADGw2ZMAAAAAXNCSVQI5gpbmQAAAZJJREFUWIXt2DFv2zAQhuHvTlTkyDaMuBmCtEv//+9q0KKuAtWWZJnkfR0UAwY69jyVXAhoeEQcyOUVAAAEvos3rAjE0ydBfugCVVV1Oz9hZmYgEABoqOs6ePGE5Rgv2UAEiIamXa+boE665Xkch3PKQIBo3e4/7bdN5TJ70i7De6ckjUGg9eb5y+u+rdUBB5jO/Y/G4s3ZX7++bB98RmNp6lpOwzkal7lv9i+fdysvfVzb8XCoVWS5M0273T01lYvOtMJxt2kqBQIgolX90DQrLx1T+9jUlQgCAIiKVlXlpKOqQwgqsrwmALIsDx2iqioqAHxu4V8/+DjoffTrKnrRi170ohe96EUvetGLXvSiF/3/0bkkvWsnIHn98u+ymRmNV51Gyzn7RD2mHFNKxqUYkpbjZZ7h1WfGaZzmmEkEAJbm8djDr1z96vrTnA0IoKX51H3Hb8fq9u1nP0UjA2hx7N7QuxbDt26IRgSKxdOBg3PtfJ8Sl8nEEXPvXWrnbIQAcqfKTN65kN+97t9sfv6y/QG64fFP78GtbgAAAABJRU5ErkJggg==" + height="1" + width="1" /></mask><mask + id="mask3528-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3530-9"><g + id="g3532-7" + clip-path="url(#clipPath3524-3)"><g + id="g3534-3"><g + id="g3536-8" + transform="matrix(93,0,0,68,667,493)"><image + id="image3538-8" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAA51JREFUeJztnEtv4koQRo/BpokBoThZIJJN/v/vCkoUYuSAbYwfPYu57duOzWPm3lEvpo5UwqLbXhyXihaLzwM0A3ieN/S1cCNaD2oFwOObdM/zWuH2tXAbWutWuH1t45sLI3c0GvXKXheGMXKbpumVvQ6WdPgp3Pd9giBoy/d9EX8FW3hd15RlSVmWnE4n6rruiffhp0wjXClFGIbMZjNmsxlKqVa8SB9Ga90KL4qCLMvIsow0TTkej1RVRV3X7f6O9CAICMOQKIp4eHggiiIWiwVKKcbjscz4AczcbpqG0+lEmqbsdjviOGY0GrXrZo/WGt+e5UEQMJ/PeXx85Pn5mfV6TRRFhGFIEATtmBG6aK2pqorj8UiSJLy/v6OUomkayrK8vdPX6zUvLy+sVisWiwWTyURGzBmapqGqKvI8J45jwjBEa02e5+2IKcvy306H/kyfz+dEUcRqteLp6Ynlcsl0OhXpZzDSsyxjNpvRNA37/Z7tdst2u22nhBnPg6cX82O6WCxYLpfc39935rrQxYyX6XQKwH6/Z7lcMp/PW2/2aO6c003Hj8djgiBgMpmglEIpxXQ6FelnMNIB8jwnDEPu7u5QShEEQe8Q4n9/gBFvvwC7RHofc/42zer7flv2WDH0pBvMxqES+pgmtctuYBs5A/4BrjWoSHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASP8D2GlGQ5zNBrBvvPaQvx07LsouO0LKpifd3mjCwEyBJNYNYaJHTFJdVVVtGZeDEYH22zI3n04niqKgKAoAiR45g5GeZRl5nrefRVFQliV1XXfEdzrdxCKZmLv9fk+SJAASJ3UBO07q8/OTOI5JkoTD4UBRFJ1sRvhHuulwI/xwOBDHMW9vbwB8fX1JcNoFvgenvb6+8vHxQZIk5HneCU1rg9OM9LIsybKMOI7ZbDYAJEkiEYFXGIoI3Gw2xHFMmqYd6QC+1hrP81rph8OB7XaL1po0TSUM8wqXwjB3ux15nlNVVf+H9HunAxRFQZIkEvt6A5diX+2ZbsS3Ud52NqMEHP8atwQc26eXjnSQKO/f5VeivCW0/n/mltD6nvR2QWT/Jy79ZfIDbR7YvKqk0OoAAAAASUVORK5CYII=" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3524-3" + clipPathUnits="userSpaceOnUse"><path + id="path3526-7" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3520-9" + clipPathUnits="userSpaceOnUse"><path + id="path3522-1" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3498-7" + clipPathUnits="userSpaceOnUse"><path + id="path3500-1" + d="m 560,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3490-4" + clipPathUnits="userSpaceOnUse"><path + id="path3492-5" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3476-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3478-3" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHcAAAK/CAAAAADGr34kAAAAAXNCSVQI5gpbmQAACLFJREFUeJzt1DEBACAMwDDAv+fhohwkCnp1zwJIndcBwHd8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4DaBSkuBn0AocgUAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><clipPath + id="clipPath3470-9" + clipPathUnits="userSpaceOnUse"><path + id="path3472-6" + d="m 538,215 954,0 0,118 -954,0 0,-118 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3460-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3462-0" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHcAAAK/CAAAAADGr34kAAAAAXNCSVQI5gpbmQAACLFJREFUeJzt1DEBACAMwDDAv+fhohwkCnp1zwJIndcBwHd8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4Ca7wA13wFqvgPUfAeo+Q5Q8x2g5jtAzXeAmu8ANd8Bar4D1HwHqPkOUPMdoOY7QM13gJrvADXfAWq+A9R8B6j5DlDzHaDmO0DNd4DaBSkuBn0AocgUAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><clipPath + id="clipPath3454-4" + clipPathUnits="userSpaceOnUse"><path + id="path3456-3" + d="m 554,720 958,0 0,204 -958,0 0,-204 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3432-5" + clipPathUnits="userSpaceOnUse"><path + id="path3434-1" + d="m 904,587 32,0 0,12 -32,0 0,-12 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3428-5" + clipPathUnits="userSpaceOnUse"><path + id="path3430-0" + d="m 904,599 32,0 0,-12.002 -32,0 0,12.002 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3420-8" + clipPathUnits="userSpaceOnUse"><path + id="path3422-5" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3396-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3398-2" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAACdCAAAAAAcMECVAAAAAXNCSVQI5gpbmQAAAZdJREFUaIHt27tu20AQheEzy6Vo0BKMKCkMJ03e/70MFzYR3YisljsnBQXETZpg1J0pt/gwGGz7GwAAhtjhX9UMFsmTIFfckFJKKWx7wt3dQWQAKfd9n6N0wpda6+JAhqU8jI+PQ05BuC/lcpmxODMs9eP+6343dCF3J1s5TR9wJ7Mh9dtvP172Y58CbMDrPL3atdRmt81ffj7vNiF3oV9Pb5h/HZNxvfl2//z96SEI/33AYTvkZLffMoy7py9DF4K3guM45AQgA2ap6zfD8BCEY9j0XTIDMgBYstR1XQyOrku2XjivT7ZOAI5PUMz3+8cIFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwoULFy5cuHDhwv97bkEBSZIR4GcpAwCd3loLaX7YWnM6bzjprV5LQVQhUq61Oblu7kuZTwfEtS2nuSwOIIO+lPP0hmNglTOdy+JEBr3O0ysOoT3RNFcnMs3r+Z2X4BLqXB1cN0c5RDdc1QkD7E71Ge/bzd27+APu1Sr+ATqP3j0zgxPtAAAAAElFTkSuQmCC" + height="1" + width="1" /></mask><mask + id="mask3384-8" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3386-8"><g + id="g3388-5" + clip-path="url(#clipPath3380-6)"><g + id="g3390-8"><g + id="g3392-8" + transform="matrix(92,0,0,157,888,469)"><image + id="image3394-9" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAACdCAYAAAA5Wx9JAAAABHNCSVQICAgIfAhkiAAABIZJREFUeJzt111rs0gYxvHL92AipbYHIe1Jv//3CjlopMYXMmqcPdgdH03s7lOWvWDh+sPQUDWUX4c7Ew+AxUqe5639Wv1m1q6ywsMduOd5E/b8tfq9rLUT9vy1K3QvHKzv+w9rfl2t52DHcXxY8+vh/CHf9xGGIaIomlYYhkL/h+bYwzCg7/tpDcMwoQN/gXueN2EnSYI0TbHdbrHdbpEkyYQu8PWstRO2MQZN06BpGrRtCwATurV2CR5FEdI0RZ7neHl5QZ7nyLIMSZIgCALN9JXcnL7dbjDGoKoqFEWB8/kM4NeIcfeF89kdRRF2ux1eX1/x/v6Ow+GAPM+RpimiKJpGi1o2jiP6vkfbtiiKAsfjEZ7noes6GGPQ9z1utxs8z/t+hx8OB3x8fGC/3yPLMsRxrLGykhsnXdehqiqcTicAQNu2+Pr6wuVymdweRoqb4bvdDnmeY7/f4+3tDU9PT9hsNgJfyYFfr1eUZQkAKMsSu91u9fNv9ZTiPjizLMPT0xOen58Xc1z9aj6/AeByuSBN0wX2vMU53O30IAgQRRHiOEaSJEiSBJvNRuArOXAASJIEcRwjiiIEQTDt7LlZeP8GDn2OP18CX+bO4M5nbrdm9QDucv+ZtaWW/cRI5zxyAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInJ3ByAicncHICJydwcgInF353wVq7utSynzo9gFtrMY7j9PN2u00LADzP++/++v9h1tqF0dxuDT6cPzhH7vseXdfBGANjDAAgCAKB3+XAnVPXdej7foE/h1/s8HEcMQwDjDFo2xZVVaEsSwDAZrOB7/sCv8tt0uv1irIsUVUV2raFMQbDMGAcx8X94fwhh13XNYqiwOl0AgBcLhfEcSzwlZxd13Woqgqn0wlFUaCu6wW62+UL8L7v0bYtiqLA8XgEAJRliTRNEUURfF+HmrXW7IqiQNu26Pt+CW6thed500N1XePz8xPWWjRNgyzLkCTJNL+1w5e5Ge3meFVVKIoC5/MZdV1P4O7e1R0OAMYYlGWJJEkQhqHGyd90P5KbpkHTNKs73ANggT+Pe77vIwxDRFE0LYft7lGPOUyH3vf9tO5n+AIcAHzff1jz62q9Ofr9ml+fwF3zOa2Z/fPm5+61b50P4NMFQf+rvvt6/we8Kp4iURf8WgAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3380-6" + clipPathUnits="userSpaceOnUse"><path + id="path3382-6" + d="m 888,626 92,0 0,-157 -92,0 0,157 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3376-1" + clipPathUnits="userSpaceOnUse"><path + id="path3378-8" + d="m 888,626 92,0 0,-157 -92,0 0,157 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3354-1" + clipPathUnits="userSpaceOnUse"><path + id="path3356-9" + d="m 1116,527 26.006,0 0,3 -26.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3350-2" + clipPathUnits="userSpaceOnUse"><path + id="path3352-7" + d="m 1116,530 26.01,0 0,-3 -26.01,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3328-2" + clipPathUnits="userSpaceOnUse"><path + id="path3330-1" + d="m 1057.994,527 11.006,0 0,3 -11.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3324-6" + clipPathUnits="userSpaceOnUse"><path + id="path3326-2" + d="m 1057.99,530 26,0 0,-3 -26,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3302-2" + clipPathUnits="userSpaceOnUse"><path + id="path3304-7" + d="m 1000,527 26.006,0 0,3 -26.006,0 0,-3 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3298-9" + clipPathUnits="userSpaceOnUse"><path + id="path3300-4" + d="m 1000,530 26.01,0 0,-3 -26.01,0 0,3 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3272-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3274-5" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3260-5" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3262-2"><g + id="g3264-6" + clip-path="url(#clipPath3256-0)"><g + id="g3266-6"><g + id="g3268-0" + transform="matrix(62,0,0,84,1107,495)"><image + id="image3270-6" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3256-0" + clipPathUnits="userSpaceOnUse"><path + id="path3258-5" + d="m 1107,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3252-1" + clipPathUnits="userSpaceOnUse"><path + id="path3254-3" + d="m 1107,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3226-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3228-8" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3214-5" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3216-2"><g + id="g3218-9" + clip-path="url(#clipPath3210-9)"><g + id="g3220-8"><g + id="g3222-2" + transform="matrix(62,0,0,84,1049,495)"><image + id="image3224-4" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3210-9" + clipPathUnits="userSpaceOnUse"><path + id="path3212-3" + d="m 1049,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3206-7" + clipPathUnits="userSpaceOnUse"><path + id="path3208-1" + d="m 1049,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><mask + id="mask3180-7" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3182-1" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAAAAAD/xdukAAAAAXNCSVQI5gpbmQAAAZZJREFUWIXt2L1u3DAQBOBZijoZsg9GLikMO03e/72MK85CLvrxUeTuuJCCJB0lA6m4tT4tl6x2BAAAwdbiHycC2fYDEuTCBc455zacgDAzMxAegPN1Xft8T5jGOKuBHuJ8097fN95lc9MwjdMtKTzE1e3p6+nYVJnzkxbGn28dafACVz98+/58amuX2z29X8+1zlF17f784+l4yD29xenSxKF/j7LM/nB6enm8y+bzeLhd2kMl68037fHxS1Nlzm6zn9rGOxF4QMRV9aFp7rK5C96B5PLuECeuqqpcLk6oKamtHJClsjgAWNKkBHMf69/+NFMzErs4uBR2coAksJ+vVXjhhRdeeOGFF1544YUXXvh/5usO+3srzKm/v/UAQKOpamZ0QlU1GldOmsY5BOQu4BrCHNXIpbulMPVX5IYPtNu1n0IyAB60FIbujF+50Qdt7s/dEJIRHrQ4da+45gcvFqfutZuiEZ5icbhw3BL7aOi7tyEauHRHuG4KnVIYxykaIYDsiLxSjDGt/BOB2+fjPmB/2PgBJp8FYb3sImYAAAAASUVORK5CYII=" + height="1" + width="1" /></mask><mask + id="mask3168-6" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3170-1"><g + id="g3172-4" + clip-path="url(#clipPath3164-3)"><g + id="g3174-6"><g + id="g3176-0" + transform="matrix(62,0,0,84,991,495)"><image + id="image3178-0" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABUCAYAAADaroR4AAAABHNCSVQICAgIfAhkiAAAA1pJREFUeJztnMtuqzoYRheES0QSRaUdRGknff/3qjJIUCmXYIzhDI6MTC5765wR1F6SFQvHESvmByb+PGDgAZ7nPTq8OIbhoR4eN+Ke543SZn9pDMMwSpt9TaA7WtD3/btmjs8dLdj3/V0zxwNzku/7BEFAGIZjC4JgMfKmtFIKKSVSStq2RSk1kQ/gXyEtHccxSZKw2WzYbDbEcTzKL0FcSwshqOuaqqqo65qmaei6DqUUwFQ8DEOSJCFNU15fX0nTlN1uRxzHrFarWde8ruO+7xFCUFUV39/fXC4XsiybjAMEZm2HYch2u+Xt7Y2Pjw+OxyNpmpIkCWEYjpf8XBmGga7ruF6v5HnO6XQiDEOUUrRti5QSpRRKqecrfjwe+fz85HA4sNvtiKJo9pd73/dIKanrmvP5TBzHSCkpy5KiKLher0gp8TzvcY1vt1vSNOVwOPD+/s5+v2e9Xi9CvG1bqqoiiiKapuF8PpMkCVEUjeUKT+7q+ga32+3Y7/e8vLxM6nyO6Ppt25YgCKjrmiRJ7m7Od+L6oO/7rFYrwjAkiiLiOCaOY9br9SLEfd9HCDF5DOsbm/kiE9z+gJY3/wSzzVncPPdhGFBK0XXd+Bjr+/65uEZfAY/aEuj7fhTWn+Yr7LyfT/8D83mtX2Z037zUf5043Nf0bX3DLxXXPBLW/GrxP+HEbcOJ24YTtw0nbhtO3DacuG04cdtw4rbhxG3DiduGE7cNJ24bTtw2nLhtOHHbcOK24cRtw4nbhhO3DSduG07cNpy4bThx23DituHEbcOJ24YTtw1rxZ/uLf3bTr258V/P905c78s0gyZ0g/kmg+jdw7qZDo/+gMCcaMrqGBEhBEIIgNlvo9ZpIEKISSbE7YZauFlxvfVYR4kURUGe5wCzj0rQi9Y0DXmeUxQFdV0jhKDrujENRBOYk7R0WZZkWcbpdALg5+dn9uEYZlRCURScTieyLKMsy4n8ZOO8nqQTNbIs4+vrC4A8zxcTh/LIIcsy6rpGSjkV1xEDelJZlpzPZ4ZhoKqqxQXg6DovioIsy7hcLpRlOYrr7z5ccQAhBHmeLzLySJdsVVVj5NHtio+xZmYWzG8Iueq6bgy5klLe1fhEHOyJNbM2yO5OfBxYqPAtz15b/wHsvBSUT5t0gwAAAABJRU5ErkJggg==" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3164-3" + clipPathUnits="userSpaceOnUse"><path + id="path3166-1" + d="m 991,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3160-9" + clipPathUnits="userSpaceOnUse"><path + id="path3162-3" + d="m 991,579 62,0 0,-84 -62,0 0,84 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3152-4" + clipPathUnits="userSpaceOnUse"><path + id="path3154-4" + d="m 897,482 239.999,0 0,135 -239.999,0 0,-135 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3126-1" + clipPathUnits="userSpaceOnUse"><path + id="path3128-0" + d="m 887,628 270,0 0,-182 -270,0 0,182 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3118-8" + clipPathUnits="userSpaceOnUse"><path + id="path3120-5" + d="m 0,1080 1920,0 L 1920,0 0,0 0,1080 z" + inkscape:connector-curvature="0" /></clipPath><clipPath + id="clipPath3722-1-6" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3724-4-6" + d="m 652,532 12.002,0 0,-12 -12.002,0 0,12 z" /></clipPath><clipPath + id="clipPath3726-6-9" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3728-1-7" + d="m 652.002,520.002 12,0 0,11.998 -12,0 0,-11.998 z" /></clipPath><clipPath + id="clipPath3696-9-7" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3698-6-3" + d="m 592,530.865 32,0 0,-12.002 -32,0 0,12.002 z" /></clipPath><clipPath + id="clipPath3700-1-1" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3702-8-2" + d="m 592,518.865 32,0 0,12 -32,0 0,-12 z" /></clipPath><clipPath + id="clipPath3696-9-7-9" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3698-6-3-5" + d="m 592,530.865 32,0 0,-12.002 -32,0 0,12.002 z" /></clipPath><clipPath + id="clipPath3700-1-1-8" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3702-8-2-6" + d="m 592,518.865 32,0 0,12 -32,0 0,-12 z" /></clipPath><clipPath + id="clipPath3722-1-6-1" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3724-4-6-5" + d="m 652,532 12.002,0 0,-12 -12.002,0 0,12 z" /></clipPath><clipPath + id="clipPath3726-6-9-4" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3728-1-7-6" + d="m 652.002,520.002 12,0 0,11.998 -12,0 0,-11.998 z" /></clipPath><clipPath + id="clipPath3086-4-0"><path + inkscape:connector-curvature="0" + d="m 1058,722 374,0 0,-374 -374,0 0,374 z" + id="path3088-8-7" /></clipPath><clipPath + id="clipPath3100-8-2"><path + inkscape:connector-curvature="0" + d="m 1029,344 432,0 0,-32 -432,0 0,32 z" + id="path3102-2-9" /></clipPath><clipPath + id="clipPath3116-4-9"><path + inkscape:connector-curvature="0" + d="m 1029,756 432,0 0,-32 -432,0 0,32 z" + id="path3118-5-1" /></clipPath><clipPath + id="clipPath3592-5-68" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3594-9-1" + d="m 730.778,549 12,0 0,-12 -12,0 0,12 z" /></clipPath><clipPath + id="clipPath3566-8-9" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3568-8-22" + d="m 679,547.865 32,0 0,-12.002 -32,0 0,12.002 z" /></clipPath><clipPath + id="clipPath3570-6-4" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3572-0-00" + d="m 679,535.865 32,0 0,12 -32,0 0,-12 z" /></clipPath><clipPath + id="clipPath3520-6-4" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3522-3-32" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3528-3-5" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><g + id="g3530-7-3"><g + id="g3532-3-5" + clip-path="url(#clipPath3524-5-2)"><g + id="g3534-0-0"><g + id="g3536-4-5" + transform="matrix(93,0,0,68,667,493)"><image + id="image3538-4-00" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAA51JREFUeJztnEtv4koQRo/BpokBoThZIJJN/v/vCkoUYuSAbYwfPYu57duOzWPm3lEvpo5UwqLbXhyXihaLzwM0A3ieN/S1cCNaD2oFwOObdM/zWuH2tXAbWutWuH1t45sLI3c0GvXKXheGMXKbpumVvQ6WdPgp3Pd9giBoy/d9EX8FW3hd15RlSVmWnE4n6rruiffhp0wjXClFGIbMZjNmsxlKqVa8SB9Ga90KL4qCLMvIsow0TTkej1RVRV3X7f6O9CAICMOQKIp4eHggiiIWiwVKKcbjscz4AczcbpqG0+lEmqbsdjviOGY0GrXrZo/WGt+e5UEQMJ/PeXx85Pn5mfV6TRRFhGFIEATtmBG6aK2pqorj8UiSJLy/v6OUomkayrK8vdPX6zUvLy+sVisWiwWTyURGzBmapqGqKvI8J45jwjBEa02e5+2IKcvy306H/kyfz+dEUcRqteLp6Ynlcsl0OhXpZzDSsyxjNpvRNA37/Z7tdst2u22nhBnPg6cX82O6WCxYLpfc39935rrQxYyX6XQKwH6/Z7lcMp/PW2/2aO6c003Hj8djgiBgMpmglEIpxXQ6FelnMNIB8jwnDEPu7u5QShEEQe8Q4n9/gBFvvwC7RHofc/42zer7flv2WDH0pBvMxqES+pgmtctuYBs5A/4BrjWoSHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASHeASP8D2GlGQ5zNBrBvvPaQvx07LsouO0LKpifd3mjCwEyBJNYNYaJHTFJdVVVtGZeDEYH22zI3n04niqKgKAoAiR45g5GeZRl5nrefRVFQliV1XXfEdzrdxCKZmLv9fk+SJAASJ3UBO07q8/OTOI5JkoTD4UBRFJ1sRvhHuulwI/xwOBDHMW9vbwB8fX1JcNoFvgenvb6+8vHxQZIk5HneCU1rg9OM9LIsybKMOI7ZbDYAJEkiEYFXGIoI3Gw2xHFMmqYd6QC+1hrP81rph8OB7XaL1po0TSUM8wqXwjB3ux15nlNVVf+H9HunAxRFQZIkEvt6A5diX+2ZbsS3Ud52NqMEHP8atwQc26eXjnSQKO/f5VeivCW0/n/mltD6nvR2QWT/Jy79ZfIDbR7YvKqk0OoAAAAASUVORK5CYII=" + transform="matrix(1,0,0,-1,0,1)" + height="1" + width="1" /></g></g></g></g></mask><clipPath + id="clipPath3524-5-2" + clipPathUnits="userSpaceOnUse"><path + inkscape:connector-curvature="0" + id="path3526-4-1" + d="m 667,561 93,0 0,-68 -93,0 0,68 z" /></clipPath><mask + id="mask3540-4-88" + height="1" + width="1" + y="0" + x="0" + maskUnits="userSpaceOnUse"><image + id="image3542-1-5" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAAAAADGw2ZMAAAAAXNCSVQI5gpbmQAAAZJJREFUWIXt2DFv2zAQhuHvTlTkyDaMuBmCtEv//+9q0KKuAtWWZJnkfR0UAwY69jyVXAhoeEQcyOUVAAAEvos3rAjE0ydBfugCVVV1Oz9hZmYgEABoqOs6ePGE5Rgv2UAEiIamXa+boE665Xkch3PKQIBo3e4/7bdN5TJ70i7De6ckjUGg9eb5y+u+rdUBB5jO/Y/G4s3ZX7++bB98RmNp6lpOwzkal7lv9i+fdysvfVzb8XCoVWS5M0273T01lYvOtMJxt2kqBQIgolX90DQrLx1T+9jUlQgCAIiKVlXlpKOqQwgqsrwmALIsDx2iqioqAHxu4V8/+DjoffTrKnrRi170ohe96EUvetGLXvSiF/3/0bkkvWsnIHn98u+ymRmNV51Gyzn7RD2mHFNKxqUYkpbjZZ7h1WfGaZzmmEkEAJbm8djDr1z96vrTnA0IoKX51H3Hb8fq9u1nP0UjA2hx7N7QuxbDt26IRgSKxdOBg3PtfJ8Sl8nEEXPvXWrnbIQAcqfKTN65kN+97t9sfv6y/QG64fFP78GtbgAAAABJRU5ErkJggg==" + height="1" + width="1" /></mask></defs><g + inkscape:groupmode="layer" + id="layer8" + inkscape:label="Layer5" + transform="translate(436.0495,72.4457)"><g + style="display:inline" + id="g3508-5-64" + transform="matrix(5.09,0,0,-5.09,-201.82504,927.02677)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c -2.209,0 -4,1.791 -4,4 l 0,92 c 0,2.209 1.791,4 4,4 l 92,0 c 2.209,0 4,-1.791 4,-4 L 96,4 C 96,1.791 94.209,0 92,0 L 0,0 z" + style="fill:#4fbdee;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3510-0-3" /></g><g + style="display:inline" + id="g3516-7-0" + transform="matrix(5.09,0,0,-5.09,-3586.6755,3426.2166)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3518-2-5" /><g + id="g3544-9-2"><g + style="opacity:0.119995" + clip-path="url(#clipPath3520-6-4)" + id="g3546-7-5"><g + id="g3548-8-3"><g + id="g3550-3-8" /><g + mask="url(#mask3528-3-5)" + id="g3552-8-72"><g + transform="matrix(93,0,0,68,667,493)" + id="g3554-9-8"><image + width="1" + height="1" + transform="matrix(1,0,0,-1,0,1)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAAP1JREFUeJzt3DGKhEAARcFWvP+VeyNBxJGFHX3BVmU2BvL4DBP1Muec48KyLFfH/NKHrGOMMbbzgdjfsXe8ir+dX+K7ruKv1cf8N8dRr+cDnmfpL9rHvVr5+yw9sIwxPv+h5BGWHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9JfNOUUvrHe3H/MMS3/RPvD1+MA7LP0lx2Fv50N3737X7aX155fE/5u7n+wfv04ph+lQnM8AAAAASUVORK5CYII=" + mask="url(#mask3540-4-88)" + id="image3556-9-3" /></g></g></g></g></g></g><g + style="display:inline" + id="g3558-6-1" + transform="matrix(5.09,0,0,-5.09,235.91496,860.85677)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-1.65 -1.35,-3 -3,-3 l -74,0 c -1.649,0 -3,1.35 -3,3 l 0,50 c 0,1.65 1.351,3 3,3 l 74,0 c 1.65,0 3,-1.35 3,-3 L 0,0 z" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3560-5-6" /></g><g + style="display:inline" + id="g3562-4-0" + transform="matrix(5.09,0,0,-5.09,-3586.6755,3426.2166)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3564-6-3" /><g + id="g3574-7-9"><g + style="opacity:0.5" + clip-path="url(#clipPath3566-8-9)" + id="g3576-7-8"><g + id="g3578-2-9"><g + clip-path="url(#clipPath3570-6-4)" + id="g3580-1-9"><path + inkscape:connector-curvature="0" + d="m 710.994,535.863 -31.988,0 0,12.002 31.988,0 0,-12.002 z" + style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3582-0-16" /></g></g></g></g></g><path + inkscape:connector-curvature="0" + d="m 123.93496,748.87677 -254.5,0 0,-25.45 254.5,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3584-1-9" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><path + inkscape:connector-curvature="0" + d="m 6.8649591,799.77677 -137.4299991,0 0,-25.45 137.4299991,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3586-5-0" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><g + style="display:inline" + id="g3588-5-0" + transform="matrix(5.09,0,0,-5.09,-3586.6755,3426.2166)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3590-3-9" /><g + id="g3596-7-2"><g + style="opacity:0" + clip-path="url(#clipPath3592-5-68)" + id="g3598-6-5"><path + inkscape:connector-curvature="0" + d="m 730.777,537 12,0 0,12 -12,0 0,-12 z" + style="fill:#03a9f4;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3600-8-1" /></g></g></g><g + style="display:inline" + id="g3602-7-0" + transform="matrix(5.09,0,0,-5.09,188.97176,661.07427)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,0.414 -0.336,0.75 -0.75,0.75 l -2.75,0 -2.5,4 -1,0 1.25,-4 -2.75,0 -0.75,1 -0.75,0 0.5,-1.75 -0.5,-1.75 0.75,0 0.75,1 2.75,0 -1.25,-4 1,0 2.5,4 2.75,0 C -0.336,-0.75 0,-0.414 0,0" + style="fill:#00c853;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3604-3-8" /></g><g + transform="matrix(1.25,0,0,-1.25,-1884.887,1349.9021)" + id="g3012-8" + style="display:inline" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + transform="translate(1677.8121,922.30928)" + id="g3072-5" + style="fill:#333333;fill-opacity:1"><path + inkscape:connector-curvature="0" + d="m 0,0 -285.486,0 -14,-190.491 313.486,0 L 0,0 z" + id="path3074-5" + style="fill:#101010;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="translate(1691.8121,352.84148)" + id="g3076-9" + style="fill:#333333;fill-opacity:1"><path + inkscape:connector-curvature="0" + d="m 0,0 -313.486,0 14,-190.491 286.486,0 L 0,0 z" + id="path3078-3" + style="fill:#101010;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><path + inkscape:connector-curvature="0" + d="m 1721.07,355.32978 -374,0 0,374 374,0 0,-374 z m 3.332,409 -378.666,0 c -14.729,0 -26.666,-11.938 -26.666,-26.667 l 0,-5.333 0,-380 0,-5.334 c 0,-14.727 11.937,-26.666 26.666,-26.666 l 378.666,0 c 14.729,0 26.668,11.939 26.668,26.666 l 0,5.334 0,380 0,5.333 c 0,14.729 -11.939,26.667 -26.668,26.667" + id="path3080-4" + style="fill:#101010;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g + transform="translate(290.06807,8.3297801)" + id="g3082-3"><g + id="g3084-9" /><g + id="g3090-2"><g + clip-path="url(#clipPath3086-4-0)" + id="g3092-8" + style="opacity:0.10000598"><path + inkscape:connector-curvature="0" + d="m 1432.002,722 -374,0 0,-374 374,0 0,374 z m -3,-371 -368,0 0,368 368,0 0,-368 z" + id="path3094-1" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g><g + transform="translate(290.06807,8.3297801)" + id="g3096-6"><g + id="g3098-6" /><g + id="g3104-5"><g + clip-path="url(#clipPath3100-8-2)" + id="g3106-9" + style="opacity:0.10000598"><g + transform="translate(1434.334,317.334)" + id="g3108-9"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,11.938 -26.666,26.666 l 0,-5.334 c 0,-14.727 11.937,-26.666 26.666,-26.666 L 0,-5.334 c 14.729,0 26.668,11.939 26.668,26.666 l 0,5.334 C 26.668,11.938 14.729,0 0,0" + id="path3110-8" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g><g + transform="translate(290.06807,8.3297801)" + id="g3112-1"><g + id="g3114-5" /><g + id="g3120-7"><g + clip-path="url(#clipPath3116-4-9)" + id="g3122-5" + style="opacity:0.10000598"><g + transform="translate(1434.334,756)" + id="g3124-1"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,-11.938 -26.666,-26.667 l 0,-5.333 c 0,14.728 11.937,26.667 26.666,26.667 L 0,-5.333 c 14.729,0 26.668,-11.939 26.668,-26.667 l 0,5.333 C 26.668,-11.938 14.729,0 0,0" + id="path3126-0" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g></g></g><g + inkscape:groupmode="layer" + id="layer7" + inkscape:label="Layer4" + style="display:inline" + transform="translate(436.0495,72.4457)"><g + style="display:inline" + id="g3622-5-8" + transform="matrix(5.2235991,0,0,-5.1937884,507.13047,984.15494)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c -32.947,0 -59.75,26.804 -59.75,59.75 0,32.946 26.803,59.75 59.75,59.75 32.945,0 59.75,-26.804 59.75,-59.75 C 59.75,26.804 32.945,0 0,0" + style="fill:#ff4a3c;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3624-3-9" /></g><g + style="display:inline" + id="g3630-4-7" + transform="matrix(5.2235991,0,0,-5.1937884,-2762.8426,3436.9216)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3632-0-4" + clip-path="url(#clipPath3634-8-0)"><g + id="g3638-2-4"><g + id="g3640-4-1" /><g + id="g3666-9-6"><g + style="opacity:0.119995" + clip-path="url(#clipPath3642-3-4)" + id="g3668-8-7"><g + id="g3670-0-5"><g + id="g3672-4-4" /><g + mask="url(#mask3650-5-0)" + id="g3674-2-0"><g + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)" + id="g3676-6-2"><image + width="1" + height="1" + transform="matrix(1,0,0,-1,0,1)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAAASdJREFUeJzt1rFuwyAUQFGw/P+/TIfKamQ5be7kRjlnshEDw9WDudZa48Kc82qZD/Eki7GfF4TCGD8dnMPZzxvg0Tmc7c7D8D6OcLbHH/iLCcPL5pxjjjGun8NwwYQhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDIlgSARDIhgSwZAIhkQwJIIhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDIlgSARDIhgSwZAIhkQwJIIhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDMm21rr7DLyJtZYJQ7ON8V0O/OZoxIQh2Y+Po6A5522H4f853z77sw3C+WzPnilfB04mnX6avLgAAAAASUVORK5CYII=" + mask="url(#mask3662-9-4)" + id="image3678-1-3" /></g></g></g></g></g></g><g + id="g3680-5-3" + transform="translate(689.7031,476.2588)"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-1.642 -1.344,-2.984 -2.986,-2.984 l -121.434,0 c -1.643,0 -2.986,1.342 -2.986,2.984 l 0,60.719 c 0,1.641 1.343,2.985 2.986,2.985 l 121.434,0 C -1.344,63.704 0,62.36 0,60.719 L 0,0 z" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3682-6-2" /></g></g></g><g + id="layer3" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + transform="matrix(1.25,0,0,-1.25,715.58308,453.4259)" + id="g3024-4" + style="fill:#000000;display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 -12.195,205.05 -307.49,0 L -331.88,0 c 44.595,34.886 102.563,58.974 165.94,58.974 C -102.563,58.974 -44.595,34.886 0,0" + id="path3026-3" + style="fill:#2e2e2e;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,300.90228,894.5124)" + id="g3028-0" + style="fill:#000000;display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 12.06,-202.039 307.49,0 L 331.609,0 C 287.033,-34.822 229.119,-58.869 165.805,-58.869 102.491,-58.869 44.576,-34.822 0,0" + id="path3030-8" + style="fill:#2e2e2e;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,456.87948,388.9154)" + id="g3032-2" + style="display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 c 5.706,1.02 11.485,1.83 17.328,2.423 3.895,0.395 7.819,0.694 11.769,0.895 3.95,0.2 7.926,0.301 11.926,0.301 4,0 7.976,-0.101 11.926,-0.301 3.95,-0.201 7.874,-0.5 11.77,-0.895 C 70.561,1.83 76.341,1.02 82.047,0 c 108.425,-19.367 190.726,-114.139 190.726,-228.132 0,-127.991 -103.756,-231.75 -231.75,-231.75 -127.99,0 -231.751,103.759 -231.751,231.75 0,113.993 82.305,208.765 190.728,228.132 m 41.023,26.794 c -140.792,0 -254.926,-114.133 -254.926,-254.926 0,-140.792 114.134,-254.926 254.926,-254.926 140.79,0 254.926,114.134 254.926,254.926 0,140.793 -114.136,254.926 -254.926,254.926" + id="path3034-1" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,508.15818,355.4224)" + id="g3036-8" + style="fill:#000000;display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 c -140.792,0 -254.926,-114.134 -254.926,-254.926 0,-140.792 114.134,-254.926 254.926,-254.926 140.792,0 254.926,114.134 254.926,254.926 C 254.926,-114.134 140.792,0 0,0 m 231.75,-254.926 c 0,-127.991 -103.758,-231.75 -231.75,-231.75 -127.992,0 -231.75,103.759 -231.75,231.75 0,127.993 103.758,231.751 231.75,231.751 127.992,0 231.75,-103.758 231.75,-231.751" + id="path3038-3" + style="fill:#2c2c2c;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,-264.34372,1339.588)" + id="g3040-9" + style="display:inline"><g + id="g3042-1" /><g + id="g3048-2"><g + clip-path="url(#clipPath3044-8-3-2)" + id="g3050-4" + style="opacity:0.10000598"><g + transform="translate(844.249,532.4062)" + id="g3052-7"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-124.952 -101.295,-226.248 -226.248,-226.248 -124.952,0 -226.247,101.296 -226.247,226.248 0,124.953 101.295,226.248 226.247,226.248 C -101.295,226.248 0,124.953 0,0" + id="path3054-5" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g><g + transform="matrix(1.25,0,0,-1.25,-264.34372,1339.588)" + id="g3056-2" + style="display:inline"><g + id="g3058-6" /><g + id="g3064-1"><g + clip-path="url(#clipPath3060-8-2-1)" + id="g3066-2" + style="opacity:0.10000598"><g + transform="translate(618.0015,787.7266)" + id="g3068-5"><path + inkscape:connector-curvature="0" + d="m 0,0 c -34.466,0 -67.903,-6.75 -99.383,-20.065 -30.404,-12.861 -57.707,-31.269 -81.155,-54.716 -23.446,-23.447 -41.856,-50.752 -54.715,-81.156 -13.314,-31.48 -20.066,-64.916 -20.066,-99.383 0,-34.465 6.752,-67.904 20.066,-99.381 12.859,-30.405 31.269,-57.709 54.715,-81.156 23.448,-23.447 50.751,-41.857 81.155,-54.715 31.48,-13.315 64.917,-20.066 99.383,-20.066 34.466,0 67.903,6.751 99.382,20.066 30.405,12.858 57.708,31.268 81.155,54.715 23.447,23.447 41.857,50.751 54.716,81.156 13.314,31.477 20.066,64.916 20.066,99.381 0,34.467 -6.752,67.903 -20.066,99.383 -12.859,30.404 -31.269,57.709 -54.716,81.156 -23.447,23.447 -50.75,41.855 -81.155,54.716 C 67.903,-6.75 34.466,0 0,0 m 0,-3.153 c 139.268,0 252.167,-112.897 252.167,-252.167 0,-139.267 -112.899,-252.166 -252.167,-252.166 -139.268,0 -252.167,112.899 -252.167,252.166 0,139.27 112.899,252.167 252.167,252.167" + id="path3070-2" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g></g><g + style="display:inline" + id="g3692-7-8" + transform="matrix(5.2956158,0,0,-5.2956158,-2815.7539,3491.418)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3694-5-3" /><g + id="g3704-5-3"><g + clip-path="url(#clipPath3696-9-7-9)" + id="g3706-2-5" + style="opacity:0.5"><g + id="g3708-6-9"><g + clip-path="url(#clipPath3700-1-1-8)" + id="g3710-3-0"><path + d="m 623.994,518.863 -31.988,0 0,12.002 31.988,0 0,-12.002 z" + style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3712-4-5" + inkscape:connector-curvature="0" /></g></g></g></g></g><path + d="m 584.03152,795.94967 -264.78075,0 0,-26.47808 264.78075,0 0,26.47808 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3714-2-5" + inkscape:connector-curvature="0" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><path + d="m 462.23242,848.90583 -142.98165,0 0,-26.47808 142.98165,0 0,26.47808 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3716-3-1" + inkscape:connector-curvature="0" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><g + style="display:inline" + id="g3718-7-1" + transform="matrix(5.2956158,0,0,-5.2956158,-2815.7539,3491.418)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3720-1-0" /><g + id="g3730-8-4"><g + clip-path="url(#clipPath3722-1-6-1)" + id="g3732-8-4" + style="opacity:0"><g + id="g3734-9-2"><g + clip-path="url(#clipPath3726-6-9-4)" + id="g3736-3-3"><path + d="m 664.001,520 -12.001,0 0,12 12.001,0 0,-12 z" + style="fill:#ee2a7b;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3738-7-6" + inkscape:connector-curvature="0" /></g></g></g></g></g><g + style="display:inline" + id="g3740-9-7" + transform="matrix(5.2956158,0,0,-5.2956158,644.93112,705.35546)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + d="m 0,0 4.5,0 0,-4.501 0.501,0 4,9.001 L 0,0.501 0,0 z" + style="fill:#03a9f4;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3742-8-7" + inkscape:connector-curvature="0" /></g></g><g + inkscape:groupmode="layer" + id="layer6" + inkscape:label="Layer3" + style="display:inline" + transform="translate(436.0495,72.4457)"><g + style="display:inline" + id="g3508-5-6" + transform="matrix(5.09,0,0,-5.09,742.55563,925.70096)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c -2.209,0 -4,1.791 -4,4 l 0,92 c 0,2.209 1.791,4 4,4 l 92,0 c 2.209,0 4,-1.791 4,-4 L 96,4 C 96,1.791 94.209,0 92,0 L 0,0 z" + style="fill:#9d65dc;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3510-0-8" /></g><g + style="display:inline" + id="g3516-7-9" + transform="matrix(5.09,0,0,-5.09,-2642.2948,3424.8908)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3518-2-7" /><g + id="g3544-9-6"><g + style="opacity:0.119995" + clip-path="url(#clipPath3520-6-0)" + id="g3546-7-3"><g + id="g3548-8-7"><g + id="g3550-3-6" /><g + mask="url(#mask3528-3-8)" + id="g3552-8-7"><g + transform="matrix(93,0,0,68,667,493)" + id="g3554-9-9"><image + width="1" + height="1" + transform="matrix(1,0,0,-1,0,1)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAAP1JREFUeJzt3DGKhEAARcFWvP+VeyNBxJGFHX3BVmU2BvL4DBP1Muec48KyLFfH/NKHrGOMMbbzgdjfsXe8ir+dX+K7ruKv1cf8N8dRr+cDnmfpL9rHvVr5+yw9sIwxPv+h5BGWHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9JfNOUUvrHe3H/MMS3/RPvD1+MA7LP0lx2Fv50N3737X7aX155fE/5u7n+wfv04ph+lQnM8AAAAASUVORK5CYII=" + mask="url(#mask3540-4-8)" + id="image3556-9-8" /></g></g></g></g></g></g><g + style="display:inline" + id="g3558-6-7" + transform="matrix(5.09,0,0,-5.09,1180.2956,859.53096)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-1.65 -1.35,-3 -3,-3 l -74,0 c -1.649,0 -3,1.35 -3,3 l 0,50 c 0,1.65 1.351,3 3,3 l 74,0 c 1.65,0 3,-1.35 3,-3 L 0,0 z" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3560-5-9" /></g><g + style="display:inline" + id="g3562-4-5" + transform="matrix(5.09,0,0,-5.09,-2642.2948,3424.8908)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3564-6-8" /><g + id="g3574-7-5"><g + style="opacity:0.5" + clip-path="url(#clipPath3566-8-1)" + id="g3576-7-7"><g + id="g3578-2-1"><g + clip-path="url(#clipPath3570-6-5)" + id="g3580-1-1"><path + inkscape:connector-curvature="0" + d="m 710.994,535.863 -31.988,0 0,12.002 31.988,0 0,-12.002 z" + style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3582-0-1" /></g></g></g></g></g><path + inkscape:connector-curvature="0" + d="m 1068.3156,747.55096 -254.49997,0 0,-25.45 254.49997,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3584-1-4" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><path + inkscape:connector-curvature="0" + d="m 951.24563,798.45096 -137.43,0 0,-25.45 137.43,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3586-5-3" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><g + style="display:inline" + id="g3588-5-2" + transform="matrix(5.09,0,0,-5.09,-2642.2948,3424.8908)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3590-3-5" /><g + id="g3596-7-7"><g + style="opacity:0" + clip-path="url(#clipPath3592-5-6)" + id="g3598-6-3"><path + inkscape:connector-curvature="0" + d="m 730.777,537 12,0 0,12 -12,0 0,-12 z" + style="fill:#03a9f4;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3600-8-8" /></g></g></g><g + style="display:inline" + id="g3602-7-1" + transform="matrix(5.09,0,0,-5.09,1133.3524,659.74846)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,0.414 -0.336,0.75 -0.75,0.75 l -2.75,0 -2.5,4 -1,0 1.25,-4 -2.75,0 -0.75,1 -0.75,0 0.5,-1.75 -0.5,-1.75 0.75,0 0.75,1 2.75,0 -1.25,-4 1,0 2.5,4 2.75,0 C -0.336,-0.75 0,-0.414 0,0" + style="fill:#00c853;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3604-3-1" /></g><g + id="layer2" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + transform="matrix(1.25,0,0,-1.25,1156.7381,197.1134)" + id="g3072-2" + style="fill:#4d4d4d;display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 -285.486,0 -14,-190.491 313.486,0 L 0,0 z" + id="path3074-4" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,1174.2381,908.94815)" + id="g3076-2" + style="fill:#333333;display:inline"><path + inkscape:connector-curvature="0" + d="m 0,0 -313.486,0 14,-190.491 286.486,0 L 0,0 z" + id="path3078-2" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><path + inkscape:connector-curvature="0" + d="m 1210.8105,905.83777 -467.50004,0 0,-467.5 467.50004,0 0,467.5 z m 4.165,-511.25 -473.33254,0 c -18.41125,0 -33.3325,14.92251 -33.3325,33.33376 l 0,6.66624 0,475 0,6.66751 c 0,18.40874 14.92125,33.33249 33.3325,33.33249 l 473.33254,0 c 18.4112,0 33.335,-14.92375 33.335,-33.33249 l 0,-6.66751 0,-475 0,-6.66624 c 0,-18.41125 -14.9238,-33.33376 -33.335,-33.33376" + id="path3080-8" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" /><g + transform="matrix(1.25,0,0,-1.25,-577.94194,1339.5878)" + id="g3082-6" + style="display:inline"><g + id="g3084-6" /><g + id="g3090-6"><g + clip-path="url(#clipPath3086-0-0-4)" + id="g3092-6" + style="opacity:0.10000598"><path + inkscape:connector-curvature="0" + d="m 1432.002,722 -374,0 0,-374 374,0 0,374 z m -3,-371 -368,0 0,368 368,0 0,-368 z" + id="path3094-4" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g><g + transform="matrix(1.25,0,0,-1.25,-577.94194,1339.5878)" + id="g3096-1" + style="display:inline"><g + id="g3098-7" /><g + id="g3104-3"><g + clip-path="url(#clipPath3100-0-7-1)" + id="g3106-8" + style="opacity:0.10000598"><g + transform="translate(1434.334,317.334)" + id="g3108-5"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,11.938 -26.666,26.666 l 0,-5.334 c 0,-14.727 11.937,-26.666 26.666,-26.666 L 0,-5.334 c 14.729,0 26.668,11.939 26.668,26.666 l 0,5.334 C 26.668,11.938 14.729,0 0,0" + id="path3110-7" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g><g + transform="matrix(1.25,0,0,-1.25,-577.94194,1339.5878)" + id="g3112-0" + style="display:inline"><g + id="g3114-4" /><g + id="g3120-4"><g + clip-path="url(#clipPath3116-3-2-9)" + id="g3122-9" + style="opacity:0.10000598"><g + transform="translate(1434.334,756)" + id="g3124-5"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,-11.938 -26.666,-26.667 l 0,-5.333 c 0,14.728 11.937,26.667 26.666,26.667 L 0,-5.333 c 14.729,0 26.668,-11.939 26.668,-26.667 l 0,5.333 C 26.668,-11.938 14.729,0 0,0" + id="path3126-3" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g></g></g><g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Layer2" + style="display:inline" + transform="translate(436.0495,72.4457)"><g + style="display:inline" + id="g3622-5" + transform="matrix(5.2235991,0,0,-5.1937884,1450.0328,987.05631)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c -32.947,0 -59.75,26.804 -59.75,59.75 0,32.946 26.803,59.75 59.75,59.75 32.945,0 59.75,-26.804 59.75,-59.75 C 59.75,26.804 32.945,0 0,0" + style="fill:#0bda2f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3624-3" /></g><g + style="display:inline" + id="g3630-4" + transform="matrix(5.2235991,0,0,-5.1937884,-1819.9402,3439.8229)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3632-0" + clip-path="url(#clipPath3634-8)"><g + id="g3638-2"><g + id="g3640-4" /><g + id="g3666-9"><g + style="opacity:0.119995" + clip-path="url(#clipPath3642-3)" + id="g3668-8"><g + id="g3670-0"><g + id="g3672-4" /><g + mask="url(#mask3650-5)" + id="g3674-2"><g + transform="matrix(139.35082,0,0,78.633677,558.31445,465.31115)" + id="g3676-6"><image + width="1" + height="1" + transform="matrix(1,0,0,-1,0,1)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABPCAYAAAA5vC0kAAAABHNCSVQICAgIfAhkiAAAASdJREFUeJzt1rFuwyAUQFGw/P+/TIfKamQ5be7kRjlnshEDw9WDudZa48Kc82qZD/Eki7GfF4TCGD8dnMPZzxvg0Tmc7c7D8D6OcLbHH/iLCcPL5pxjjjGun8NwwYQhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDIlgSARDIhgSwZAIhkQwJIIhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDIlgSARDIhgSwZAIhkQwJIIhEQyJYEgEQyIYEsGQCIZEMCSCIREMiWBIBEMiGBLBkAiGRDAkgiERDMm21rr7DLyJtZYJQ7ON8V0O/OZoxIQh2Y+Po6A5522H4f853z77sw3C+WzPnilfB04mnX6avLgAAAAASUVORK5CYII=" + mask="url(#mask3662-9)" + id="image3678-1" /></g></g></g></g></g></g><g + id="g3680-5" + transform="translate(689.7031,476.2588)"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-1.642 -1.344,-2.984 -2.986,-2.984 l -121.434,0 c -1.643,0 -2.986,1.342 -2.986,2.984 l 0,60.719 c 0,1.641 1.343,2.985 2.986,2.985 l 121.434,0 C -1.344,63.704 0,62.36 0,60.719 L 0,0 z" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3682-6" /></g></g></g><g + transform="translate(2e-5,-3e-5)" + id="layer1" + style="display:inline" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + transform="matrix(1.25,0,0,-1.25,1656.802,453.73263)" + id="g3024" + style="fill:#000000"><path + inkscape:connector-curvature="0" + d="m 0,0 -12.195,205.05 -307.49,0 L -331.88,0 c 44.595,34.886 102.563,58.974 165.94,58.974 C -102.563,58.974 -44.595,34.886 0,0" + id="path3026" + style="fill:#606060;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,1242.1212,894.81914)" + id="g3028" + style="fill:#000000"><path + inkscape:connector-curvature="0" + d="m 0,0 12.06,-202.039 307.49,0 L 331.609,0 C 287.033,-34.822 229.119,-58.869 165.805,-58.869 102.491,-58.869 44.576,-34.822 0,0" + id="path3030" + style="fill:#606060;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,1398.0984,389.22214)" + id="g3032"><path + inkscape:connector-curvature="0" + d="m 0,0 c 5.706,1.02 11.485,1.83 17.328,2.423 3.895,0.395 7.819,0.694 11.769,0.895 3.95,0.2 7.926,0.301 11.926,0.301 4,0 7.976,-0.101 11.926,-0.301 3.95,-0.201 7.874,-0.5 11.77,-0.895 C 70.561,1.83 76.341,1.02 82.047,0 c 108.425,-19.367 190.726,-114.139 190.726,-228.132 0,-127.991 -103.756,-231.75 -231.75,-231.75 -127.99,0 -231.751,103.759 -231.751,231.75 0,113.993 82.305,208.765 190.728,228.132 m 41.023,26.794 c -140.792,0 -254.926,-114.133 -254.926,-254.926 0,-140.792 114.134,-254.926 254.926,-254.926 140.79,0 254.926,114.134 254.926,254.926 0,140.793 -114.136,254.926 -254.926,254.926" + id="path3034" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,1449.3771,355.72914)" + id="g3036" + style="fill:#000000"><path + inkscape:connector-curvature="0" + d="m 0,0 c -140.792,0 -254.926,-114.134 -254.926,-254.926 0,-140.792 114.134,-254.926 254.926,-254.926 140.792,0 254.926,114.134 254.926,254.926 C 254.926,-114.134 140.792,0 0,0 m 231.75,-254.926 c 0,-127.991 -103.758,-231.75 -231.75,-231.75 -127.992,0 -231.75,103.759 -231.75,231.75 0,127.993 103.758,231.751 231.75,231.751 127.992,0 231.75,-103.758 231.75,-231.751" + id="path3038" + style="fill:#606060;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="matrix(1.25,0,0,-1.25,676.87523,1339.8947)" + id="g3040"><g + id="g3042" /><g + id="g3048"><g + clip-path="url(#clipPath3044-8-1)" + id="g3050" + style="opacity:0.10000598"><g + transform="translate(844.249,532.4062)" + id="g3052"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-124.952 -101.295,-226.248 -226.248,-226.248 -124.952,0 -226.247,101.296 -226.247,226.248 0,124.953 101.295,226.248 226.247,226.248 C -101.295,226.248 0,124.953 0,0" + id="path3054" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g><g + transform="matrix(1.25,0,0,-1.25,676.87523,1339.8947)" + id="g3056"><g + id="g3058" /><g + id="g3064"><g + clip-path="url(#clipPath3060-8-27)" + id="g3066" + style="opacity:0.10000598"><g + transform="translate(618.0015,787.7266)" + id="g3068"><path + inkscape:connector-curvature="0" + d="m 0,0 c -34.466,0 -67.903,-6.75 -99.383,-20.065 -30.404,-12.861 -57.707,-31.269 -81.155,-54.716 -23.446,-23.447 -41.856,-50.752 -54.715,-81.156 -13.314,-31.48 -20.066,-64.916 -20.066,-99.383 0,-34.465 6.752,-67.904 20.066,-99.381 12.859,-30.405 31.269,-57.709 54.715,-81.156 23.448,-23.447 50.751,-41.857 81.155,-54.715 31.48,-13.315 64.917,-20.066 99.383,-20.066 34.466,0 67.903,6.751 99.382,20.066 30.405,12.858 57.708,31.268 81.155,54.715 23.447,23.447 41.857,50.751 54.716,81.156 13.314,31.477 20.066,64.916 20.066,99.381 0,34.467 -6.752,67.903 -20.066,99.383 -12.859,30.404 -31.269,57.709 -54.716,81.156 -23.447,23.447 -50.75,41.855 -81.155,54.716 C 67.903,-6.75 34.466,0 0,0 m 0,-3.153 c 139.268,0 252.167,-112.897 252.167,-252.167 0,-139.267 -112.899,-252.166 -252.167,-252.166 -139.268,0 -252.167,112.899 -252.167,252.166 0,139.27 112.899,252.167 252.167,252.167" + id="path3070" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g></g><g + style="display:inline" + id="g3692-7" + transform="matrix(5.2956158,0,0,-5.2956158,-1870.725,3488.2105)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3694-5" /><g + id="g3704-5"><g + clip-path="url(#clipPath3696-9-7)" + id="g3706-2" + style="opacity:0.5"><g + id="g3708-6"><g + clip-path="url(#clipPath3700-1-1)" + id="g3710-3"><path + d="m 623.994,518.863 -31.988,0 0,12.002 31.988,0 0,-12.002 z" + style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3712-4" + inkscape:connector-curvature="0" /></g></g></g></g></g><path + d="m 1529.0604,792.7421 -264.7808,0 0,-26.47808 264.7808,0 0,26.47808 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3714-2" + inkscape:connector-curvature="0" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><path + d="m 1407.2613,845.69826 -142.9817,0 0,-26.47808 142.9817,0 0,26.47808 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3716-3" + inkscape:connector-curvature="0" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><g + style="display:inline" + id="g3718-7" + transform="matrix(5.2956158,0,0,-5.2956158,-1870.725,3488.2105)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3720-1" /><g + id="g3730-8"><g + clip-path="url(#clipPath3722-1-6)" + id="g3732-8" + style="opacity:0"><g + id="g3734-9"><g + clip-path="url(#clipPath3726-6-9)" + id="g3736-3"><path + d="m 664.001,520 -12.001,0 0,12 12.001,0 0,-12 z" + style="fill:#ee2a7b;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3738-7" + inkscape:connector-curvature="0" /></g></g></g></g></g><g + style="display:inline" + id="g3740-9" + transform="matrix(5.2956158,0,0,-5.2956158,1589.96,702.14789)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + d="m 0,0 4.5,0 0,-4.501 0.501,0 4,9.001 L 0,0.501 0,0 z" + style="fill:#03a9f4;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3742-8" + inkscape:connector-curvature="0" /></g></g><g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Layer1" + style="display:inline" + transform="translate(436.0495,72.4457)"><g + style="display:inline" + id="g3508-5" + transform="matrix(5.09,0,0,-5.09,1683.0619,927.12472)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c -2.209,0 -4,1.791 -4,4 l 0,92 c 0,2.209 1.791,4 4,4 l 92,0 c 2.209,0 4,-1.791 4,-4 L 96,4 C 96,1.791 94.209,0 92,0 L 0,0 z" + style="fill:#ffed0d;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3510-0" /></g><g + style="display:inline" + id="g3516-7" + transform="matrix(5.09,0,0,-5.09,-1701.7885,3426.3146)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3518-2" /><g + id="g3544-9"><g + style="opacity:0.119995" + clip-path="url(#clipPath3520-6)" + id="g3546-7"><g + id="g3548-8"><g + id="g3550-3" /><g + mask="url(#mask3528-3)" + id="g3552-8"><g + transform="matrix(93,0,0,68,667,493)" + id="g3554-9"><image + width="1" + height="1" + transform="matrix(1,0,0,-1,0,1)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF0AAABECAYAAADjqDmQAAAABHNCSVQICAgIfAhkiAAAAP1JREFUeJzt3DGKhEAARcFWvP+VeyNBxJGFHX3BVmU2BvL4DBP1Muec48KyLFfH/NKHrGOMMbbzgdjfsXe8ir+dX+K7ruKv1cf8N8dRr+cDnmfpL9rHvVr5+yw9sIwxPv+h5BGWHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9IDoAdEDogdED4geED0gekD0gOgB0QOiB0QPiB4QPSB6QPSA6AHRA6IHRA+IHhA9IHpA9JfNOUUvrHe3H/MMS3/RPvD1+MA7LP0lx2Fv50N3737X7aX155fE/5u7n+wfv04ph+lQnM8AAAAASUVORK5CYII=" + mask="url(#mask3540-4)" + id="image3556-9" /></g></g></g></g></g></g><g + style="display:inline" + id="g3558-6" + transform="matrix(5.09,0,0,-5.09,2120.8019,860.95472)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,-1.65 -1.35,-3 -3,-3 l -74,0 c -1.649,0 -3,1.35 -3,3 l 0,50 c 0,1.65 1.351,3 3,3 l 74,0 c 1.65,0 3,-1.35 3,-3 L 0,0 z" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3560-5" /></g><g + style="display:inline" + id="g3562-4" + transform="matrix(5.09,0,0,-5.09,-1701.7885,3426.3146)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3564-6" /><g + id="g3574-7"><g + style="opacity:0.5" + clip-path="url(#clipPath3566-8)" + id="g3576-7"><g + id="g3578-2"><g + clip-path="url(#clipPath3570-6)" + id="g3580-1"><path + inkscape:connector-curvature="0" + d="m 710.994,535.863 -31.988,0 0,12.002 31.988,0 0,-12.002 z" + style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3582-0" /></g></g></g></g></g><path + inkscape:connector-curvature="0" + d="m 2008.8219,748.97472 -254.5,0 0,-25.45 254.5,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3584-1" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><path + inkscape:connector-curvature="0" + d="m 1891.7519,799.87472 -137.43,0 0,-25.45 137.43,0 0,25.45 z" + style="fill:#d1d2d3;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="path3586-5" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" /><g + style="display:inline" + id="g3588-5" + transform="matrix(5.09,0,0,-5.09,-1701.7885,3426.3146)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><g + id="g3590-3" /><g + id="g3596-7"><g + style="opacity:0" + clip-path="url(#clipPath3592-5)" + id="g3598-6"><path + inkscape:connector-curvature="0" + d="m 730.777,537 12,0 0,12 -12,0 0,-12 z" + style="fill:#03a9f4;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3600-8" /></g></g></g><g + style="display:inline" + id="g3602-7" + transform="matrix(5.09,0,0,-5.09,2073.8587,661.17222)" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999"><path + inkscape:connector-curvature="0" + d="m 0,0 c 0,0.414 -0.336,0.75 -0.75,0.75 l -2.75,0 -2.5,4 -1,0 1.25,-4 -2.75,0 -0.75,1 -0.75,0 0.5,-1.75 -0.5,-1.75 0.75,0 0.75,1 2.75,0 -1.25,-4 1,0 2.5,4 2.75,0 C -0.336,-0.75 0,-0.414 0,0" + style="fill:#00c853;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3604-3" /></g><g + transform="matrix(1.25,0,0,-1.25,0,1350)" + id="g3012" + style="display:inline" + inkscape:export-xdpi="18.559999" + inkscape:export-ydpi="18.559999" + inkscape:export-filename="C:\Users\Joe Fernandez\Downloads\Android Wear Artwork\g3014.png"><g + transform="translate(1677.8121,922.30928)" + id="g3072" + style="fill:#333333;fill-opacity:1"><path + inkscape:connector-curvature="0" + d="m 0,0 -285.486,0 -14,-190.491 313.486,0 L 0,0 z" + id="path3074" + style="fill:#747474;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><g + transform="translate(1691.8121,352.84148)" + id="g3076" + style="fill:#333333;fill-opacity:1"><path + inkscape:connector-curvature="0" + d="m 0,0 -313.486,0 14,-190.491 286.486,0 L 0,0 z" + id="path3078" + style="fill:#747474;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g><path + inkscape:connector-curvature="0" + d="m 1721.07,355.32978 -374,0 0,374 374,0 0,-374 z m 3.332,409 -378.666,0 c -14.729,0 -26.666,-11.938 -26.666,-26.667 l 0,-5.333 0,-380 0,-5.334 c 0,-14.727 11.937,-26.666 26.666,-26.666 l 378.666,0 c 14.729,0 26.668,11.939 26.668,26.666 l 0,5.334 0,380 0,5.333 c 0,14.729 -11.939,26.667 -26.668,26.667" + id="path3080" + style="fill:#747474;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g + transform="translate(290.06807,8.3297801)" + id="g3082"><g + id="g3084" /><g + id="g3090"><g + clip-path="url(#clipPath3086-4)" + id="g3092" + style="opacity:0.10000598"><path + inkscape:connector-curvature="0" + d="m 1432.002,722 -374,0 0,-374 374,0 0,374 z m -3,-371 -368,0 0,368 368,0 0,-368 z" + id="path3094" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g><g + transform="translate(290.06807,8.3297801)" + id="g3096"><g + id="g3098" /><g + id="g3104"><g + clip-path="url(#clipPath3100-8)" + id="g3106" + style="opacity:0.10000598"><g + transform="translate(1434.334,317.334)" + id="g3108"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,11.938 -26.666,26.666 l 0,-5.334 c 0,-14.727 11.937,-26.666 26.666,-26.666 L 0,-5.334 c 14.729,0 26.668,11.939 26.668,26.666 l 0,5.334 C 26.668,11.938 14.729,0 0,0" + id="path3110" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g><g + transform="translate(290.06807,8.3297801)" + id="g3112"><g + id="g3114" /><g + id="g3120"><g + clip-path="url(#clipPath3116-4)" + id="g3122" + style="opacity:0.10000598"><g + transform="translate(1434.334,756)" + id="g3124"><path + inkscape:connector-curvature="0" + d="m 0,0 -378.666,0 c -14.729,0 -26.666,-11.938 -26.666,-26.667 l 0,-5.333 c 0,14.728 11.937,26.667 26.666,26.667 L 0,-5.333 c 14.729,0 26.668,-11.939 26.668,-26.667 l 0,5.333 C 26.668,-11.938 14.729,0 0,0" + id="path3126" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></g></g></g><g + id="g3744" + transform="matrix(1.25,0,0,-1.25,-298.02174,762.5543)"><g + id="g3746" /><g + id="g3752"><g + clip-path="url(#clipPath3748-6)" + id="g3754" + style="opacity:0"><path + d="m 1484,446 -270,0 0,182 270,0 0,-182 z" + style="fill:#ec008c;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path3756" + inkscape:connector-curvature="0" /></g></g></g><g + id="g3758" + transform="matrix(1.25,0,0,-1.25,1378.2283,155.0543)" /><g + id="g3762" + transform="matrix(1.25,0,0,-1.25,1539.4783,28.8043)" /></g></svg>
\ No newline at end of file diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index bec1d38..1fac5b6 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1058,54 +1058,72 @@ public abstract class Drawable { final Drawable drawable; final String name = parser.getName(); - if (name.equals("selector")) { - drawable = new StateListDrawable(); - } else if (name.equals("animated-selector")) { - drawable = new AnimatedStateListDrawable(); - } else if (name.equals("level-list")) { - drawable = new LevelListDrawable(); - } else if (name.equals("layer-list")) { - drawable = new LayerDrawable(); - } else if (name.equals("transition")) { - drawable = new TransitionDrawable(); - } else if (name.equals("ripple")) { - drawable = new RippleDrawable(); - } else if (name.equals("color")) { - drawable = new ColorDrawable(); - } else if (name.equals("shape")) { - drawable = new GradientDrawable(); - } else if (name.equals("vector")) { - drawable = new VectorDrawable(); - } else if (name.equals("animated-vector")) { - drawable = new AnimatedVectorDrawable(); - } else if (name.equals("scale")) { - drawable = new ScaleDrawable(); - } else if (name.equals("clip")) { - drawable = new ClipDrawable(); - } else if (name.equals("rotate")) { - drawable = new RotateDrawable(); - } else if (name.equals("animated-rotate")) { - drawable = new AnimatedRotateDrawable(); - } else if (name.equals("animation-list")) { - drawable = new AnimationDrawable(); - } else if (name.equals("inset")) { - drawable = new InsetDrawable(); - } else if (name.equals("bitmap")) { - //noinspection deprecation - drawable = new BitmapDrawable(r); - if (r != null) { - ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); - } - } else if (name.equals("nine-patch")) { - drawable = new NinePatchDrawable(); - if (r != null) { - ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); - } - } else { - throw new XmlPullParserException(parser.getPositionDescription() + - ": invalid drawable tag " + name); - } + switch (name) { + case "selector": + drawable = new StateListDrawable(); + break; + case "animated-selector": + drawable = new AnimatedStateListDrawable(); + break; + case "level-list": + drawable = new LevelListDrawable(); + break; + case "layer-list": + drawable = new LayerDrawable(); + break; + case "transition": + drawable = new TransitionDrawable(); + break; + case "ripple": + drawable = new RippleDrawable(); + break; + case "color": + drawable = new ColorDrawable(); + break; + case "shape": + drawable = new GradientDrawable(); + break; + case "vector": + drawable = new VectorDrawable(); + break; + case "animated-vector": + drawable = new AnimatedVectorDrawable(); + break; + case "scale": + drawable = new ScaleDrawable(); + break; + case "clip": + drawable = new ClipDrawable(); + break; + case "rotate": + drawable = new RotateDrawable(); + break; + case "animated-rotate": + drawable = new AnimatedRotateDrawable(); + break; + case "animation-list": + drawable = new AnimationDrawable(); + break; + case "inset": + drawable = new InsetDrawable(); + break; + case "bitmap": + drawable = new BitmapDrawable(r); + if (r != null) { + ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); + } + break; + case "nine-patch": + drawable = new NinePatchDrawable(); + if (r != null) { + ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); + } + break; + default: + throw new XmlPullParserException(parser.getPositionDescription() + + ": invalid drawable tag " + name); + } drawable.inflate(r, parser, attrs, theme); return drawable; } diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index 5808d20..1344dd9 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -26,6 +26,7 @@ testFiles := \ Idmap_test.cpp \ ResTable_test.cpp \ Split_test.cpp \ + TestHelpers.cpp \ Theme_test.cpp \ TypeWrappers_test.cpp \ ZipUtils_test.cpp diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index 89d271d0..6a9314e 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -37,8 +37,6 @@ namespace { #include "data/lib/lib_arsc.h" -enum { MAY_NOT_BE_BAG = false }; - TEST(ResTableTest, shouldLoadSuccessfully) { ResTable table; ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); @@ -48,15 +46,7 @@ TEST(ResTableTest, simpleTypeIsRetrievedCorrectly) { ResTable table; ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); - Res_value val; - ssize_t block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG); - - ASSERT_GE(block, 0); - ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); - - const ResStringPool* pool = table.getTableStringBlock(block); - ASSERT_TRUE(NULL != pool); - ASSERT_EQ(String8("test1"), pool->string8ObjectAt(val.data)); + EXPECT_TRUE(IsStringEqual(table, base::R::string::test1, "test1")); } TEST(ResTableTest, resourceNameIsResolved) { diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp index f63f566..b69d685 100644 --- a/libs/androidfw/tests/Split_test.cpp +++ b/libs/androidfw/tests/Split_test.cpp @@ -42,6 +42,9 @@ namespace { * Package: com.android.test.basic */ #include "data/basic/split_de_fr_arsc.h" +#include "data/basic/split_hdpi_v4_arsc.h" +#include "data/basic/split_xhdpi_v4_arsc.h" +#include "data/basic/split_xxhdpi_v4_arsc.h" /** * Include a binary resource table. This table @@ -163,6 +166,33 @@ TEST(SplitTest, TypeEntrySpecFlagsAreUpdated) { EXPECT_EQ(ResTable_config::CONFIG_LOCALE, frSpecFlags); } +TEST(SplitTest, SelectBestDensity) { + ResTable_config baseConfig; + memset(&baseConfig, 0, sizeof(baseConfig)); + baseConfig.density = ResTable_config::DENSITY_XHIGH; + baseConfig.sdkVersion = 21; + + ResTable table; + table.setParameters(&baseConfig); + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + ASSERT_EQ(NO_ERROR, table.add(split_hdpi_v4_arsc, split_hdpi_v4_arsc_len)); + + EXPECT_TRUE(IsStringEqual(table, base::R::string::density, "hdpi")); + + ASSERT_EQ(NO_ERROR, table.add(split_xhdpi_v4_arsc, split_xhdpi_v4_arsc_len)); + + EXPECT_TRUE(IsStringEqual(table, base::R::string::density, "xhdpi")); + + ASSERT_EQ(NO_ERROR, table.add(split_xxhdpi_v4_arsc, split_xxhdpi_v4_arsc_len)); + + EXPECT_TRUE(IsStringEqual(table, base::R::string::density, "xhdpi")); + + baseConfig.density = ResTable_config::DENSITY_XXHIGH; + table.setParameters(&baseConfig); + + EXPECT_TRUE(IsStringEqual(table, base::R::string::density, "xxhdpi")); +} + TEST(SplitFeatureTest, TestNewResourceIsAccessible) { ResTable table; ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); @@ -188,7 +218,7 @@ TEST(SplitFeatureTest, TestNewResourceNameHasCorrectName) { ASSERT_EQ(NO_ERROR, table.add(feature_arsc, feature_arsc_len)); - EXPECT_TRUE(table.getResourceName(base::R::string::test3, false, &name)); + ASSERT_TRUE(table.getResourceName(base::R::string::test3, false, &name)); EXPECT_EQ(String16("com.android.test.basic"), String16(name.package, name.packageLen)); diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp new file mode 100644 index 0000000..41a19a7 --- /dev/null +++ b/libs/androidfw/tests/TestHelpers.cpp @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#include "TestHelpers.h" + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> +#include <gtest/gtest.h> + +namespace android { + +::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resourceId, const char* expectedStr) { + Res_value val; + ssize_t block = table.getResource(resourceId, &val, MAY_NOT_BE_BAG); + if (block < 0) { + return ::testing::AssertionFailure() << "could not find resource"; + } + + if (val.dataType != Res_value::TYPE_STRING) { + return ::testing::AssertionFailure() << "resource is not a string"; + } + + const ResStringPool* pool = table.getTableStringBlock(block); + if (pool == NULL) { + return ::testing::AssertionFailure() << "table has no string pool for block " << block; + } + + const String8 actual = pool->string8ObjectAt(val.data); + if (String8(expectedStr) != actual) { + return ::testing::AssertionFailure() << actual.string(); + } + return ::testing::AssertionSuccess() << actual.string(); +} + +} // namespace android diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index fe2e5ce..ac80d88 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -6,6 +6,7 @@ #include <androidfw/ResourceTypes.h> #include <utils/String8.h> #include <utils/String16.h> +#include <gtest/gtest.h> static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) { return out << str.string(); @@ -17,6 +18,8 @@ static inline ::std::ostream& operator<<(::std::ostream& out, const android::Str namespace android { +enum { MAY_NOT_BE_BAG = false }; + static inline bool operator==(const android::ResTable_config& a, const android::ResTable_config& b) { return memcmp(&a, &b, sizeof(a)) == 0; } @@ -25,6 +28,8 @@ static inline ::std::ostream& operator<<(::std::ostream& out, const android::Res return out << c.toString().string(); } +::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resourceId, const char* expectedStr); + } // namespace android #endif // __TEST_HELPERS_H diff --git a/libs/androidfw/tests/data/app/R.h b/libs/androidfw/tests/data/app/R.h index 780a116..23e68e3 100644 --- a/libs/androidfw/tests/data/app/R.h +++ b/libs/androidfw/tests/data/app/R.h @@ -1,3 +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. + */ + #ifndef __APP_R_H #define __APP_R_H diff --git a/libs/androidfw/tests/data/app/build b/libs/androidfw/tests/data/app/build index 89c4641..62257bc 100755 --- a/libs/androidfw/tests/data/app/build +++ b/libs/androidfw/tests/data/app/build @@ -1,4 +1,19 @@ #!/bin/bash +# +# 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. +# aapt package -v -I ../system/bundle.apk -M AndroidManifest.xml -S res -F bundle.apk -f && \ unzip bundle.apk resources.arsc && \ diff --git a/libs/androidfw/tests/data/app/res/values/values.xml b/libs/androidfw/tests/data/app/res/values/values.xml index b0ead38..c1cf64c 100644 --- a/libs/androidfw/tests/data/app/res/values/values.xml +++ b/libs/androidfw/tests/data/app/res/values/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <attr name="number" format="integer"/> <style name="Theme.One" parent="@android:style/Theme.One"> diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h index 363dcb9..aaac740 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -1,3 +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. + */ + #ifndef __BASE_R_H #define __BASE_R_H @@ -21,9 +37,10 @@ namespace string { enum { test1 = 0x7f030000, // default test2 = 0x7f030001, // default + density = 0x7f030002, // default - test3 = 0x7f070000, // default (in feature) - test4 = 0x7f070001, // default (in feature) + test3 = 0x7f080000, // default (in feature) + test4 = 0x7f080001, // default (in feature) }; } @@ -32,7 +49,7 @@ namespace integer { number1 = 0x7f040000, // default, sv number2 = 0x7f040001, // default - test3 = 0x7f080000, // default (in feature) + test3 = 0x7f090000, // default (in feature) }; } diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h index 61cb94c..13ab4fa 100644 --- a/libs/androidfw/tests/data/basic/basic_arsc.h +++ b/libs/androidfw/tests/data/basic/basic_arsc.h @@ -1,5 +1,5 @@ unsigned char basic_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x60, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x68, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, @@ -16,7 +16,7 @@ unsigned char basic_arsc[] = { 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, - 0x98, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0xa0, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, @@ -101,61 +101,61 @@ unsigned char basic_arsc[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, - 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, - 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, - 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00, - 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, - 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, - 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x7f, - 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, - 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, + 0x08, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x7f, + 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x7f, 0x10, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, - 0x03, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00 }; -unsigned int basic_arsc_len = 1888; +unsigned int basic_arsc_len = 1896; diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build index 036e468..fd289fa 100755 --- a/libs/androidfw/tests/data/basic/build +++ b/libs/androidfw/tests/data/basic/build @@ -1,11 +1,39 @@ #!/bin/bash +# +# 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. +# PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar -aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split fr,de -F bundle.apk -f && \ +aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split hdpi --split xhdpi --split xxhdpi --split fr,de -F bundle.apk -f && \ unzip bundle.apk resources.arsc && \ mv resources.arsc basic.arsc && \ xxd -i basic.arsc > basic_arsc.h && \ +\ unzip bundle_de_fr.apk resources.arsc && \ mv resources.arsc split_de_fr.arsc && \ -xxd -i split_de_fr.arsc > split_de_fr_arsc.h +xxd -i split_de_fr.arsc > split_de_fr_arsc.h && \ +\ +unzip bundle_hdpi-v4.apk resources.arsc && \ +mv resources.arsc split_hdpi_v4.arsc && \ +xxd -i split_hdpi_v4.arsc > split_hdpi_v4_arsc.h && \ +\ +unzip bundle_xhdpi-v4.apk resources.arsc && \ +mv resources.arsc split_xhdpi_v4.arsc && \ +xxd -i split_xhdpi_v4.arsc > split_xhdpi_v4_arsc.h && \ +\ +unzip bundle_xxhdpi-v4.apk resources.arsc && \ +mv resources.arsc split_xxhdpi_v4.arsc && \ +xxd -i split_xxhdpi_v4.arsc > split_xxhdpi_v4_arsc.h \ diff --git a/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml b/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml index 05ffd58..0dcf7e0 100644 --- a/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml +++ b/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml @@ -1,3 +1,18 @@ <?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. +--> + <merge> </merge> diff --git a/libs/androidfw/tests/data/basic/res/layout/main.xml b/libs/androidfw/tests/data/basic/res/layout/main.xml index 05ffd58..0dcf7e0 100644 --- a/libs/androidfw/tests/data/basic/res/layout/main.xml +++ b/libs/androidfw/tests/data/basic/res/layout/main.xml @@ -1,3 +1,18 @@ <?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. +--> + <merge> </merge> diff --git a/libs/androidfw/tests/data/basic/res/values-de/values.xml b/libs/androidfw/tests/data/basic/res/values-de/values.xml index 103c6a3..2683a7e 100644 --- a/libs/androidfw/tests/data/basic/res/values-de/values.xml +++ b/libs/androidfw/tests/data/basic/res/values-de/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <string name="test1">versuch 1</string> <string name="test2">versuch 2</string> diff --git a/libs/androidfw/tests/data/basic/res/values-fr/values.xml b/libs/androidfw/tests/data/basic/res/values-fr/values.xml index 1806a2d..7d3bed3 100644 --- a/libs/androidfw/tests/data/basic/res/values-fr/values.xml +++ b/libs/androidfw/tests/data/basic/res/values-fr/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <string name="test1">essai 1</string> <string name="test2">essai 2</string> diff --git a/libs/androidfw/tests/data/basic/res/values-hdpi/values.xml b/libs/androidfw/tests/data/basic/res/values-hdpi/values.xml new file mode 100644 index 0000000..04bf943 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values-hdpi/values.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<resources> + <string name="density">hdpi</string> +</resources> diff --git a/libs/androidfw/tests/data/basic/res/values-sv/values.xml b/libs/androidfw/tests/data/basic/res/values-sv/values.xml index 9d52307..7351b49 100644 --- a/libs/androidfw/tests/data/basic/res/values-sv/values.xml +++ b/libs/androidfw/tests/data/basic/res/values-sv/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <integer name="number1">400</integer> </resources> diff --git a/libs/androidfw/tests/data/basic/res/values-xhdpi/values.xml b/libs/androidfw/tests/data/basic/res/values-xhdpi/values.xml new file mode 100644 index 0000000..845e9a0 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values-xhdpi/values.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<resources> + <string name="density">xhdpi</string> +</resources> diff --git a/libs/androidfw/tests/data/basic/res/values-xxhdpi/values.xml b/libs/androidfw/tests/data/basic/res/values-xxhdpi/values.xml new file mode 100644 index 0000000..964da02 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values-xxhdpi/values.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<resources> + <string name="density">xxhdpi</string> +</resources> diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 662eda6..a010cca 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <attr name="attr1" format="reference|integer" /> <attr name="attr2" format="reference|integer" /> diff --git a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h index a8eaf0b..b742d28 100644 --- a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h +++ b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h @@ -1,5 +1,5 @@ unsigned char split_de_fr_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0xd8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0xe4, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, @@ -10,7 +10,7 @@ unsigned char split_de_fr_arsc[] = { 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00, - 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x50, 0x03, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x5c, 0x03, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, @@ -55,24 +55,25 @@ unsigned char split_de_fr_arsc[] = { 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, @@ -82,4 +83,4 @@ unsigned char split_de_fr_arsc[] = { 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -unsigned int split_de_fr_arsc_len = 984; +unsigned int split_de_fr_arsc_len = 996; diff --git a/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h new file mode 100644 index 0000000..e9fb7ea --- /dev/null +++ b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h @@ -0,0 +1,68 @@ +unsigned char split_hdpi_v4_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x08, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x68, 0x00, + 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, + 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, + 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x73, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int split_hdpi_v4_arsc_len = 776; diff --git a/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h new file mode 100644 index 0000000..7835f71 --- /dev/null +++ b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h @@ -0,0 +1,68 @@ +unsigned char split_xhdpi_v4_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x78, 0x00, + 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0xb0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x64, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int split_xhdpi_v4_arsc_len = 780; diff --git a/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h new file mode 100644 index 0000000..f805db1 --- /dev/null +++ b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h @@ -0,0 +1,68 @@ +unsigned char split_xxhdpi_v4_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x78, 0x00, + 0x78, 0x00, 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0xb0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x64, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int split_xxhdpi_v4_arsc_len = 780; diff --git a/libs/androidfw/tests/data/feature/build b/libs/androidfw/tests/data/feature/build index b547dc2..0f3307f 100755 --- a/libs/androidfw/tests/data/feature/build +++ b/libs/androidfw/tests/data/feature/build @@ -1,4 +1,19 @@ #!/bin/bash +# +# 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. +# aapt package -M AndroidManifest.xml -S res --feature-of ../basic/bundle.apk -F bundle.apk -f && \ unzip bundle.apk resources.arsc && \ diff --git a/libs/androidfw/tests/data/feature/feature_arsc.h b/libs/androidfw/tests/data/feature/feature_arsc.h index cf7647d..cd29910 100644 --- a/libs/androidfw/tests/data/feature/feature_arsc.h +++ b/libs/androidfw/tests/data/feature/feature_arsc.h @@ -1,11 +1,11 @@ unsigned char feature_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x40, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x44, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x33, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf4, 0x02, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, @@ -28,46 +28,46 @@ unsigned char feature_arsc[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x3c, 0x00, 0x65, 0x00, 0x6d, 0x00, - 0x70, 0x00, 0x74, 0x00, 0x79, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, - 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, - 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, - 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x33, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, - 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x3c, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x79, 0x00, 0x3e, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x33, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x74, 0x00, 0x34, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, + 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x33, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x6c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, - 0xc8, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00 }; -unsigned int feature_arsc_len = 832; +unsigned int feature_arsc_len = 836; diff --git a/libs/androidfw/tests/data/feature/res/values/values.xml b/libs/androidfw/tests/data/feature/res/values/values.xml index d03445a..343fd6c 100644 --- a/libs/androidfw/tests/data/feature/res/values/values.xml +++ b/libs/androidfw/tests/data/feature/res/values/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <string name="test3">test3</string> <string name="test4">test4</string> diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib/R.h index 13bf095..ff31120 100644 --- a/libs/androidfw/tests/data/lib/R.h +++ b/libs/androidfw/tests/data/lib/R.h @@ -1,3 +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. + */ + #ifndef __LIB_R_H #define __LIB_R_H diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib/build index 8e6e70c..4102903 100755 --- a/libs/androidfw/tests/data/lib/build +++ b/libs/androidfw/tests/data/lib/build @@ -1,4 +1,19 @@ #!/bin/bash +# +# 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. +# aapt package -M AndroidManifest.xml -S res -F bundle.apk -f --shared-lib && \ unzip bundle.apk resources.arsc && \ diff --git a/libs/androidfw/tests/data/lib/lib_arsc.h b/libs/androidfw/tests/data/lib/lib_arsc.h index d670c5b..dd3dad5 100644 --- a/libs/androidfw/tests/data/lib/lib_arsc.h +++ b/libs/androidfw/tests/data/lib/lib_arsc.h @@ -1,8 +1,8 @@ unsigned char lib_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0xc8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0xb8, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xa0, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x90, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, @@ -37,48 +37,25 @@ unsigned char lib_arsc[] = { 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, - 0x03, 0x02, 0x0c, 0x00, 0x10, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, - 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, - 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, - 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x08, 0x00, 0x00, 0x10, 0xbc, 0x02, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x10, 0xbc, 0x02, 0x00, 0x00 }; -unsigned int lib_arsc_len = 968; +unsigned int lib_arsc_len = 696; diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib/res/values/values.xml index a77f0c7..3ec79b1 100644 --- a/libs/androidfw/tests/data/lib/res/values/values.xml +++ b/libs/androidfw/tests/data/lib/res/values/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <attr name="attr1" format="integer" /> diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build index 87cf6de..f737677 100755 --- a/libs/androidfw/tests/data/overlay/build +++ b/libs/androidfw/tests/data/overlay/build @@ -1,4 +1,19 @@ #!/bin/bash +# +# 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. +# aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \ unzip bundle.apk resources.arsc && \ diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml index 227e889..3e1af98 100644 --- a/libs/androidfw/tests/data/overlay/res/values/values.xml +++ b/libs/androidfw/tests/data/overlay/res/values/values.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <string name="test2">test2-overlay</string> <integer-array name="integerArray1"> diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h index 7a9d3db..27f25fe 100644 --- a/libs/androidfw/tests/data/system/R.h +++ b/libs/androidfw/tests/data/system/R.h @@ -1,3 +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. + */ + #ifndef __ANDROID_R_H #define __ANDROID_R_H diff --git a/libs/androidfw/tests/data/system/build b/libs/androidfw/tests/data/system/build index 2a3ac0b..1a70e84 100755 --- a/libs/androidfw/tests/data/system/build +++ b/libs/androidfw/tests/data/system/build @@ -1,4 +1,19 @@ #!/bin/bash +# +# 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. +# aapt package -x -M AndroidManifest.xml -S res -F bundle.apk -f && \ unzip bundle.apk resources.arsc && \ diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml index b29848e..35d43c7 100644 --- a/libs/androidfw/tests/data/system/res/values/themes.xml +++ b/libs/androidfw/tests/data/system/res/values/themes.xml @@ -1,4 +1,19 @@ <?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. +--> + <resources> <public name="background" type="attr" id="0x01010000"/> <public name="foreground" type="attr" id="0x01010001"/> diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 70ff6e5..40cd13e 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -296,6 +296,9 @@ void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount(); } } + // Damage with the old display list first then the new one to catch any + // changes in isRenderable or, in the future, bounds + damageSelf(info); deleteDisplayListData(); mDisplayListData = mStagingDisplayListData; mStagingDisplayListData = NULL; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 3bb4778..9d2ae8b 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -42,7 +42,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, : mRenderThread(thread) , mEglManager(thread.eglManager()) , mEglSurface(EGL_NO_SURFACE) - , mDirtyRegionsEnabled(false) + , mBufferPreserved(false) + , mSwapBehavior(kSwap_default) , mOpaque(!translucent) , mCanvas(NULL) , mHaveNewSurface(false) @@ -82,7 +83,8 @@ void CanvasContext::setSurface(ANativeWindow* window) { } if (mEglSurface != EGL_NO_SURFACE) { - mDirtyRegionsEnabled = mEglManager.enableDirtyRegions(mEglSurface); + const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer); + mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); mHaveNewSurface = true; makeCurrent(); } else { @@ -103,6 +105,10 @@ void CanvasContext::requireSurface() { makeCurrent(); } +void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { + mSwapBehavior = swapBehavior; +} + bool CanvasContext::initialize(ANativeWindow* window) { setSurface(window); if (mCanvas) return false; @@ -200,7 +206,7 @@ void CanvasContext::draw() { if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { mCanvas->setViewport(width, height); dirty.setEmpty(); - } else if (!mDirtyRegionsEnabled || mHaveNewSurface) { + } else if (!mBufferPreserved || mHaveNewSurface) { dirty.setEmpty(); } else { if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index d4282fa..e20564b 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -48,6 +48,11 @@ namespace renderthread { class EglManager; +enum SwapBehavior { + kSwap_default, + kSwap_discardBuffer, +}; + // This per-renderer class manages the bridge between the global EGL context // and the render surface. // TODO: Rename to Renderer or some other per-window, top-level manager @@ -57,6 +62,9 @@ public: IContextFactory* contextFactory); virtual ~CanvasContext(); + // Won't take effect until next EGLSurface creation + void setSwapBehavior(SwapBehavior swapBehavior); + bool initialize(ANativeWindow* window); void updateSurface(ANativeWindow* window); void pauseSurface(ANativeWindow* window); @@ -111,7 +119,8 @@ private: EglManager& mEglManager; sp<ANativeWindow> mNativeWindow; EGLSurface mEglSurface; - bool mDirtyRegionsEnabled; + bool mBufferPreserved; + SwapBehavior mSwapBehavior; bool mOpaque; OpenGLRenderer* mCanvas; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index a87834e..760fc15 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -70,12 +70,12 @@ EglManager::EglManager(RenderThread& thread) , mEglConfig(0) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mRequestDirtyRegions(load_dirty_regions_property()) + , mAllowPreserveBuffer(load_dirty_regions_property()) , mCurrentSurface(EGL_NO_SURFACE) , mAtlasMap(NULL) , mAtlasMapSize(0) { - mCanSetDirtyRegions = mRequestDirtyRegions; - ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false"); + mCanSetPreserveBuffer = mAllowPreserveBuffer; + ALOGD("Use EGL_SWAP_BEHAVIOR_PRESERVED: %s", mAllowPreserveBuffer ? "true" : "false"); } void EglManager::initialize() { @@ -113,7 +113,7 @@ void EglManager::requireGlContext() { } void EglManager::loadConfig() { - EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + EGLint swapBehavior = mCanSetPreserveBuffer ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, @@ -131,10 +131,10 @@ void EglManager::loadConfig() { if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs) || num_configs != 1) { // Failed to get a valid config - if (mCanSetDirtyRegions) { + if (mCanSetPreserveBuffer) { ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); // Try again without dirty regions enabled - mCanSetDirtyRegions = false; + mCanSetPreserveBuffer = false; loadConfig(); } else { LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str()); @@ -273,25 +273,30 @@ bool EglManager::swapBuffers(EGLSurface surface) { return false; } -bool EglManager::enableDirtyRegions(EGLSurface surface) { - if (!mRequestDirtyRegions) return false; +bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) { + if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false; - if (mCanSetDirtyRegions) { - if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) { + bool preserved = false; + if (mCanSetPreserveBuffer) { + preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, + preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED); + if (CC_UNLIKELY(!preserved)) { ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", (void*) surface, egl_error_str()); - return false; } - return true; } - // Perhaps it is already enabled? - EGLint value; - if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) { - ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p", - (void*) surface, egl_error_str()); - return false; + if (CC_UNLIKELY(!preserved)) { + // Maybe it's already set? + EGLint swapBehavior; + if (eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &swapBehavior)) { + preserved = (swapBehavior == EGL_BUFFER_PRESERVED); + } else { + ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p", + (void*) surface, egl_error_str()); + } } - return value == EGL_BUFFER_PRESERVED; + + return preserved; } } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 71213fb..ae03ea1 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -49,7 +49,8 @@ public: void beginFrame(EGLSurface surface, EGLint* width, EGLint* height); bool swapBuffers(EGLSurface surface); - bool enableDirtyRegions(EGLSurface surface); + // Returns true iff the surface is now preserving buffers. + bool setPreserveBuffer(EGLSurface surface, bool preserve); void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize); @@ -71,8 +72,8 @@ private: EGLContext mEglContext; EGLSurface mPBufferSurface; - const bool mRequestDirtyRegions; - bool mCanSetDirtyRegions; + const bool mAllowPreserveBuffer; + bool mCanSetPreserveBuffer; EGLSurface mCurrentSurface; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 047819d..8f99b4e 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -103,6 +103,18 @@ void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) { post(task); } +CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) { + args->context->setSwapBehavior(args->swapBehavior); + return NULL; +} + +void RenderProxy::setSwapBehavior(SwapBehavior swapBehavior) { + SETUP_TASK(setSwapBehavior); + args->context = mContext; + args->swapBehavior = swapBehavior; + post(task); +} + CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) { bool needsRedraw = false; if (Caches::hasInstance()) { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 678e7e2..dddf0c7 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -31,6 +31,7 @@ #include "../Caches.h" #include "../IContextFactory.h" +#include "CanvasContext.h" #include "DrawFrameTask.h" namespace android { @@ -44,7 +45,6 @@ class Rect; namespace renderthread { -class CanvasContext; class ErrorChannel; class RenderThread; class RenderProxyBridge; @@ -63,6 +63,8 @@ public: ANDROID_API virtual ~RenderProxy(); ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos); + // Won't take effect until next EGLSurface creation + ANDROID_API void setSwapBehavior(SwapBehavior swapBehavior); ANDROID_API bool loadSystemProperties(); ANDROID_API bool initialize(const sp<ANativeWindow>& window); diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 819affc..5d5ea02 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -332,8 +332,13 @@ public abstract class TvInputService extends Service { } /** - * Sends the change on the track information. This is expected to be called whenever a track - * is added/removed and the metadata of a track is modified. + * Sends the list of all audio/video/subtitle tracks. The is used by the framework to + * maintain the track information for a given session, which in turn is used by + * {@link TvView#getTracks} for the application to retrieve metadata for a given track type. + * The TV input service must call this method as soon as the track information becomes + * available or is updated. Note that in a case where a part of the information for a + * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object + * with a different track ID. * * @param tracks A list which includes track information. * @throws IllegalArgumentException if {@code tracks} contains redundant tracks. @@ -364,8 +369,12 @@ public abstract class TvInputService extends Service { } /** - * Sends the ID of the selected track for a given track type. This is expected to be called - * whenever there is a change on track selection. + * Sends the type and ID of a selected track. This is used to inform the application that a + * specific track is selected. The TV input service must call this method as soon as a track + * is selected either by default or in response to a call to {@link #onSelectTrack}. The + * selected track ID for a given type is maintained in the framework until the next call to + * this method even after the entire track list is updated (but is reset when the session is + * tuned to a new channel), so care must be taken not to result in an obsolete track ID. * * @param type The type of the selected track. The type can be * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or diff --git a/media/tests/omxjpegdecoder/Android.mk b/media/tests/omxjpegdecoder/Android.mk deleted file mode 100644 index b0bc5d4..0000000 --- a/media/tests/omxjpegdecoder/Android.mk +++ /dev/null @@ -1,45 +0,0 @@ -# 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. - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - omx_jpeg_decoder.cpp \ - jpeg_decoder_bench.cpp \ - StreamSource.cpp - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libskia \ - libstagefright \ - libstagefright_foundation \ - libbinder \ - libutils \ - liblog \ - libjpeg - -LOCAL_C_INCLUDES := \ - $(TOP)/external/jpeg \ - $(TOP)/frameworks/base/media/libstagefright \ - $(TOP)/frameworks/base/include/ \ - $(TOP)/frameworks/base/ \ - $(TOP)/frameworks/native/include/media/openmax - -LOCAL_MODULE := jpeg_bench - -LOCAL_MODULE_TAGS := optional - -include $(BUILD_EXECUTABLE) diff --git a/media/tests/omxjpegdecoder/StreamSource.cpp b/media/tests/omxjpegdecoder/StreamSource.cpp deleted file mode 100644 index f764121a..0000000 --- a/media/tests/omxjpegdecoder/StreamSource.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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. - */ - -#include <media/stagefright/foundation/ADebug.h> - -#include "StreamSource.h" - -namespace android { - -StreamSource::StreamSource(SkStream *stream) - : mStream(stream) { - CHECK(stream != NULL); - mSize = stream->getLength(); -} - -StreamSource::~StreamSource() { - delete mStream; - mStream = NULL; -} - -status_t StreamSource::initCheck() const { - return mStream != NULL ? OK : NO_INIT; -} - -ssize_t StreamSource::readAt(off64_t offset, void *data, size_t size) { - Mutex::Autolock autoLock(mLock); - - mStream->rewind(); - mStream->skip(offset); - ssize_t result = mStream->read(data, size); - - return result; -} - -status_t StreamSource::getSize(off64_t *size) { - *size = mSize; - return OK; -} - -} // namespace android diff --git a/media/tests/omxjpegdecoder/StreamSource.h b/media/tests/omxjpegdecoder/StreamSource.h deleted file mode 100644 index 9807385..0000000 --- a/media/tests/omxjpegdecoder/StreamSource.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -#ifndef STREAM_SOURCE_H_ - -#define STREAM_SOURCE_H_ - -#include <stdio.h> - -#include <SkStream.h> -#include <media/stagefright/DataSource.h> -#include <media/stagefright/MediaErrors.h> -#include <utils/threads.h> - -namespace android { - -class StreamSource : public DataSource { -public: - // Pass the ownership of SkStream to StreamSource. - StreamSource(SkStream *SkStream); - virtual status_t initCheck() const; - virtual ssize_t readAt(off64_t offset, void *data, size_t size); - virtual status_t getSize(off64_t *size); - -protected: - virtual ~StreamSource(); - -private: - SkStream *mStream; - size_t mSize; - Mutex mLock; - - StreamSource(const StreamSource &); - StreamSource &operator=(const StreamSource &); -}; - -} // namespace android - -#endif // STREAM_SOURCE_H_ diff --git a/media/tests/omxjpegdecoder/jpeg_decoder_bench.cpp b/media/tests/omxjpegdecoder/jpeg_decoder_bench.cpp deleted file mode 100644 index de6294d..0000000 --- a/media/tests/omxjpegdecoder/jpeg_decoder_bench.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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. - */ - -#define LOG_TAG "OmxJpegDecoder" -#include <sys/time.h> -#include <utils/Log.h> - -#include <binder/ProcessState.h> - -#include "SkBitmap.h" -#include "SkImageDecoder.h" -#include "SkStream.h" -#include "omx_jpeg_decoder.h" - -class SkJPEGImageDecoder : public SkImageDecoder { -public: - virtual Format getFormat() const { - return kJPEG_Format; - } - -protected: - virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode); -}; - -int nullObjectReturn(const char msg[]) { - if (msg) { - SkDebugf("--- %s\n", msg); - } - return -1; -} - -static int64_t getNowUs() { - struct timeval tv; - gettimeofday(&tv, NULL); - - return tv.tv_usec + (int64_t) tv.tv_sec * 1000000; -} - -int testDecodeBounds(SkImageDecoder* decoder, SkStream* stream, - SkBitmap* bitmap) { - int64_t startTime = getNowUs(); - SkColorType prefColorType = kN32_SkColorType; - SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodeBounds_Mode; - - // Decode the input stream and then use the bitmap. - if (!decoder->decode(stream, bitmap, prefColorType, decodeMode)) { - return nullObjectReturn("decoder->decode returned false"); - } else { - int64_t delay = getNowUs() - startTime; - printf("WidthxHeight: %dx%d\n", bitmap->width(), bitmap->height()); - printf("Decoding Time in BoundsMode %.1f msec.\n", delay / 1000.0f); - return 0; - } -} - -int testDecodePixels(SkImageDecoder* decoder, SkStream* stream, - SkBitmap* bitmap) { - int64_t startTime = getNowUs(); - SkColorType prefColorType = kN32_SkColorType; - SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode; - - // Decode the input stream and then use the bitmap. - if (!decoder->decode(stream, bitmap, prefColorType, decodeMode)) { - return nullObjectReturn("decoder->decode returned false"); - } else { - int64_t delay = getNowUs() - startTime; - printf("Decoding Time in PixelsMode %.1f msec.\n", delay / 1000.0f); - const char* filename = "/sdcard/omxJpegDecodedBitmap.rgba"; - return storeBitmapToFile(bitmap, filename); - } -} - -int testDecoder(SkImageDecoder* decoder, char* filename) { - // test DecodeMode == Pixels - SkStream* stream = new SkFILEStream(filename); - SkBitmap* bitmap = new SkBitmap; - testDecodePixels(decoder, stream, bitmap); - delete bitmap; - - // test DecodeMode == Bounds - stream = new SkFILEStream(filename); - bitmap = new SkBitmap; - testDecodeBounds(decoder, stream, bitmap); - delete bitmap; - - delete decoder; - return 0; -} - -int main(int argc, char** argv) { - android::ProcessState::self()->startThreadPool(); - - printf("Decoding jpeg with libjpeg...\n"); - SkJPEGImageDecoder* libjpeg = new SkJPEGImageDecoder; - testDecoder(libjpeg, argv[1]); - - printf("\nDecoding jpeg with OMX...\n"); - OmxJpegImageDecoder* omx = new OmxJpegImageDecoder; - testDecoder(omx, argv[1]); - return 0; -} diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp deleted file mode 100644 index 229bfdb..0000000 --- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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. - */ - -#define LOG_TAG "OmxJpegDecoder" -#include <sys/time.h> -#include <utils/Log.h> - -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <binder/IServiceManager.h> -#include <binder/ProcessState.h> -#include <media/IMediaPlayerService.h> -#include <media/stagefright/foundation/ADebug.h> -#include <media/stagefright/MediaSource.h> -#include <media/stagefright/MetaData.h> -#include <media/stagefright/OMXClient.h> -#include <media/stagefright/OMXCodec.h> -#include <SkImage.h> -#include <SkMallocPixelRef.h> - -#include "omx_jpeg_decoder.h" -#include "StreamSource.h" - -using namespace android; - -static void getJpegOutput(MediaBuffer* buffer, const char* filename) { - int size = buffer->range_length(); - int offset = buffer->range_offset(); - FILE *pFile = fopen(filename, "w+"); - - if (pFile == NULL) { - printf("Error: cannot open %s.\n", filename); - } else { - char* data = (char*) buffer->data(); - data += offset; - while (size > 0) { - int numChars = fwrite(data, sizeof(char), 1024, pFile); - int numBytes = numChars * sizeof(char); - size -= numBytes; - data += numBytes; - } - fclose(pFile); - } - return; -} - -extern int storeBitmapToFile(SkBitmap* bitmap, const char* filename) { - bitmap->lockPixels(); - uint8_t* data = (uint8_t *)bitmap->getPixels(); - int size = bitmap->getSize(); - FILE* fp = fopen(filename, "w+"); - - if (NULL == fp) { - printf("Cannot open the output file! \n"); - return -1; - } else { - while (size > 0) { - int numChars = fwrite(data, sizeof(char), 1024, fp); - int numBytes = numChars * sizeof(char); - size -= numBytes; - data += numBytes; - } - fclose(fp); - } - return 0; -} - -static int64_t getNowUs() { - struct timeval tv; - gettimeofday(&tv, NULL); - - return (int64_t)tv.tv_usec + tv.tv_sec * 1000000; -} - -OmxJpegImageDecoder::OmxJpegImageDecoder() { - status_t err = mClient.connect(); - CHECK_EQ(err, (status_t)OK); -} - -OmxJpegImageDecoder::~OmxJpegImageDecoder() { - mClient.disconnect(); -} - -bool OmxJpegImageDecoder::onDecode(SkStream* stream, - SkBitmap* bm, Mode mode) { - sp<MediaSource> source = prepareMediaSource(stream); - sp<MetaData> meta = source->getFormat(); - int width; - int height; - meta->findInt32(kKeyWidth, &width); - meta->findInt32(kKeyHeight, &height); - configBitmapSize( - bm, getPrefColorType(k32Bit_SrcDepth, false), - width, height); - - // mode == DecodeBounds - if (mode == SkImageDecoder::kDecodeBounds_Mode) { - return true; - } - - // mode == DecodePixels - if (!this->allocPixelRef(bm, NULL)) { - ALOGI("Cannot allocPixelRef()!"); - return false; - } - - sp<MediaSource> decoder = getDecoder(&mClient, source); - return decodeSource(decoder, source, bm); -} - -JPEGSource* OmxJpegImageDecoder::prepareMediaSource(SkStream* stream) { - DataSource::RegisterDefaultSniffers(); - sp<DataSource> dataSource = new StreamSource(stream); - return new JPEGSource(dataSource); -} - -sp<MediaSource> OmxJpegImageDecoder::getDecoder( - OMXClient *client, const sp<MediaSource>& source) { - sp<MetaData> meta = source->getFormat(); - sp<MediaSource> decoder = OMXCodec::Create( - client->interface(), meta, false /* createEncoder */, source); - - CHECK(decoder != NULL); - return decoder; -} - -bool OmxJpegImageDecoder::decodeSource(sp<MediaSource> decoder, - const sp<MediaSource>& source, SkBitmap* bm) { - status_t rt = decoder->start(); - if (rt != OK) { - ALOGE("Cannot start OMX Decoder!"); - return false; - } - int64_t startTime = getNowUs(); - MediaBuffer *buffer; - - // decode source - status_t err = decoder->read(&buffer, NULL); - int64_t duration = getNowUs() - startTime; - - if (err != OK) { - CHECK(buffer == NULL); - } - printf("Duration in decoder->read(): %.1f (msecs). \n", - duration / 1E3 ); - - // Copy pixels from buffer to bm. - // May need to check buffer->rawBytes() == bm->rawBytes(). - CHECK_EQ(buffer->size(), bm->getSize()); - memcpy(bm->getPixels(), buffer->data(), buffer->size()); - buffer->release(); - decoder->stop(); - - return true; -} - -void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkColorType pref, - int width, int height) { - // Set the color space to ARGB_8888 for now (ignoring pref) - // because of limitation in hardware support. - bm->setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType)); -} diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h deleted file mode 100644 index e487245..0000000 --- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - */ - -#ifndef OMXJPEGIMAGEDECODER -#define OMXJPEGIMAGEDECODER - -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <media/stagefright/JPEGSource.h> -#include <media/stagefright/MediaSource.h> -#include <media/stagefright/OMXClient.h> -#include <media/stagefright/OMXCodec.h> -#include <SkImageDecoder.h> -#include <SkStream.h> - -using namespace android; - -extern int storeBitmapToFile(SkBitmap* bitmap, const char* filename); - -class OmxJpegImageDecoder : public SkImageDecoder { -public: - OmxJpegImageDecoder(); - ~OmxJpegImageDecoder(); - - virtual Format getFormat() const { - return kJPEG_Format; - } - -protected: - virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode); - -private: - JPEGSource* prepareMediaSource(SkStream* stream); - sp<MediaSource> getDecoder(OMXClient* client, const sp<MediaSource>& source); - bool decodeSource(sp<MediaSource> decoder, const sp<MediaSource>& source, - SkBitmap* bm); - void configBitmapSize(SkBitmap* bm, SkColorType, int width, int height); - - OMXClient mClient; -}; - -#endif diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index efba03d..16c6075 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -21,6 +21,7 @@ <integer name="def_screen_off_timeout">60000</integer> <integer name="def_sleep_timeout">-1</integer> <bool name="def_airplane_mode_on">false</bool> + <bool name="def_theater_mode_on">false</bool> <!-- Comma-separated list of bluetooth, wifi, and cell. --> <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string> <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index f88ea8b..e56806a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -71,7 +71,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 114; + private static final int DATABASE_VERSION = 116; private Context mContext; private int mUserHandle; @@ -1828,22 +1828,43 @@ public class DatabaseHelper extends SQLiteOpenHelper { upgradeVersion = 113; } - if (upgradeVersion < 114) { + // We skipped 114 to handle a merge conflict with the introduction of theater mode. + + if (upgradeVersion < 115) { if (mUserHandle == UserHandle.USER_OWNER) { db.beginTransaction(); SQLiteStatement stmt = null; try { stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)" + " VALUES(?,?);"); - loadSetting(stmt, Settings.Global.VOLTE_VT_ENABLED, ImsConfig.FeatureValueConstants.ON); + loadBooleanSetting(stmt, Global.THEATER_MODE_ON, + R.bool.def_theater_mode_on); db.setTransactionSuccessful(); } finally { db.endTransaction(); if (stmt != null) stmt.close(); } } - upgradeVersion = 114; + upgradeVersion = 115; } + + if (upgradeVersion < 116) { + if (mUserHandle == UserHandle.USER_OWNER) { + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)" + + " VALUES(?,?);"); + loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) stmt.close(); + } + } + upgradeVersion = 116; + } + // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -2440,6 +2461,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Global.AIRPLANE_MODE_ON, R.bool.def_airplane_mode_on); + loadBooleanSetting(stmt, Settings.Global.THEATER_MODE_ON, + R.bool.def_theater_mode_on); + loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_RADIOS, R.string.def_airplane_mode_radios); @@ -2586,7 +2610,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED, R.bool.def_guest_user_enabled); - loadSetting(stmt, Settings.Global.VOLTE_VT_ENABLED, ImsConfig.FeatureValueConstants.ON); + loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON); // --- New global settings start here } finally { if (stmt != null) stmt.close(); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7e6afa6..3c44245 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -92,6 +92,7 @@ <uses-permission android:name="android.permission.FRAME_STATS" /> <uses-permission android:name="android.permission.BIND_APPWIDGET" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> + <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/> <application android:label="@string/app_label"> <provider diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index eb808c2..5c7909a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -571,6 +571,9 @@ public class UserSwitcherController { cancel(); } else { dismiss(); + if (ActivityManager.isUserAMonkey()) { + return; + } UserInfo user = mUserManager.createSecondaryUser( mContext.getString(R.string.user_new_user_name), 0 /* flags */); if (user == null) { diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 9c81f0a..1ed61fd 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -3342,9 +3342,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE); if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) { - addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); + setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE); } else { - clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); + setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE); } // Non-floating windows on high end devices must put up decor beneath the system bars and diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 6b9d95d..e184577 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -503,6 +503,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { // What we do when the user double-taps on home private int mDoubleTapOnHomeBehavior; + // Allowed theater mode wake actions + private boolean mAllowTheaterModeWakeFromKey; + private boolean mAllowTheaterModeWakeFromPowerKey; + private boolean mAllowTheaterModeWakeFromMotion; + private boolean mAllowTheaterModeWakeFromCameraLens; + private boolean mAllowTheaterModeWakeFromLidSwitch; + private boolean mAllowTheaterModeWakeFromWakeGesture; + // Screenshot trigger states // Time to volume and power must be pressed within this interval of each other. private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150; @@ -656,7 +664,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { synchronized (mLock) { if (shouldEnableWakeGestureLp()) { performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture); } } } @@ -1022,6 +1030,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.bool.config_lidControlsSleep); mTranslucentDecorEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableTranslucentDecor); + + mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromKey); + mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey + || mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey); + mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion); + mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens); + mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch); + mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture); + readConfigurationDependentBehaviors(); mAccessibilityManager = (AccessibilityManager) context.getSystemService( @@ -3181,10 +3204,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // whether it is taking care of insetting its content. If not, // we need to use the parent's content frame so that the entire // window is positioned within that content. Otherwise we can use - // the display frame and let the attached window take care of + // the overscan frame and let the attached window take care of // positioning its content appropriately. if (adjust != SOFT_INPUT_ADJUST_RESIZE) { - cf.set(attached.getOverscanFrameLw()); + // Set the content frame of the attached window to the parent's decor frame + // (same as content frame when IME isn't present) if specifically requested by + // setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag. + // Otherwise, use the overscan frame. + cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0 + ? attached.getContentFrameLw() : attached.getOverscanFrameLw()); } else { // If the window is resizing, then we want to base the content // frame on our attached content frame to resize... however, @@ -4060,7 +4088,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateRotation(true); if (lidOpen) { - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch); } else if (!mLidControlsSleep) { mPowerManager.userActivity(SystemClock.uptimeMillis(), false); } @@ -4082,7 +4110,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); } - mPowerManager.wakeUp(whenNanos / 1000000); + wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens); mContext.startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); } mCameraLensCoverState = lensCoverState; @@ -4260,7 +4288,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // key processing. if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { if (isWakeKey) { - mPowerManager.wakeUp(event.getEventTime()); + wakeUp(event.getEventTime(), keyCode == KeyEvent.KEYCODE_POWER + ? mAllowTheaterModeWakeFromPowerKey : mAllowTheaterModeWakeFromKey); } return result; } @@ -4509,8 +4538,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (isWakeKey) { - mPowerManager.wakeUp(event.getEventTime()); + wakeUp(event.getEventTime(), keyCode == KeyEvent.KEYCODE_POWER + ? mAllowTheaterModeWakeFromPowerKey : mAllowTheaterModeWakeFromKey); } + return result; } @@ -4553,7 +4584,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { if ((policyFlags & FLAG_WAKE) != 0) { - mPowerManager.wakeUp(whenNanos / 1000000); + wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion); return 0; } if (shouldDispatchInputWhenNonInteractive()) { @@ -4731,6 +4762,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void wakeUp(long wakeTime, boolean wakeInTheaterMode) { + if (!wakeInTheaterMode && isTheaterModeEnabled()) { + return; + } + + mPowerManager.wakeUp(wakeTime); + } + // Called on the PowerManager's Notifier thread. @Override public void wakingUp() { @@ -5609,6 +5648,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { ringTone.play(); } + private boolean isTheaterModeEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 1; + } + private boolean isGlobalAccessibilityGestureEnabled() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1; diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index d05c280..41ce25d 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -65,11 +65,15 @@ final class DockObserver extends SystemService { private boolean mUpdatesStopped; + private final boolean mAllowTheaterModeWakeFromDock; + public DockObserver(Context context) { super(context); mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); init(); // set initial status @@ -126,8 +130,12 @@ final class DockObserver extends SystemService { if (newState != mReportedDockState) { mReportedDockState = newState; if (mSystemReady) { - // Wake up immediately when docked or undocked. - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + // Wake up immediately when docked or undocked except in theater mode. + if (mAllowTheaterModeWakeFromDock + || Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 0) { + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } updateLocked(); } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index c6d2db2..83d6986 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -137,11 +137,15 @@ public class JobSchedulerService extends com.android.server.SystemService public void onReceive(Context context, Intent intent) { Slog.d(TAG, "Receieved: " + intent.getAction()); if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); - if (DEBUG) { - Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); + // If this is an outright uninstall rather than the first half of an + // app update sequence, cancel the jobs associated with the app. + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (DEBUG) { + Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); + } + cancelJobsForUid(uidRemoved); } - cancelJobsForUid(uidRemoved); } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); if (DEBUG) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 52807c0..5e95dfe 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -281,6 +281,9 @@ public final class PowerManagerService extends SystemService // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + // True if the device should wake up when plugged or unplugged in theater mode. + private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig; + // True if the device should suspend when the screen is off due to proximity. private boolean mSuspendWhenScreenOffDueToProximityConfig; @@ -420,6 +423,9 @@ public final class PowerManagerService extends SystemService // True if the battery level is currently considered low. private boolean mBatteryLevelLow; + // True if theater mode is enabled + private boolean mTheaterModeEnabled; + private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(); @@ -568,6 +574,9 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.THEATER_MODE_ON), + false, mSettingsObserver, UserHandle.USER_ALL); // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -585,6 +594,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay); mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( com.android.internal.R.bool.config_unplugTurnsOnScreen); + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug); mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity); mDreamsSupportedConfig = resources.getBoolean( @@ -636,6 +647,8 @@ public final class PowerManagerService extends SystemService UserHandle.USER_CURRENT); mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC); + mTheaterModeEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 1; final int oldScreenBrightnessSetting = mScreenBrightnessSetting; mScreenBrightnessSetting = Settings.System.getIntForUser(resolver, @@ -1334,6 +1347,11 @@ public final class PowerManagerService extends SystemService return false; } + // Don't wake while theater mode is enabled. + if (mTheaterModeEnabled && !mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig) { + return false; + } + // Otherwise wake up! return true; } @@ -2360,6 +2378,10 @@ public final class PowerManagerService extends SystemService + mDecoupleHalInteractiveModeFromDisplayConfig); pw.println(" mWakeUpWhenPluggedOrUnpluggedConfig=" + mWakeUpWhenPluggedOrUnpluggedConfig); + pw.println(" mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=" + + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); + pw.println(" mTheaterModeEnabled=" + + mTheaterModeEnabled); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index f85e2d9..f19bfc2 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.Slog; +import android.view.WindowManager; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; @@ -295,9 +296,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } - /** + /** * Hide or show the on-screen Menu key. Only call this from the window manager, typically in - * response to a window with FLAG_NEEDS_MENU_KEY set. + * response to a window with {@link android.view.WindowManager.LayoutParams#needsMenuKey} set + * to {@link android.view.WindowManager.LayoutParams#NEEDS_MENU_SET_TRUE}. */ @Override public void topAppWindowChanged(final boolean menuVisible) { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index fefbe0a..f9b1704 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -120,8 +120,9 @@ public class TrustManagerService extends SystemService { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && !isSafeMode()) { mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); - maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_OWNER); refreshAgentList(UserHandle.USER_ALL); + } else if (phase == SystemService.PHASE_BOOT_COMPLETED && !isSafeMode()) { + maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_OWNER); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 30589b1..b0feca8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -376,10 +376,9 @@ class DisplayContent { stack.dump(prefix + " ", pw); } pw.println(); - pw.println(" Application tokens in bottom up Z order:"); + pw.println(" Application tokens in top down Z order:"); int ndx = 0; - final int numStacks = mStacks.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks(); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9ceac41..a60be3b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -60,6 +60,12 @@ class Task { return removed; } + void setSendingToBottom(boolean toBottom) { + for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) { + mAppTokens.get(appTokenNdx).sendingToBottom = toBottom; + } + } + @Override public String toString() { return "{taskId=" + taskId + " appTokens=" + mAppTokens + "}"; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0a0e8a1..57d793f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -619,6 +619,9 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; + // Whether or not a layout can cause a wake up when theater mode is enabled. + boolean mAllowTheaterModeWakeFromLayout; + DragState mDragState = null; // For frozen screen animations. @@ -881,6 +884,9 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator = new WindowAnimator(this); + mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); + LocalServices.addService(WindowManagerInternal.class, new LocalService()); initPolicy(); @@ -5052,6 +5058,10 @@ public class WindowManagerService extends IWindowManager.Stub } } stack.moveTaskToTop(task); + if (mAppTransition.isTransitionSet()) { + task.setSendingToBottom(false); + } + moveStackWindowsLocked(displayContent); } } finally { Binder.restoreCallingIdentity(origId); @@ -5070,6 +5080,9 @@ public class WindowManagerService extends IWindowManager.Stub } final TaskStack stack = task.mStack; stack.moveTaskToBottom(task); + if (mAppTransition.isTransitionSet()) { + task.setSendingToBottom(true); + } moveStackWindowsLocked(stack.getDisplayContent()); } } finally { @@ -9953,8 +9966,12 @@ public class WindowManagerService extends IWindowManager.Stub } if (mTurnOnScreen) { - if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + if (mAllowTheaterModeWakeFromLayout + || Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 0) { + if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } mTurnOnScreen = false; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b4a7f04..806f7c5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -704,9 +704,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { WindowState ws = this; WindowList windows = getWindowList(); while (true) { - if ((ws.mAttrs.privateFlags - & WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY) != 0) { - return (ws.mAttrs.flags & WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0; + if (ws.mAttrs.needsMenuKey != WindowManager.LayoutParams.NEEDS_MENU_UNSET) { + return ws.mAttrs.needsMenuKey == WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE; } // If we reached the bottom of the range of windows we are considering, // assume no menu is needed. diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index f934963..354fa2e 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -484,10 +484,10 @@ public final class Call { /** * Notifies this {@code Call} that an account has been selected and to proceed with placing - * an outgoing call. + * an outgoing call. Optionally sets this account as the default account. */ - public void phoneAccountSelected(PhoneAccountHandle accountHandle) { - mInCallAdapter.phoneAccountSelected(mTelecomCallId, accountHandle); + public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) { + mInCallAdapter.phoneAccountSelected(mTelecomCallId, accountHandle, setDefault); } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 795053d..2932721 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -898,6 +898,13 @@ public abstract class Connection { } /** + * @hide + */ + public final ConnectionService getConnectionService() { + return mConnectionService; + } + + /** * Sets the conference that this connection is a part of. This will fail if the connection is * already part of a conference call. {@link #resetConference} to un-set the conference first. * diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java index fd3cf2e..62b8dea 100644 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -189,14 +189,16 @@ public final class InCallAdapter { } /** - * Instructs Telecom to add a PhoneAccountHandle to the specified call + * Instructs Telecom to add a PhoneAccountHandle to the specified call. * - * @param callId The identifier of the call - * @param accountHandle The PhoneAccountHandle through which to place the call + * @param callId The identifier of the call. + * @param accountHandle The PhoneAccountHandle through which to place the call. + * @param setDefault {@code True} if this account should be set as the default for calls. */ - public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle) { + public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle, + boolean setDefault) { try { - mAdapter.phoneAccountSelected(callId, accountHandle); + mAdapter.phoneAccountSelected(callId, accountHandle, setDefault); } catch (RemoteException e) { } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 66b52ae..402df30 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -105,11 +105,17 @@ public class PhoneAccount implements Parcelable { */ public static final String SCHEME_SIP = "sip"; + /** + * Indicating no color is set. + */ + public static final int NO_COLOR = -1; + private final PhoneAccountHandle mAccountHandle; private final Uri mAddress; private final Uri mSubscriptionAddress; private final int mCapabilities; private final int mIconResId; + private final int mColor; private final CharSequence mLabel; private final CharSequence mShortDescription; private final List<String> mSupportedUriSchemes; @@ -120,6 +126,7 @@ public class PhoneAccount implements Parcelable { private Uri mSubscriptionAddress; private int mCapabilities; private int mIconResId; + private int mColor = NO_COLOR; private CharSequence mLabel; private CharSequence mShortDescription; private List<String> mSupportedUriSchemes = new ArrayList<String>(); @@ -141,6 +148,7 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress = phoneAccount.getSubscriptionAddress(); mCapabilities = phoneAccount.getCapabilities(); mIconResId = phoneAccount.getIconResId(); + mColor = phoneAccount.getColor(); mLabel = phoneAccount.getLabel(); mShortDescription = phoneAccount.getShortDescription(); mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes()); @@ -166,6 +174,11 @@ public class PhoneAccount implements Parcelable { return this; } + public Builder setColor(int value) { + this.mColor = value; + return this; + } + public Builder setShortDescription(CharSequence value) { this.mShortDescription = value; return this; @@ -219,6 +232,7 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress, mCapabilities, mIconResId, + mColor, mLabel, mShortDescription, mSupportedUriSchemes); @@ -231,6 +245,7 @@ public class PhoneAccount implements Parcelable { Uri subscriptionAddress, int capabilities, int iconResId, + int color, CharSequence label, CharSequence shortDescription, List<String> supportedUriSchemes) { @@ -239,6 +254,7 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress = subscriptionAddress; mCapabilities = capabilities; mIconResId = iconResId; + mColor = color; mLabel = label; mShortDescription = shortDescription; mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); @@ -371,6 +387,15 @@ public class PhoneAccount implements Parcelable { } /** + * A highlight color to use in displaying information about this {@code PhoneAccount}. + * + * @return A hexadecimal color value. + */ + public int getColor() { + return mColor; + } + + /** * An icon to represent this {@code PhoneAccount} in a user interface. * * @return An icon for this {@code PhoneAccount}. @@ -418,6 +443,7 @@ public class PhoneAccount implements Parcelable { out.writeParcelable(mSubscriptionAddress, 0); out.writeInt(mCapabilities); out.writeInt(mIconResId); + out.writeInt(mColor); out.writeCharSequence(mLabel); out.writeCharSequence(mShortDescription); out.writeList(mSupportedUriSchemes); @@ -444,6 +470,7 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress = in.readParcelable(getClass().getClassLoader()); mCapabilities = in.readInt(); mIconResId = in.readInt(); + mColor = in.readInt(); mLabel = in.readCharSequence(); mShortDescription = in.readCharSequence(); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e883e4e..2652b45 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -586,7 +586,6 @@ public class TelecomManager { * * @param account The complete {@link PhoneAccount}. */ - @SystemApi public void registerPhoneAccount(PhoneAccount account) { try { if (isServiceConnected()) { @@ -602,7 +601,6 @@ public class TelecomManager { * * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister. */ - @SystemApi public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { @@ -616,7 +614,6 @@ public class TelecomManager { /** * Remove all Accounts that belong to the calling package from the system. */ - @SystemApi public void clearAccounts() { try { if (isServiceConnected()) { @@ -670,7 +667,6 @@ public class TelecomManager { * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE} * </p> */ - @SystemApi public boolean isInCall() { try { if (isServiceConnected()) { @@ -822,7 +818,6 @@ public class TelecomManager { * @param extras A bundle that will be passed through to * {@link ConnectionService#onCreateIncomingConnection}. */ - @SystemApi public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { try { if (isServiceConnected()) { diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl index 138a877..863fff2 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -46,7 +46,8 @@ oneway interface IInCallAdapter { void postDialContinue(String callId, boolean proceed); - void phoneAccountSelected(String callId, in PhoneAccountHandle accountHandle); + void phoneAccountSelected(String callId, in PhoneAccountHandle accountHandle, + boolean setDefault); void conference(String callId, String otherCallId); diff --git a/telephony/java/android/telephony/SubInfoRecord.java b/telephony/java/android/telephony/SubInfoRecord.java index b9de871..f2df079 100644 --- a/telephony/java/android/telephony/SubInfoRecord.java +++ b/telephony/java/android/telephony/SubInfoRecord.java @@ -16,6 +16,7 @@ package android.telephony; +import android.graphics.drawable.BitmapDrawable; import android.os.Parcel; import android.os.Parcelable; @@ -107,6 +108,31 @@ public class SubInfoRecord implements Parcelable { this.mnc = mnc; } + /** + * Returns the string displayed to the user that identifies this subscription + */ + public String getLabel() { + return this.displayName; + } + + /** + * Return the icon used to identify this SIM. + * TODO: return the correct drawable. + */ + public BitmapDrawable getIconDrawable() { + return new BitmapDrawable(); + } + + /** + * Return the color to be used for when displaying to the user. This is the value of the color. + * ex: 0x00ff00 + */ + public int getColor() { + // Note: This color is currently an index into a list of drawables, but this is soon to + // change. + return this.color; + } + public static final Parcelable.Creator<SubInfoRecord> CREATOR = new Parcelable.Creator<SubInfoRecord>() { @Override public SubInfoRecord createFromParcel(Parcel source) { diff --git a/telephony/java/com/android/internal/telephony/DcParamObject.java b/telephony/java/com/android/internal/telephony/DcParamObject.java index 2736e6f..c92988f 100644 --- a/telephony/java/com/android/internal/telephony/DcParamObject.java +++ b/telephony/java/com/android/internal/telephony/DcParamObject.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 MediaTek Inc. + * 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. diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index defb43b..a4e9486 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -99,6 +99,7 @@ public class DctConstants { public static final int EVENT_PROVISIONING_APN_ALARM = BASE + 39; public static final int CMD_NET_STAT_POLL = BASE + 40; public static final int EVENT_DATA_RAT_CHANGED = BASE + 41; + public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42; /***** Constants *****/ diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 117fc24..b7f64f6 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1141,9 +1141,10 @@ bail: ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths) + sp<FilePathStore>& fullResPaths, + const bool overwrite) { - ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths); + ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite); if (res > 0) { mGroupEntries.add(kind); } diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index d809c5b..7ae5368 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -591,7 +591,8 @@ private: const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths); + sp<FilePathStore>& fullResPaths, + const bool overwrite=false); ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); ssize_t slurpResourceZip(Bundle* bundle, const char* filename); diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h index 779c423..4f999a2 100644 --- a/tools/aapt/ConfigDescription.h +++ b/tools/aapt/ConfigDescription.h @@ -28,10 +28,12 @@ struct ConfigDescription : public android::ResTable_config { memset(this, 0, sizeof(*this)); size = sizeof(android::ResTable_config); } + ConfigDescription(const android::ResTable_config&o) { *static_cast<android::ResTable_config*>(this) = o; size = sizeof(android::ResTable_config); } + ConfigDescription(const ConfigDescription&o) { *static_cast<android::ResTable_config*>(this) = o; } @@ -41,6 +43,7 @@ struct ConfigDescription : public android::ResTable_config { size = sizeof(android::ResTable_config); return *this; } + ConfigDescription& operator=(const ConfigDescription& o) { *static_cast<android::ResTable_config*>(this) = o; return *this; diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp index d60a07f..8835fb0 100644 --- a/tools/aapt/ResourceIdCache.cpp +++ b/tools/aapt/ResourceIdCache.cpp @@ -9,8 +9,6 @@ #include <utils/Log.h> #include "ResourceIdCache.h" #include <map> -using namespace std; - static size_t mHits = 0; static size_t mMisses = 0; @@ -29,7 +27,7 @@ struct CacheEntry { CacheEntry(const android::String16& name, uint32_t resId) : hashedName(name), id(resId) { } }; -static map< uint32_t, CacheEntry > mIdMap; +static std::map< uint32_t, CacheEntry > mIdMap; // djb2; reasonable choice for strings when collisions aren't particularly important @@ -63,7 +61,7 @@ uint32_t ResourceIdCache::lookup(const android::String16& package, bool onlyPublic) { const String16 hashedName = makeHashableName(package, type, name, onlyPublic); const uint32_t hashcode = hash(hashedName); - map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode); + std::map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode); if (item == mIdMap.end()) { // cache miss mMisses++; diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index bef5181..4993262 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -213,6 +213,10 @@ public class FontFamily_Delegate { return null; } + @Nullable + /*package*/ static String getFontLocation() { + return sFontLocation; + } // ---- native methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 276e134..b9460b4 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -27,6 +27,8 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import static android.graphics.FontFamily_Delegate.getFontLocation; + /** * Delegate implementing the native methods of android.graphics.Typeface * @@ -48,8 +50,6 @@ public final class Typeface_Delegate { private static final DelegateManager<Typeface_Delegate> sManager = new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); - // ---- delegate helper data ---- - private static String sFontLocation; // ---- delegate data ---- @@ -61,11 +61,8 @@ public final class Typeface_Delegate { private static long sDefaultTypeface; + // ---- Public Helper methods ---- - public static synchronized void setFontLocation(String fontLocation) { - sFontLocation = fontLocation; - FontFamily_Delegate.setFontLocation(fontLocation); - } public static Typeface_Delegate getDelegate(long nativeTypeface) { return sManager.getDelegate(nativeTypeface); @@ -131,6 +128,18 @@ public final class Typeface_Delegate { return fonts; } + /** + * Clear the default typefaces when disposing bridge. + */ + public static void resetDefaults() { + // Sometimes this is called before the Bridge is initialized. In that case, we don't want to + // initialize Typeface because the SDK fonts location hasn't been set. + if (FontFamily_Delegate.getFontLocation() != null) { + Typeface.sDefaults = null; + } + } + + // ---- native methods ---- @LayoutlibDelegate @@ -193,7 +202,7 @@ public final class Typeface_Delegate { @LayoutlibDelegate /*package*/ static File getSystemFontConfigLocation() { - return new File(sFontLocation); + return new File(getFontLocation()); } // ---- Private delegate/helper methods ---- diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 3d0e1e8..825731b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -38,7 +38,7 @@ import com.ibm.icu.util.ULocale; import android.content.res.BridgeAssetManager; import android.graphics.Bitmap; -import android.graphics.Typeface_Accessor; +import android.graphics.FontFamily_Delegate; import android.graphics.Typeface_Delegate; import android.os.Looper; import android.os.Looper_Accessor; @@ -250,7 +250,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } // load the fonts. - Typeface_Delegate.setFontLocation(fontLocation.getAbsolutePath()); + FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath()); // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. @@ -303,7 +303,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { BridgeAssetManager.clearSystem(); // dispose of the default typeface. - Typeface_Accessor.resetDefaults(); + Typeface_Delegate.resetDefaults(); return true; } diff --git a/tools/split-select/Abi.cpp b/tools/split-select/Abi.cpp new file mode 100644 index 0000000..20654b6 --- /dev/null +++ b/tools/split-select/Abi.cpp @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#include "Abi.h" + +namespace split { +namespace abi { + +static const std::vector<Variant> sNoneVariants = {}; +static const std::vector<Variant> sArmVariants = + {Variant::armeabi, Variant::armeabi_v7a, Variant::arm64_v8a}; +static const std::vector<Variant> sIntelVariants = {Variant::x86, Variant::x86_64}; +static const std::vector<Variant> sMipsVariants = {Variant::mips, Variant::mips64}; + +Family getFamily(Variant variant) { + switch (variant) { + case Variant::none: + return Family::none; + case Variant::armeabi: + case Variant::armeabi_v7a: + case Variant::arm64_v8a: + return Family::arm; + case Variant::x86: + case Variant::x86_64: + return Family::intel; + case Variant::mips: + case Variant::mips64: + return Family::mips; + } + return Family::none; +} + +const std::vector<Variant>& getVariants(Family family) { + switch (family) { + case Family::none: + return sNoneVariants; + case Family::arm: + return sArmVariants; + case Family::intel: + return sIntelVariants; + case Family::mips: + return sMipsVariants; + } + return sNoneVariants; +} + +const char* toString(Variant variant) { + switch (variant) { + case Variant::none: + return ""; + case Variant::armeabi: + return "armeabi"; + case Variant::armeabi_v7a: + return "armeabi-v7a"; + case Variant::arm64_v8a: + return "arm64-v8a"; + case Variant::x86: + return "x86"; + case Variant::x86_64: + return "x86_64"; + case Variant::mips: + return "mips"; + case Variant::mips64: + return "mips64"; + } + return ""; +} + +} // namespace abi +} // namespace split diff --git a/tools/split-select/Abi.h b/tools/split-select/Abi.h new file mode 100644 index 0000000..3e00eba --- /dev/null +++ b/tools/split-select/Abi.h @@ -0,0 +1,50 @@ +/* + * 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 H_ANDROID_SPLIT_ABI +#define H_ANDROID_SPLIT_ABI + +#include <vector> + +namespace split { +namespace abi { + +enum class Variant { + none = 0, + armeabi, + armeabi_v7a, + arm64_v8a, + x86, + x86_64, + mips, + mips64, +}; + +enum class Family { + none, + arm, + intel, + mips, +}; + +Family getFamily(Variant variant); +const std::vector<Variant>& getVariants(Family family); +const char* toString(Variant variant); + +} // namespace abi +} // namespace split + +#endif // H_ANDROID_SPLIT_ABI diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk new file mode 100644 index 0000000..d0b7287 --- /dev/null +++ b/tools/split-select/Android.mk @@ -0,0 +1,119 @@ +# +# 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. +# + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) + +# TODO(adamlesinski): Enable OS X builds when I figure out how +# to build with clang and libc++ +ifneq ($(HOST_OS),darwin) + +# ========================================================== +# Setup some common variables for the different build +# targets here. +# ========================================================== +LOCAL_PATH:= $(call my-dir) + +main := Main.cpp +sources := \ + Abi.cpp \ + Grouper.cpp \ + Rule.cpp \ + RuleGenerator.cpp \ + SplitDescription.cpp + +testSources := \ + Grouper_test.cpp \ + Rule_test.cpp \ + RuleGenerator_test.cpp + +cIncludes := \ + external/zlib \ + frameworks/base/tools + +hostLdLibs := +hostStaticLibs := \ + libaapt \ + libandroidfw \ + libpng \ + liblog \ + libutils \ + libcutils \ + libexpat \ + libziparchive-host + +cFlags := -std=c++11 -Wall -Werror + +ifeq ($(HOST_OS),linux) + hostLdLibs += -lrt -ldl -lpthread +endif + +# Statically link libz for MinGW (Win SDK under Linux), +# and dynamically link for all others. +ifneq ($(strip $(USE_MINGW)),) + hostStaticLibs += libz +else + hostLdLibs += -lz +endif + + +# ========================================================== +# Build the host static library: libsplit-select +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libsplit-select + +LOCAL_SRC_FILES := $(sources) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_CFLAGS += $(cFlags) -D_DARWIN_UNLIMITED_STREAMS + +include $(BUILD_HOST_STATIC_LIBRARY) + + +# ========================================================== +# Build the host tests: libsplit-select_tests +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libsplit-select_tests +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(testSources) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) + +include $(BUILD_HOST_NATIVE_TEST) + +# ========================================================== +# Build the host executable: split-select +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := split-select + +LOCAL_SRC_FILES := $(main) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) + +include $(BUILD_HOST_EXECUTABLE) + +endif # Not OS X +endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/split-select/Grouper.cpp b/tools/split-select/Grouper.cpp new file mode 100644 index 0000000..15edf89 --- /dev/null +++ b/tools/split-select/Grouper.cpp @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#include "Grouper.h" + +#include "SplitDescription.h" + +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +template <typename Key, typename Value> +static void addToVector(KeyedVector<Key, SortedVector<Value> >& group, + const Key& key, const Value& value) { + ssize_t idx = group.indexOfKey(key); + if (idx < 0) { + idx = group.add(key, SortedVector<Value>()); + } + group.editValueAt(idx).add(value); +} + +Vector<SortedVector<SplitDescription> > +groupByMutualExclusivity(const Vector<SplitDescription>& splits) { + Vector<SortedVector<SplitDescription> > groups; + + // Find mutually exclusive splits and group them. + KeyedVector<SplitDescription, SortedVector<SplitDescription> > densityGroups; + KeyedVector<SplitDescription, SortedVector<SplitDescription> > abiGroups; + KeyedVector<SplitDescription, SortedVector<SplitDescription> > localeGroups; + for (const SplitDescription& split : splits) { + if (split.config.density != 0) { + SplitDescription key(split); + key.config.density = 0; + key.config.sdkVersion = 0; // Ignore density so we can support anydpi. + addToVector(densityGroups, key, split); + } else if (split.abi != abi::Variant::none) { + SplitDescription key(split); + key.abi = abi::Variant::none; + addToVector(abiGroups, key, split); + } else if (split.config.locale != 0) { + SplitDescription key(split); + key.config.clearLocale(); + addToVector(localeGroups, key, split); + } else { + groups.add(); + groups.editTop().add(split); + } + } + + const size_t densityCount = densityGroups.size(); + for (size_t i = 0; i < densityCount; i++) { + groups.add(densityGroups[i]); + } + + const size_t abiCount = abiGroups.size(); + for (size_t i = 0; i < abiCount; i++) { + groups.add(abiGroups[i]); + } + + const size_t localeCount = localeGroups.size(); + for (size_t i = 0; i < localeCount; i++) { + groups.add(localeGroups[i]); + } + return groups; +} + +} // namespace split diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java b/tools/split-select/Grouper.h index adad2ac..5cb0b5b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java +++ b/tools/split-select/Grouper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 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,14 +14,19 @@ * limitations under the License. */ -package android.graphics; +#ifndef H_ANDROID_SPLIT_GROUPER +#define H_ANDROID_SPLIT_GROUPER -/** - * Class allowing access to package-protected methods/fields. - */ -public class Typeface_Accessor { +#include "SplitDescription.h" + +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +namespace split { + +android::Vector<android::SortedVector<SplitDescription> > +groupByMutualExclusivity(const android::Vector<SplitDescription>& splits); + +} // namespace split - public static void resetDefaults() { - Typeface.sDefaults = null; - } -} +#endif // H_ANDROID_SPLIT_GROUPER diff --git a/tools/split-select/Grouper_test.cpp b/tools/split-select/Grouper_test.cpp new file mode 100644 index 0000000..4d146cd --- /dev/null +++ b/tools/split-select/Grouper_test.cpp @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#include "Grouper.h" + +#include "SplitDescription.h" + +#include <gtest/gtest.h> +#include <initializer_list> +#include <utils/String8.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +class GrouperTest : public ::testing::Test { +protected: + virtual void SetUp() { + Vector<SplitDescription> splits; + addSplit(splits, "en-rUS-sw600dp-hdpi"); + addSplit(splits, "fr-rFR-sw600dp-hdpi"); + addSplit(splits, "fr-rFR-sw600dp-xhdpi"); + addSplit(splits, ":armeabi"); + addSplit(splits, "en-rUS-sw300dp-xhdpi"); + addSplit(splits, "large"); + addSplit(splits, "pl-rPL"); + addSplit(splits, "xlarge"); + addSplit(splits, "en-rUS-sw600dp-xhdpi"); + addSplit(splits, "en-rUS-sw300dp-hdpi"); + addSplit(splits, "xxhdpi"); + addSplit(splits, "hdpi"); + addSplit(splits, "de-rDE"); + addSplit(splits, "xhdpi"); + addSplit(splits, ":x86"); + addSplit(splits, "anydpi"); + addSplit(splits, "v7"); + addSplit(splits, "v8"); + addSplit(splits, "sw600dp"); + addSplit(splits, "sw300dp"); + mGroups = groupByMutualExclusivity(splits); + } + + void addSplit(Vector<SplitDescription>& splits, const char* str); + void expectHasGroupWithSplits(std::initializer_list<const char*> l); + + Vector<SortedVector<SplitDescription> > mGroups; +}; + +TEST_F(GrouperTest, shouldHaveCorrectNumberOfGroups) { + EXPECT_EQ(12u, mGroups.size()); +} + +TEST_F(GrouperTest, shouldGroupDensities) { + expectHasGroupWithSplits({"en-rUS-sw300dp-hdpi", "en-rUS-sw300dp-xhdpi"}); + expectHasGroupWithSplits({"en-rUS-sw600dp-hdpi", "en-rUS-sw600dp-xhdpi"}); + expectHasGroupWithSplits({"fr-rFR-sw600dp-hdpi", "fr-rFR-sw600dp-xhdpi"}); + expectHasGroupWithSplits({"hdpi", "xhdpi", "xxhdpi", "anydpi"}); +} + +TEST_F(GrouperTest, shouldGroupAbi) { + expectHasGroupWithSplits({":armeabi", ":x86"}); +} + +TEST_F(GrouperTest, shouldGroupLocale) { + expectHasGroupWithSplits({"pl-rPL", "de-rDE"}); +} + +TEST_F(GrouperTest, shouldGroupEachSplitIntoItsOwnGroup) { + expectHasGroupWithSplits({"large"}); + expectHasGroupWithSplits({"xlarge"}); + expectHasGroupWithSplits({"v7"}); + expectHasGroupWithSplits({"v8"}); + expectHasGroupWithSplits({"sw600dp"}); + expectHasGroupWithSplits({"sw300dp"}); +} + +// +// Helper methods +// + +void GrouperTest::expectHasGroupWithSplits(std::initializer_list<const char*> l) { + Vector<SplitDescription> splits; + for (const char* str : l) { + splits.add(); + if (!SplitDescription::parse(String8(str), &splits.editTop())) { + ADD_FAILURE() << "Failed to parse SplitDescription " << str; + return; + } + } + const size_t splitCount = splits.size(); + + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + const SortedVector<SplitDescription>& group = mGroups[i]; + if (group.size() != splitCount) { + continue; + } + + size_t found = 0; + for (size_t j = 0; j < splitCount; j++) { + if (group.indexOf(splits[j]) >= 0) { + found++; + } + } + + if (found == splitCount) { + return; + } + } + + String8 errorMessage("Failed to find expected group ["); + for (size_t i = 0; i < splitCount; i++) { + if (i != 0) { + errorMessage.append(", "); + } + errorMessage.append(splits[i].toString()); + } + errorMessage.append("].\nActual:\n"); + + for (size_t i = 0; i < groupCount; i++) { + errorMessage.appendFormat("Group %d:\n", int(i + 1)); + const SortedVector<SplitDescription>& group = mGroups[i]; + for (size_t j = 0; j < group.size(); j++) { + errorMessage.append(" "); + errorMessage.append(group[j].toString()); + errorMessage.append("\n"); + } + } + ADD_FAILURE() << errorMessage.string(); +} + +void GrouperTest::addSplit(Vector<SplitDescription>& splits, const char* str) { + splits.add(); + EXPECT_TRUE(SplitDescription::parse(String8(str), &splits.editTop())); +} + +} // namespace split diff --git a/tools/split-select/Main.cpp b/tools/split-select/Main.cpp new file mode 100644 index 0000000..d6251c3 --- /dev/null +++ b/tools/split-select/Main.cpp @@ -0,0 +1,314 @@ +/* + * 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. + */ + +#include <algorithm> +#include <cstdio> + +#include "aapt/AaptUtil.h" + +#include "Grouper.h" +#include "Rule.h" +#include "RuleGenerator.h" +#include "SplitDescription.h" + +#include <androidfw/AssetManager.h> +#include <androidfw/ResourceTypes.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +static void usage() { + fprintf(stderr, + "split-select --help\n" + "split-select --target <config> --split <path/to/apk> [--split <path/to/apk> [...]]\n" + "split-select --generate --split <path/to/apk> [--split <path/to/apk> [...]]\n" + "\n" + " --help Displays more information about this program.\n" + " --target <config> Performs the Split APK selection on the given configuration.\n" + " --generate Generates the logic for selecting the Split APK, in JSON format.\n" + " --split <path/to/apk> Includes a Split APK in the selection process.\n" + "\n" + " Where <config> is an extended AAPT resource qualifier of the form\n" + " 'resource-qualifiers:extended-qualifiers', where 'resource-qualifiers' is an AAPT resource\n" + " qualifier (ex: en-rUS-sw600dp-xhdpi), and 'extended-qualifiers' is an ordered list of one\n" + " qualifier (or none) from each category:\n" + " Architecture: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips\n"); +} + +static void help() { + usage(); + fprintf(stderr, "\n" + " Generates the logic for selecting a Split APK given some target Android device configuration.\n" + " Using the flag --generate will emit a JSON encoded tree of rules that must be satisfied in order\n" + " to install the given Split APK. Using the flag --target along with the device configuration\n" + " will emit the set of Split APKs to install, following the same logic that would have been emitted\n" + " via JSON.\n"); +} + +class SplitSelector { +public: + SplitSelector() = default; + SplitSelector(const Vector<SplitDescription>& splits); + + Vector<SplitDescription> getBestSplits(const SplitDescription& target) const; + + template <typename RuleGenerator> + KeyedVector<SplitDescription, sp<Rule> > getRules() const; + +private: + Vector<SortedVector<SplitDescription> > mGroups; +}; + +SplitSelector::SplitSelector(const Vector<SplitDescription>& splits) + : mGroups(groupByMutualExclusivity(splits)) { +} + +static void selectBestFromGroup(const SortedVector<SplitDescription>& splits, + const SplitDescription& target, Vector<SplitDescription>& splitsOut) { + SplitDescription bestSplit; + bool isSet = false; + const size_t splitCount = splits.size(); + for (size_t j = 0; j < splitCount; j++) { + const SplitDescription& thisSplit = splits[j]; + if (!thisSplit.match(target)) { + continue; + } + + if (!isSet || thisSplit.isBetterThan(bestSplit, target)) { + isSet = true; + bestSplit = thisSplit; + } + } + + if (isSet) { + splitsOut.add(bestSplit); + } +} + +Vector<SplitDescription> SplitSelector::getBestSplits(const SplitDescription& target) const { + Vector<SplitDescription> bestSplits; + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + selectBestFromGroup(mGroups[i], target, bestSplits); + } + return bestSplits; +} + +template <typename RuleGenerator> +KeyedVector<SplitDescription, sp<Rule> > SplitSelector::getRules() const { + KeyedVector<SplitDescription, sp<Rule> > rules; + + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + const SortedVector<SplitDescription>& splits = mGroups[i]; + const size_t splitCount = splits.size(); + for (size_t j = 0; j < splitCount; j++) { + sp<Rule> rule = Rule::simplify(RuleGenerator::generate(splits, j)); + if (rule != NULL) { + rules.add(splits[j], rule); + } + } + } + return rules; +} + +Vector<SplitDescription> select(const SplitDescription& target, const Vector<SplitDescription>& splits) { + const SplitSelector selector(splits); + return selector.getBestSplits(target); +} + +void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits) { + Vector<SplitDescription> allSplits; + const size_t apkSplitCount = splits.size(); + for (size_t i = 0; i < apkSplitCount; i++) { + allSplits.appendVector(splits[i]); + } + const SplitSelector selector(allSplits); + KeyedVector<SplitDescription, sp<Rule> > rules(selector.getRules<RuleGenerator>()); + + fprintf(stdout, "[\n"); + for (size_t i = 0; i < apkSplitCount; i++) { + sp<Rule> masterRule = new Rule(); + masterRule->op = Rule::OR_SUBRULES; + const Vector<SplitDescription>& splitDescriptions = splits[i]; + const size_t splitDescriptionCount = splitDescriptions.size(); + for (size_t j = 0; j < splitDescriptionCount; j++) { + masterRule->subrules.add(rules.valueFor(splitDescriptions[j])); + } + masterRule = Rule::simplify(masterRule); + fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }%s\n", + splits.keyAt(i).string(), + masterRule->toJson(2).string(), + i < apkSplitCount - 1 ? "," : ""); + } + fprintf(stdout, "]\n"); +} + +static void removeRuntimeQualifiers(ConfigDescription* outConfig) { + outConfig->imsi = 0; + outConfig->orientation = ResTable_config::ORIENTATION_ANY; + outConfig->screenWidth = ResTable_config::SCREENWIDTH_ANY; + outConfig->screenHeight = ResTable_config::SCREENHEIGHT_ANY; + outConfig->uiMode &= ResTable_config::UI_MODE_NIGHT_ANY; +} + +static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& path) { + AssetManager assetManager; + Vector<SplitDescription> splits; + int32_t cookie = 0; + if (!assetManager.addAssetPath(path, &cookie)) { + return splits; + } + + const ResTable& res = assetManager.getResources(false); + if (res.getError() == NO_ERROR) { + Vector<ResTable_config> configs; + res.getConfigurations(&configs); + const size_t configCount = configs.size(); + for (size_t i = 0; i < configCount; i++) { + splits.add(); + splits.editTop().config = configs[i]; + } + } + + AssetDir* dir = assetManager.openNonAssetDir(cookie, "lib"); + if (dir != NULL) { + const size_t fileCount = dir->getFileCount(); + for (size_t i = 0; i < fileCount; i++) { + splits.add(); + Vector<String8> parts = AaptUtil::splitAndLowerCase(dir->getFileName(i), '-'); + if (parseAbi(parts, 0, &splits.editTop()) < 0) { + fprintf(stderr, "Malformed library %s\n", dir->getFileName(i).string()); + splits.pop(); + } + } + delete dir; + } + return splits; +} + +static int main(int argc, char** argv) { + // Skip over the first argument. + argc--; + argv++; + + bool generateFlag = false; + String8 targetConfigStr; + Vector<String8> splitApkPaths; + while (argc > 0) { + const String8 arg(*argv); + if (arg == "--target") { + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "Missing parameter for --split.\n"); + usage(); + return 1; + } + targetConfigStr.setTo(*argv); + } else if (arg == "--split") { + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "Missing parameter for --split.\n"); + usage(); + return 1; + } + splitApkPaths.add(String8(*argv)); + } else if (arg == "--generate") { + generateFlag = true; + } else if (arg == "--help") { + help(); + return 0; + } else { + fprintf(stderr, "Unknown argument '%s'\n", arg.string()); + usage(); + return 1; + } + argc--; + argv++; + } + + if (!generateFlag && targetConfigStr == "") { + usage(); + return 1; + } + + if (splitApkPaths.size() == 0) { + usage(); + return 1; + } + + SplitDescription targetSplit; + if (!generateFlag) { + if (!SplitDescription::parse(targetConfigStr, &targetSplit)) { + fprintf(stderr, "Invalid --target config: '%s'\n", + targetConfigStr.string()); + usage(); + return 1; + } + + // We don't want to match on things that will change at run-time + // (orientation, w/h, etc.). + removeRuntimeQualifiers(&targetSplit.config); + } + + KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap; + KeyedVector<SplitDescription, String8> splitApkPathMap; + Vector<SplitDescription> splitConfigs; + const size_t splitCount = splitApkPaths.size(); + for (size_t i = 0; i < splitCount; i++) { + Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]); + if (splits.isEmpty()) { + fprintf(stderr, "Invalid --split path: '%s'. No splits found.\n", + splitApkPaths[i].string()); + usage(); + return 1; + } + apkPathSplitMap.replaceValueFor(splitApkPaths[i], splits); + const size_t apkSplitDescriptionCount = splits.size(); + for (size_t j = 0; j < apkSplitDescriptionCount; j++) { + splitApkPathMap.replaceValueFor(splits[j], splitApkPaths[i]); + } + splitConfigs.appendVector(splits); + } + + if (!generateFlag) { + Vector<SplitDescription> matchingConfigs = select(targetSplit, splitConfigs); + const size_t matchingConfigCount = matchingConfigs.size(); + SortedVector<String8> matchingSplitPaths; + for (size_t i = 0; i < matchingConfigCount; i++) { + matchingSplitPaths.add(splitApkPathMap.valueFor(matchingConfigs[i])); + } + + const size_t matchingSplitApkPathCount = matchingSplitPaths.size(); + for (size_t i = 0; i < matchingSplitApkPathCount; i++) { + fprintf(stderr, "%s\n", matchingSplitPaths[i].string()); + } + } else { + generate(apkPathSplitMap); + } + return 0; +} + +} // namespace split + +int main(int argc, char** argv) { + return split::main(argc, argv); +} diff --git a/tools/split-select/Rule.cpp b/tools/split-select/Rule.cpp new file mode 100644 index 0000000..9559fe2 --- /dev/null +++ b/tools/split-select/Rule.cpp @@ -0,0 +1,196 @@ +/* + * 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. + */ + +#include "Rule.h" + +#include <utils/String8.h> + +using namespace android; + +namespace split { + +inline static void indentStr(String8& str, int indent) { + while (indent > 0) { + str.append(" "); + indent--; + } +} + +String8 Rule::toJson(int indent) const { + String8 str; + indentStr(str, indent); + str.append("{\n"); + indent++; + indentStr(str, indent); + str.append("\"op\": \""); + switch (op) { + case ALWAYS_TRUE: + str.append("ALWAYS_TRUE"); + break; + case GREATER_THAN: + str.append("GREATER_THAN"); + break; + case LESS_THAN: + str.append("LESS_THAN"); + break; + case EQUALS: + str.append("EQUALS"); + break; + case AND_SUBRULES: + str.append("AND_SUBRULES"); + break; + case OR_SUBRULES: + str.append("OR_SUBRULES"); + break; + case CONTAINS_ANY: + str.append("CONTAINS_ANY"); + break; + default: + str.appendFormat("%d", op); + break; + } + str.append("\""); + + if (negate) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"negate\": true"); + } + + bool includeKey = true; + switch (op) { + case AND_SUBRULES: + case OR_SUBRULES: + includeKey = false; + break; + default: + break; + } + + if (includeKey) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"property\": \""); + switch (key) { + case NONE: + str.append("NONE"); + break; + case SDK_VERSION: + str.append("SDK_VERSION"); + break; + case SCREEN_DENSITY: + str.append("SCREEN_DENSITY"); + break; + case NATIVE_PLATFORM: + str.append("NATIVE_PLATFORM"); + break; + case LANGUAGE: + str.append("LANGUAGE"); + break; + default: + str.appendFormat("%d", key); + break; + } + str.append("\""); + } + + if (op == AND_SUBRULES || op == OR_SUBRULES) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"subrules\": [\n"); + const size_t subruleCount = subrules.size(); + for (size_t i = 0; i < subruleCount; i++) { + str.append(subrules[i]->toJson(indent + 1)); + if (i != subruleCount - 1) { + str.append(","); + } + str.append("\n"); + } + indentStr(str, indent); + str.append("]"); + } else { + switch (key) { + case SDK_VERSION: + case SCREEN_DENSITY: { + str.append(",\n"); + indentStr(str, indent); + str.append("\"args\": ["); + const size_t argCount = longArgs.size(); + for (size_t i = 0; i < argCount; i++) { + if (i != 0) { + str.append(", "); + } + str.appendFormat("%d", longArgs[i]); + } + str.append("]"); + break; + } + case LANGUAGE: + case NATIVE_PLATFORM: { + str.append(",\n"); + indentStr(str, indent); + str.append("\"args\": ["); + const size_t argCount = stringArgs.size(); + for (size_t i = 0; i < argCount; i++) { + if (i != 0) { + str.append(", "); + } + str.append(stringArgs[i]); + } + str.append("]"); + break; + } + default: + break; + } + } + str.append("\n"); + indent--; + indentStr(str, indent); + str.append("}"); + return str; +} + +sp<Rule> Rule::simplify(sp<Rule> rule) { + if (rule->op != AND_SUBRULES && rule->op != OR_SUBRULES) { + return rule; + } + + Vector<sp<Rule> > newSubrules; + newSubrules.setCapacity(rule->subrules.size()); + const size_t subruleCount = rule->subrules.size(); + for (size_t i = 0; i < subruleCount; i++) { + sp<Rule> simplifiedRule = simplify(rule->subrules.editItemAt(i)); + if (simplifiedRule != NULL) { + if (simplifiedRule->op == rule->op) { + newSubrules.appendVector(simplifiedRule->subrules); + } else { + newSubrules.add(simplifiedRule); + } + } + } + + const size_t newSubruleCount = newSubrules.size(); + if (newSubruleCount == 0) { + return NULL; + } else if (subruleCount == 1) { + return newSubrules.editTop(); + } + rule->subrules = newSubrules; + return rule; +} + +} // namespace split diff --git a/tools/split-select/Rule.h b/tools/split-select/Rule.h new file mode 100644 index 0000000..8029931 --- /dev/null +++ b/tools/split-select/Rule.h @@ -0,0 +1,78 @@ +/* + * 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 H_ANDROID_SPLIT_RULE +#define H_ANDROID_SPLIT_RULE + +#include "SplitDescription.h" + +#include <utils/RefBase.h> +#include <utils/StrongPointer.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace split { + +struct Rule : public virtual android::RefBase { + inline Rule(); + + enum Operator { + LESS_THAN = 1, + GREATER_THAN, + EQUALS, + CONTAINS_ANY, + CONTAINS_ALL, + IS_TRUE, + IS_FALSE, + AND_SUBRULES, + OR_SUBRULES, + ALWAYS_TRUE, + }; + + Operator op; + + enum Key { + NONE = 0, + SDK_VERSION, + SCREEN_DENSITY, + LANGUAGE, + NATIVE_PLATFORM, + TOUCH_SCREEN, + SCREEN_SIZE, + SCREEN_LAYOUT, + }; + + Key key; + bool negate; + + android::Vector<android::String8> stringArgs; + android::Vector<int> longArgs; + android::Vector<double> doubleArgs; + android::Vector<android::sp<Rule> > subrules; + + android::String8 toJson(int indent=0) const; + + static android::sp<Rule> simplify(android::sp<Rule> rule); +}; + +Rule::Rule() +: op(ALWAYS_TRUE) +, key(NONE) +, negate(false) {} + +} // namespace split + +#endif // H_ANDROID_SPLIT_RULE diff --git a/tools/split-select/RuleGenerator.cpp b/tools/split-select/RuleGenerator.cpp new file mode 100644 index 0000000..669ae78 --- /dev/null +++ b/tools/split-select/RuleGenerator.cpp @@ -0,0 +1,153 @@ +/* + * 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. + */ + +#include "RuleGenerator.h" + +#include <algorithm> +#include <cmath> +#include <vector> +#include <androidfw/ResourceTypes.h> + +using namespace android; + +namespace split { + +// Calculate the point at which the density selection changes between l and h. +static inline int findMid(int l, int h) { + double root = sqrt((h*h) + (8*l*h)); + return (double(-h) + root) / 2.0; +} + +sp<Rule> RuleGenerator::generateDensity(const Vector<int>& allDensities, size_t index) { + sp<Rule> densityRule = new Rule(); + densityRule->op = Rule::AND_SUBRULES; + + const bool anyDensity = allDensities[index] == ResTable_config::DENSITY_ANY; + sp<Rule> any = new Rule(); + any->op = Rule::EQUALS; + any->key = Rule::SCREEN_DENSITY; + any->longArgs.add((int)ResTable_config::DENSITY_ANY); + any->negate = !anyDensity; + densityRule->subrules.add(any); + + if (!anyDensity) { + if (index > 0) { + sp<Rule> gt = new Rule(); + gt->op = Rule::GREATER_THAN; + gt->key = Rule::SCREEN_DENSITY; + gt->longArgs.add(findMid(allDensities[index - 1], allDensities[index]) - 1); + densityRule->subrules.add(gt); + } + + if (index + 1 < allDensities.size() && allDensities[index + 1] != ResTable_config::DENSITY_ANY) { + sp<Rule> lt = new Rule(); + lt->op = Rule::LESS_THAN; + lt->key = Rule::SCREEN_DENSITY; + lt->longArgs.add(findMid(allDensities[index], allDensities[index + 1])); + densityRule->subrules.add(lt); + } + } + return densityRule; +} + +sp<Rule> RuleGenerator::generateAbi(const Vector<abi::Variant>& splitAbis, size_t index) { + const abi::Variant thisAbi = splitAbis[index]; + const std::vector<abi::Variant>& familyVariants = abi::getVariants(abi::getFamily(thisAbi)); + + std::vector<abi::Variant>::const_iterator start = + std::find(familyVariants.begin(), familyVariants.end(), thisAbi); + + std::vector<abi::Variant>::const_iterator end = familyVariants.end(); + if (index + 1 < splitAbis.size()) { + end = std::find(start, familyVariants.end(), splitAbis[index + 1]); + } + + sp<Rule> abiRule = new Rule(); + abiRule->op = Rule::CONTAINS_ANY; + abiRule->key = Rule::NATIVE_PLATFORM; + while (start != end) { + abiRule->stringArgs.add(String8(abi::toString(*start))); + ++start; + } + return abiRule; +} + +sp<Rule> RuleGenerator::generate(const SortedVector<SplitDescription>& group, size_t index) { + sp<Rule> rootRule = new Rule(); + rootRule->op = Rule::AND_SUBRULES; + + if (group[index].config.locale != 0) { + sp<Rule> locale = new Rule(); + locale->op = Rule::EQUALS; + locale->key = Rule::LANGUAGE; + char str[RESTABLE_MAX_LOCALE_LEN]; + group[index].config.getBcp47Locale(str); + locale->stringArgs.add(String8(str)); + rootRule->subrules.add(locale); + } + + if (group[index].config.sdkVersion != 0) { + sp<Rule> sdk = new Rule(); + sdk->op = Rule::GREATER_THAN; + sdk->key = Rule::SDK_VERSION; + sdk->longArgs.add(group[index].config.sdkVersion - 1); + rootRule->subrules.add(sdk); + } + + if (group[index].config.density != 0) { + size_t densityIndex = 0; + Vector<int> allDensities; + allDensities.add(group[index].config.density); + + const size_t groupSize = group.size(); + for (size_t i = 0; i < groupSize; i++) { + if (group[i].config.density != group[index].config.density) { + // This group differs by density. + allDensities.clear(); + for (size_t j = 0; j < groupSize; j++) { + allDensities.add(group[j].config.density); + } + densityIndex = index; + break; + } + } + rootRule->subrules.add(generateDensity(allDensities, densityIndex)); + } + + if (group[index].abi != abi::Variant::none) { + size_t abiIndex = 0; + Vector<abi::Variant> allVariants; + allVariants.add(group[index].abi); + + const size_t groupSize = group.size(); + for (size_t i = 0; i < groupSize; i++) { + if (group[i].abi != group[index].abi) { + // This group differs by ABI. + allVariants.clear(); + for (size_t j = 0; j < groupSize; j++) { + allVariants.add(group[j].abi); + } + abiIndex = index; + break; + } + } + rootRule->subrules.add(generateAbi(allVariants, abiIndex)); + } + + return rootRule; +} + +} // namespace split diff --git a/tools/split-select/RuleGenerator.h b/tools/split-select/RuleGenerator.h new file mode 100644 index 0000000..619acd9 --- /dev/null +++ b/tools/split-select/RuleGenerator.h @@ -0,0 +1,39 @@ +/* + * 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 H_ANDROID_SPLIT_RULE_GENERATOR +#define H_ANDROID_SPLIT_RULE_GENERATOR + +#include "Abi.h" +#include "Rule.h" +#include "SplitDescription.h" + +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +namespace split { + +struct RuleGenerator { + // Generate rules for a Split given the group of mutually exclusive splits it belongs to + static android::sp<Rule> generate(const android::SortedVector<SplitDescription>& group, size_t index); + + static android::sp<Rule> generateAbi(const android::Vector<abi::Variant>& allVariants, size_t index); + static android::sp<Rule> generateDensity(const android::Vector<int>& allDensities, size_t index); +}; + +} // namespace split + +#endif // H_ANDROID_SPLIT_RULE_GENERATOR diff --git a/tools/split-select/RuleGenerator_test.cpp b/tools/split-select/RuleGenerator_test.cpp new file mode 100644 index 0000000..60baabe --- /dev/null +++ b/tools/split-select/RuleGenerator_test.cpp @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#include "RuleGenerator.h" + +#include <algorithm> +#include <gtest/gtest.h> +#include <utils/String8.h> + +using namespace android; + +namespace split { + +static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan); +static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, + std::initializer_list<const char*> matches); + +TEST(RuleGeneratorTest, testAbiRules) { + Vector<abi::Variant> abis; + abis.add(abi::Variant::armeabi); + abis.add(abi::Variant::armeabi_v7a); + abis.add(abi::Variant::x86); + std::sort(abis.begin(), abis.end()); + + expectAbiRule(abis, abi::Variant::armeabi, {"armeabi"}); + expectAbiRule(abis, abi::Variant::armeabi_v7a, {"armeabi-v7a", "arm64-v8a"}); + expectAbiRule(abis, abi::Variant::x86, {"x86", "x86_64"}); +} + +TEST(RuleGeneratorTest, testDensityRules) { + Vector<int> densities; + densities.add(ConfigDescription::DENSITY_HIGH); + densities.add(ConfigDescription::DENSITY_XHIGH); + densities.add(ConfigDescription::DENSITY_XXHIGH); + densities.add(ConfigDescription::DENSITY_ANY); + + ASSERT_LT(263, ConfigDescription::DENSITY_XHIGH); + ASSERT_GT(262, ConfigDescription::DENSITY_HIGH); + ASSERT_LT(363, ConfigDescription::DENSITY_XXHIGH); + ASSERT_GT(362, ConfigDescription::DENSITY_XHIGH); + + expectDensityRule(densities, ConfigDescription::DENSITY_HIGH, 0, 263); + expectDensityRule(densities, ConfigDescription::DENSITY_XHIGH, 262, 363); + expectDensityRule(densities, ConfigDescription::DENSITY_XXHIGH, 362, 0); + expectDensityRule(densities, ConfigDescription::DENSITY_ANY, 0, 0); +} + +// +// Helper methods. +// + +static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan) { + const int* iter = std::find(densities.begin(), densities.end(), density); + if (densities.end() == iter) { + ADD_FAILURE() << density << "dpi was not in the density list."; + return; + } + + sp<Rule> rule = RuleGenerator::generateDensity(densities, iter - densities.begin()); + if (rule->op != Rule::AND_SUBRULES) { + ADD_FAILURE() << "Op in rule for " << density << "dpi is not Rule::AND_SUBRULES."; + return; + } + + size_t index = 0; + + bool isAnyDpi = density == ConfigDescription::DENSITY_ANY; + + sp<Rule> anyDpiRule = rule->subrules[index++]; + EXPECT_EQ(Rule::EQUALS, anyDpiRule->op) + << "for " << density << "dpi ANY DPI rule"; + EXPECT_EQ(Rule::SCREEN_DENSITY, anyDpiRule->key) + << "for " << density << "dpi ANY DPI rule"; + EXPECT_EQ(isAnyDpi == false, anyDpiRule->negate) + << "for " << density << "dpi ANY DPI rule"; + if (anyDpiRule->longArgs.size() == 1) { + EXPECT_EQ(ConfigDescription::DENSITY_ANY, anyDpiRule->longArgs[0]) + << "for " << density << "dpi ANY DPI rule"; + } else { + EXPECT_EQ(1u, anyDpiRule->longArgs.size()) + << "for " << density << "dpi ANY DPI rule"; + } + + + if (greaterThan != 0) { + sp<Rule> greaterThanRule = rule->subrules[index++]; + EXPECT_EQ(Rule::GREATER_THAN, greaterThanRule->op) + << "for " << density << "dpi GREATER_THAN rule"; + EXPECT_EQ(Rule::SCREEN_DENSITY, greaterThanRule->key) + << "for " << density << "dpi GREATER_THAN rule"; + if (greaterThanRule->longArgs.size() == 1) { + EXPECT_EQ(greaterThan, greaterThanRule->longArgs[0]) + << "for " << density << "dpi GREATER_THAN rule"; + } else { + EXPECT_EQ(1u, greaterThanRule->longArgs.size()) + << "for " << density << "dpi GREATER_THAN rule"; + } + } + + if (lessThan != 0) { + sp<Rule> lessThanRule = rule->subrules[index++]; + EXPECT_EQ(Rule::LESS_THAN, lessThanRule->op) + << "for " << density << "dpi LESS_THAN rule"; + EXPECT_EQ(Rule::SCREEN_DENSITY, lessThanRule->key) + << "for " << density << "dpi LESS_THAN rule"; + if (lessThanRule->longArgs.size() == 1) { + EXPECT_EQ(lessThan, lessThanRule->longArgs[0]) + << "for " << density << "dpi LESS_THAN rule"; + } else { + EXPECT_EQ(1u, lessThanRule->longArgs.size()) + << "for " << density << "dpi LESS_THAN rule"; + } + } +} + +static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, + std::initializer_list<const char*> matches) { + const abi::Variant* iter = std::find(abis.begin(), abis.end(), variant); + if (abis.end() == iter) { + ADD_FAILURE() << abi::toString(variant) << " was not in the abi list."; + return; + } + + sp<Rule> rule = RuleGenerator::generateAbi(abis, iter - abis.begin()); + + EXPECT_EQ(Rule::CONTAINS_ANY, rule->op) + << "for " << abi::toString(variant) << " rule"; + EXPECT_EQ(Rule::NATIVE_PLATFORM, rule->key) + << " for " << abi::toString(variant) << " rule"; + EXPECT_EQ(matches.size(), rule->stringArgs.size()) + << " for " << abi::toString(variant) << " rule"; + + for (const char* match : matches) { + if (rule->stringArgs.end() == + std::find(rule->stringArgs.begin(), rule->stringArgs.end(), String8(match))) { + ADD_FAILURE() << "Rule for abi " << abi::toString(variant) + << " does not contain match for expected abi " << match; + } + } +} + +} // namespace split diff --git a/tools/split-select/Rule_test.cpp b/tools/split-select/Rule_test.cpp new file mode 100644 index 0000000..aca7433 --- /dev/null +++ b/tools/split-select/Rule_test.cpp @@ -0,0 +1,147 @@ +/* + * 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. + */ + +#include "Rule.h" + +#include "SplitDescription.h" + +#include <algorithm> +#include <string> +#include <gtest/gtest.h> +#include <utils/String8.h> + +using namespace android; + +namespace split { + +TEST(RuleTest, generatesValidJson) { + sp<Rule> rule = new Rule(); + rule->op = Rule::AND_SUBRULES; + + sp<Rule> subrule = new Rule(); + subrule->op = Rule::EQUALS; + subrule->key = Rule::SDK_VERSION; + subrule->longArgs.add(7); + rule->subrules.add(subrule); + + subrule = new Rule(); + subrule->op = Rule::OR_SUBRULES; + rule->subrules.add(subrule); + + sp<Rule> subsubrule = new Rule(); + subsubrule->op = Rule::GREATER_THAN; + subsubrule->key = Rule::SCREEN_DENSITY; + subsubrule->longArgs.add(10); + subrule->subrules.add(subsubrule); + + subsubrule = new Rule(); + subsubrule->op = Rule::LESS_THAN; + subsubrule->key = Rule::SCREEN_DENSITY; + subsubrule->longArgs.add(5); + subrule->subrules.add(subsubrule); + + std::string expected( + "{" + " \"op\": \"AND_SUBRULES\"," + " \"subrules\": [" + " {" + " \"op\": \"EQUALS\"," + " \"property\": \"SDK_VERSION\"," + " \"args\": [7]" + " }," + " {" + " \"op\": \"OR_SUBRULES\"," + " \"subrules\": [" + " {" + " \"op\": \"GREATER_THAN\"," + " \"property\": \"SCREEN_DENSITY\"," + " \"args\": [10]" + " }," + " {" + " \"op\": \"LESS_THAN\"," + " \"property\": \"SCREEN_DENSITY\"," + " \"args\": [5]" + " }" + " ]" + " }" + " ]" + "}"); + // Trim + expected.erase(std::remove_if(expected.begin(), expected.end(), ::isspace), expected.end()); + + std::string result(rule->toJson().string()); + + // Trim + result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end()); + + ASSERT_EQ(expected, result); +} + +TEST(RuleTest, simplifiesSingleSubruleRules) { + sp<Rule> rule = new Rule(); + rule->op = Rule::AND_SUBRULES; + + sp<Rule> subrule = new Rule(); + subrule->op = Rule::EQUALS; + subrule->key = Rule::SDK_VERSION; + subrule->longArgs.add(7); + rule->subrules.add(subrule); + + sp<Rule> simplified = Rule::simplify(rule); + EXPECT_EQ(Rule::EQUALS, simplified->op); + EXPECT_EQ(Rule::SDK_VERSION, simplified->key); + ASSERT_EQ(1u, simplified->longArgs.size()); + EXPECT_EQ(7, simplified->longArgs[0]); +} + +TEST(RuleTest, simplifiesNestedSameOpSubrules) { + sp<Rule> rule = new Rule(); + rule->op = Rule::AND_SUBRULES; + + sp<Rule> subrule = new Rule(); + subrule->op = Rule::AND_SUBRULES; + rule->subrules.add(subrule); + + sp<Rule> subsubrule = new Rule(); + subsubrule->op = Rule::EQUALS; + subsubrule->key = Rule::SDK_VERSION; + subsubrule->longArgs.add(7); + subrule->subrules.add(subsubrule); + + subrule = new Rule(); + subrule->op = Rule::EQUALS; + subrule->key = Rule::SDK_VERSION; + subrule->longArgs.add(8); + rule->subrules.add(subrule); + + sp<Rule> simplified = Rule::simplify(rule); + EXPECT_EQ(Rule::AND_SUBRULES, simplified->op); + ASSERT_EQ(2u, simplified->subrules.size()); + + sp<Rule> simplifiedSubrule = simplified->subrules[0]; + EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op); + EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key); + ASSERT_EQ(1u, simplifiedSubrule->longArgs.size()); + EXPECT_EQ(7, simplifiedSubrule->longArgs[0]); + + simplifiedSubrule = simplified->subrules[1]; + EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op); + EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key); + ASSERT_EQ(1u, simplifiedSubrule->longArgs.size()); + EXPECT_EQ(8, simplifiedSubrule->longArgs[0]); +} + +} // namespace split diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp new file mode 100644 index 0000000..8037ef0 --- /dev/null +++ b/tools/split-select/SplitDescription.cpp @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "SplitDescription.h" + +#include "aapt/AaptConfig.h" +#include "aapt/AaptUtil.h" + +#include <utils/String8.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +SplitDescription::SplitDescription() +: abi(abi::Variant::none) { +} + +int SplitDescription::compare(const SplitDescription& rhs) const { + int cmp; + cmp = (int)abi - (int)rhs.abi; + if (cmp != 0) return cmp; + return config.compareLogical(rhs.config); +} + +bool SplitDescription::isBetterThan(const SplitDescription& o, const SplitDescription& target) const { + if (abi != abi::Variant::none || o.abi != abi::Variant::none) { + abi::Family family = abi::getFamily(abi); + abi::Family oFamily = abi::getFamily(o.abi); + if (family != oFamily) { + return family != abi::Family::none; + } + + if (int(target.abi) - int(abi) < int(target.abi) - int(o.abi)) { + return true; + } + } + return config.isBetterThan(o.config, &target.config); +} + +bool SplitDescription::match(const SplitDescription& o) const { + if (abi != abi::Variant::none) { + abi::Family family = abi::getFamily(abi); + abi::Family oFamily = abi::getFamily(o.abi); + if (family != oFamily) { + return false; + } + + if (int(abi) > int(o.abi)) { + return false; + } + } + return config.match(o.config); +} + +String8 SplitDescription::toString() const { + String8 extension; + if (abi != abi::Variant::none) { + if (extension.isEmpty()) { + extension.append(":"); + } else { + extension.append("-"); + } + extension.append(abi::toString(abi)); + } + String8 str(config.toString()); + str.append(extension); + return str; +} + +ssize_t parseAbi(const Vector<String8>& parts, const ssize_t index, + SplitDescription* outSplit) { + const ssize_t N = parts.size(); + abi::Variant abi = abi::Variant::none; + ssize_t endIndex = index; + if (parts[endIndex] == "arm64") { + endIndex++; + if (endIndex < N) { + if (parts[endIndex] == "v8a") { + endIndex++; + abi = abi::Variant::arm64_v8a; + } + } + } else if (parts[endIndex] == "armeabi") { + endIndex++; + abi = abi::Variant::armeabi; + if (endIndex < N) { + if (parts[endIndex] == "v7a") { + endIndex++; + abi = abi::Variant::armeabi_v7a; + } + } + } else if (parts[endIndex] == "x86") { + endIndex++; + abi = abi::Variant::x86; + } else if (parts[endIndex] == "x86_64") { + endIndex++; + abi = abi::Variant::x86_64; + } else if (parts[endIndex] == "mips") { + endIndex++; + abi = abi::Variant::mips; + } else if (parts[endIndex] == "mips64") { + endIndex++; + abi = abi::Variant::mips64; + } + + if (abi == abi::Variant::none && endIndex != index) { + return -1; + } + + if (outSplit != NULL) { + outSplit->abi = abi; + } + return endIndex; +} + +bool SplitDescription::parse(const String8& str, SplitDescription* outSplit) { + ssize_t index = str.find(":"); + + String8 configStr; + String8 extensionStr; + if (index >= 0) { + configStr.setTo(str.string(), index); + extensionStr.setTo(str.string() + index + 1); + } else { + configStr.setTo(str); + } + + SplitDescription split; + if (!AaptConfig::parse(configStr, &split.config)) { + return false; + } + + Vector<String8> parts = AaptUtil::splitAndLowerCase(extensionStr, '-'); + const ssize_t N = parts.size(); + index = 0; + + if (extensionStr.length() == 0) { + goto success; + } + + index = parseAbi(parts, index, &split); + if (index < 0) { + return false; + } else { + if (index == N) { + goto success; + } + } + + // Unrecognized + return false; + +success: + if (outSplit != NULL) { + *outSplit = split; + } + return true; +} + +} // namespace split diff --git a/tools/split-select/SplitDescription.h b/tools/split-select/SplitDescription.h new file mode 100644 index 0000000..5fcafc8 --- /dev/null +++ b/tools/split-select/SplitDescription.h @@ -0,0 +1,65 @@ +/* + * 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 H_ANDROID_SPLIT_SPLIT_DESCRIPTION +#define H_ANDROID_SPLIT_SPLIT_DESCRIPTION + +#include "aapt/ConfigDescription.h" +#include "Abi.h" + +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace split { + +struct SplitDescription { + SplitDescription(); + SplitDescription(const SplitDescription&) = default; + + ConfigDescription config; + abi::Variant abi; + + int compare(const SplitDescription& rhs) const; + inline bool operator<(const SplitDescription& rhs) const; + inline bool operator==(const SplitDescription& rhs) const; + inline bool operator!=(const SplitDescription& rhs) const; + + bool match(const SplitDescription& o) const; + bool isBetterThan(const SplitDescription& o, const SplitDescription& target) const; + + android::String8 toString() const; + + static bool parse(const android::String8& str, SplitDescription* outSplit); +}; + +ssize_t parseAbi(const android::Vector<android::String8>& parts, const ssize_t index, + SplitDescription* outSplit); + +bool SplitDescription::operator<(const SplitDescription& rhs) const { + return compare(rhs) < 0; +} + +bool SplitDescription::operator==(const SplitDescription& rhs) const { + return compare(rhs) == 0; +} + +bool SplitDescription::operator!=(const SplitDescription& rhs) const { + return compare(rhs) != 0; +} + +} // namespace split + +#endif // H_ANDROID_SPLIT_SPLIT_DESCRIPTION |