diff options
Diffstat (limited to 'core/java')
22 files changed, 666 insertions, 297 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ed9babf..0c47069 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1503,7 +1503,16 @@ public class Notification implements Parcelable RemoteViews button = new RemoteViews(mContext.getPackageName(), R.layout.notification_action); button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); - button.setOnClickPendingIntent(R.id.action0, action.actionIntent); + if (action.actionIntent != null) { + button.setOnClickPendingIntent(R.id.action0, action.actionIntent); + //button.setBoolean(R.id.action0, "setEnabled", true); + button.setFloat(R.id.button0, "setAlpha", 1.0f); + button.setBoolean(R.id.button0, "setClickable", true); + } else { + //button.setBoolean(R.id.action0, "setEnabled", false); + button.setFloat(R.id.button0, "setAlpha", 0.5f); + button.setBoolean(R.id.button0, "setClickable", false); + } button.setContentDescription(R.id.action0, action.title); return button; } diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java index 5b1440d..88112a7 100644 --- a/core/java/android/content/pm/ContainerEncryptionParams.java +++ b/core/java/android/content/pm/ContainerEncryptionParams.java @@ -70,16 +70,16 @@ public class ContainerEncryptionParams implements Parcelable { private final byte[] mMacTag; /** Offset into file where authenticated (e.g., MAC protected) data begins. */ - private final int mAuthenticatedDataStart; + private final long mAuthenticatedDataStart; /** Offset into file where encrypted data begins. */ - private final int mEncryptedDataStart; + private final long mEncryptedDataStart; /** * Offset into file for the end of encrypted data (and, by extension, * authenticated data) in file. */ - private final int mDataEnd; + private final long mDataEnd; public ContainerEncryptionParams(String encryptionAlgorithm, AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey) @@ -99,6 +99,8 @@ public class ContainerEncryptionParams implements Parcelable { * @param macAlgorithm MAC algorithm to use; format matches JCE * @param macSpec algorithm parameters specification, may be {@code null} * @param macKey key used for authentication (i.e., for the MAC tag) + * @param macTag message authentication code (MAC) tag for the authenticated + * data * @param authenticatedDataStart offset of start of authenticated data in * stream * @param encryptedDataStart offset of start of encrypted data in stream @@ -109,7 +111,7 @@ public class ContainerEncryptionParams implements Parcelable { public ContainerEncryptionParams(String encryptionAlgorithm, AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey, String macAlgorithm, AlgorithmParameterSpec macSpec, SecretKey macKey, byte[] macTag, - int authenticatedDataStart, int encryptedDataStart, int dataEnd) + long authenticatedDataStart, long encryptedDataStart, long dataEnd) throws InvalidAlgorithmParameterException { if (TextUtils.isEmpty(encryptionAlgorithm)) { throw new NullPointerException("algorithm == null"); @@ -172,15 +174,15 @@ public class ContainerEncryptionParams implements Parcelable { return mMacTag; } - public int getAuthenticatedDataStart() { + public long getAuthenticatedDataStart() { return mAuthenticatedDataStart; } - public int getEncryptedDataStart() { + public long getEncryptedDataStart() { return mEncryptedDataStart; } - public int getDataEnd() { + public long getDataEnd() { return mDataEnd; } @@ -315,9 +317,9 @@ public class ContainerEncryptionParams implements Parcelable { dest.writeByteArray(mMacTag); - dest.writeInt(mAuthenticatedDataStart); - dest.writeInt(mEncryptedDataStart); - dest.writeInt(mDataEnd); + dest.writeLong(mAuthenticatedDataStart); + dest.writeLong(mEncryptedDataStart); + dest.writeLong(mDataEnd); } private ContainerEncryptionParams(Parcel source) throws InvalidAlgorithmParameterException { @@ -333,9 +335,9 @@ public class ContainerEncryptionParams implements Parcelable { mMacTag = source.createByteArray(); - mAuthenticatedDataStart = source.readInt(); - mEncryptedDataStart = source.readInt(); - mDataEnd = source.readInt(); + mAuthenticatedDataStart = source.readLong(); + mEncryptedDataStart = source.readLong(); + mDataEnd = source.readLong(); switch (encParamType) { case ENC_PARAMS_IV_PARAMETERS: diff --git a/core/java/android/content/pm/LimitedLengthInputStream.java b/core/java/android/content/pm/LimitedLengthInputStream.java index 25a490f..e787277 100644 --- a/core/java/android/content/pm/LimitedLengthInputStream.java +++ b/core/java/android/content/pm/LimitedLengthInputStream.java @@ -3,6 +3,7 @@ package android.content.pm; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; /** * A class that limits the amount of data that is read from an InputStream. When @@ -15,20 +16,20 @@ public class LimitedLengthInputStream extends FilterInputStream { /** * The end of the stream where we don't want to allow more data to be read. */ - private final int mEnd; + private final long mEnd; /** * Current offset in the stream. */ - private int mOffset; + private long mOffset; /** * @param in underlying stream to wrap * @param offset offset into stream where data starts * @param length length of data at offset - * @throws IOException if an error occured with the underlying stream + * @throws IOException if an error occurred with the underlying stream */ - public LimitedLengthInputStream(InputStream in, int offset, int length) throws IOException { + public LimitedLengthInputStream(InputStream in, long offset, long length) throws IOException { super(in); if (in == null) { @@ -36,11 +37,15 @@ public class LimitedLengthInputStream extends FilterInputStream { } if (offset < 0) { - throw new IOException("offset == " + offset); + throw new IOException("offset < 0"); } if (length < 0) { - throw new IOException("length must be non-negative; is " + length); + throw new IOException("length < 0"); + } + + if (length > Long.MAX_VALUE - offset) { + throw new IOException("offset + length > Long.MAX_VALUE"); } mEnd = offset + length; @@ -65,8 +70,15 @@ public class LimitedLengthInputStream extends FilterInputStream { return -1; } + final int arrayLength = buffer.length; + Arrays.checkOffsetAndCount(arrayLength, offset, byteCount); + + if (mOffset > Long.MAX_VALUE - byteCount) { + throw new IOException("offset out of bounds: " + mOffset + " + " + byteCount); + } + if (mOffset + byteCount > mEnd) { - byteCount = mEnd - mOffset; + byteCount = (int) (mEnd - mOffset); } final int numRead = super.read(buffer, offset, byteCount); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 5ba1850..6448b55 100755 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -96,14 +96,14 @@ public final class InputManager { * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"> * <keyboard-layout android:name="keyboard_layout_english_us" * android:label="@string/keyboard_layout_english_us_label" - * android:kcm="@raw/keyboard_layout_english_us" /> + * android:keyboardLayout="@raw/keyboard_layout_english_us" /> * </keyboard-layouts> * </p><p> * The <code>android:name</code> attribute specifies an identifier by which * the keyboard layout will be known in the package. * The <code>android:label</code> attributes specifies a human-readable descriptive * label to describe the keyboard layout in the user interface, such as "English (US)". - * The <code>android:kcm</code> attribute refers to a + * The <code>android:keyboardLayout</code> attribute refers to a * <a href="http://source.android.com/tech/input/key-character-map-files.html"> * key character map</a> resource that defines the keyboard layout. * </p> diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 63275cf..2d5b625 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -370,7 +370,7 @@ public class Build { public static final int ICE_CREAM_SANDWICH_MR1 = 15; /** - * Next up on Android! + * Android 4.1. * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> @@ -381,7 +381,7 @@ public class Build { * exist in the application's manifest. * </ul> */ - public static final int JELLY_BEAN = CUR_DEVELOPMENT; + public static final int JELLY_BEAN = 16; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index 5fd7c57..ecbf4bc 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -68,24 +68,29 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl TextAppearance_textSize, -1); mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0); - int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0); - - switch (tf) { - case 1: - mTypeface = "sans"; - break; - - case 2: - mTypeface = "serif"; - break; - - case 3: - mTypeface = "monospace"; - break; - - default: - mTypeface = null; - break; + String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily); + if (family != null) { + mTypeface = family; + } else { + int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0); + + switch (tf) { + case 1: + mTypeface = "sans"; + break; + + case 2: + mTypeface = "serif"; + break; + + case 3: + mTypeface = "monospace"; + break; + + default: + mTypeface = null; + break; + } } a.recycle(); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 2883eca..c4ebec4 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -26,6 +26,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.TimeZone; import java.util.Date; @@ -381,4 +382,22 @@ public class TimeUtils { } formatDuration(time-now, pw, 0); } + + /** + * Convert a System.currentTimeMillis() value to a time of day value like + * that printed in logs. MM-DD HH:MM:SS.MMM + * + * @param millis since the epoch (1/1/1970) + * @return String representation of the time. + * @hide + */ + public static String logTimeOfDay(long millis) { + Calendar c = Calendar.getInstance(); + if (millis >= 0) { + c.setTimeInMillis(millis); + return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c); + } else { + return Long.toString(millis); + } + } } diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index f3618eb..0154556 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -131,7 +131,8 @@ class GLES20DisplayList extends DisplayList { @Override public void setAnimationMatrix(Matrix matrix) { try { - nSetAnimationMatrix(getNativeDisplayList(), matrix.native_instance); + nSetAnimationMatrix(getNativeDisplayList(), + (matrix != null) ? matrix.native_instance : 0); } catch (IllegalStateException e) { // invalid DisplayList okay: we'll set current values the next time we render to it } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 651be2e..2048de2 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -561,7 +561,17 @@ public class TextureView extends View { applyUpdate(); applyTransformMatrix(); - mLayer.copyInto(bitmap); + // This case can happen if the app invokes setSurfaceTexture() before + // we are able to create the hardware layer. We can safely initialize + // the layer here thanks to the validate() call at the beginning of + // this method + if (mLayer == null && mUpdateSurface) { + getHardwareLayer(); + } + + if (mLayer != null) { + mLayer.copyInto(bitmap); + } } return bitmap; } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 1c35e31..f703e34 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -298,6 +298,24 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { return estimate(time, yCoeff); } + /** + * Gets the X coefficient with the specified index. + * @param index The index of the coefficient to return. + * @return The X coefficient, or 0 if the index is greater than the degree. + */ + public float getXCoeff(int index) { + return index <= degree ? xCoeff[index] : 0; + } + + /** + * Gets the Y coefficient with the specified index. + * @param index The index of the coefficient to return. + * @return The Y coefficient, or 0 if the index is greater than the degree. + */ + public float getYCoeff(int index) { + return index <= degree ? yCoeff[index] : 0; + } + private float estimate(float time, float[] c) { float a = 0; float scale = 1; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index aad6756..55ea938 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2128,6 +2128,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ static final int ACCESSIBILITY_STATE_CHANGED = 0x00000080 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + /** + * Flag indicating that view has an animation set on it. This is used to track whether an + * animation is cleared between successive frames, in order to tell the associated + * DisplayList to clear its animation matrix. + */ + static final int VIEW_IS_ANIMATING_TRANSFORM = 0x10000000; + + /** + * Flag indicating whether a view failed the quickReject() check in draw(). This condition + * is used to check whether later changes to the view's transform should invalidate the + * view to force the quickReject test to run again. + */ + static final int VIEW_QUICK_REJECTED = 0x20000000; + /* End of masks for mPrivateFlags2 */ static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED; @@ -5209,6 +5223,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * call to continue to your children, you must be sure to call the super * implementation. * + * <p>Here is a sample layout that makes use of fitting system windows + * to have controls for a video view placed inside of the window decorations + * that it hides and shows. This can be used with code like the second + * sample (video player) shown in {@link #setSystemUiVisibility(int)}. + * + * {@sample development/samples/ApiDemos/res/layout/video_player.xml complete} + * * @param insets Current content insets of the window. Prior to * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify * the insets or else you and Android will be unhappy. @@ -5251,7 +5272,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Check for the FITS_SYSTEM_WINDOWS flag. If this method returns true, this view + * Check for state of {@link #setFitsSystemWindows(boolean). If this method + * returns true, this view * will account for system screen decorations such as the status bar and inset its * content. This allows the view to be positioned in absolute screen coordinates * and remain visible to the user. @@ -5260,10 +5282,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @attr ref android.R.styleable#View_fitsSystemWindows */ - public boolean fitsSystemWindows() { + public boolean getFitsSystemWindows() { return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS; } + /** @hide */ + public boolean fitsSystemWindows() { + return getFitsSystemWindows(); + } + /** * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed. */ @@ -8547,6 +8574,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setCameraDistance(-Math.abs(distance) / dpi); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } /** @@ -8589,6 +8620,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotation(rotation); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8636,6 +8671,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotationY(rotationY); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8683,6 +8722,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotationX(rotationX); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8722,6 +8765,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setScaleX(scaleX); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8761,6 +8808,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setScaleY(scaleY); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8808,6 +8859,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setPivotX(pivotX); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -8854,6 +8909,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setPivotY(pivotY); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9012,6 +9071,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9081,6 +9144,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9144,6 +9211,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9204,6 +9275,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9288,6 +9363,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setTranslationX(translationX); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -9325,6 +9404,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setTranslationY(translationY); } + if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } } } @@ -12764,16 +12847,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (a != null) { more = drawAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); + if (concatMatrix) { + mPrivateFlags2 |= VIEW_IS_ANIMATING_TRANSFORM; + } transformToApply = parent.mChildTransformation; - } else if (!useDisplayListProperties && - (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { - final boolean hasTransform = - parent.getChildStaticTransformation(this, parent.mChildTransformation); - if (hasTransform) { - final int transformType = parent.mChildTransformation.getTransformationType(); - transformToApply = transformType != Transformation.TYPE_IDENTITY ? - parent.mChildTransformation : null; - concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; + } else { + if ((mPrivateFlags2 & VIEW_IS_ANIMATING_TRANSFORM) == VIEW_IS_ANIMATING_TRANSFORM && + mDisplayList != null) { + // No longer animating: clear out old animation matrix + mDisplayList.setAnimationMatrix(null); + mPrivateFlags2 &= ~VIEW_IS_ANIMATING_TRANSFORM; + } + if (!useDisplayListProperties && + (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + final boolean hasTransform = + parent.getChildStaticTransformation(this, parent.mChildTransformation); + if (hasTransform) { + final int transformType = parent.mChildTransformation.getTransformationType(); + transformToApply = transformType != Transformation.TYPE_IDENTITY ? + parent.mChildTransformation : null; + concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; + } } } @@ -12785,8 +12879,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!concatMatrix && canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) && (mPrivateFlags & DRAW_ANIMATION) == 0) { + mPrivateFlags2 |= VIEW_QUICK_REJECTED; return more; } + mPrivateFlags2 &= ~VIEW_QUICK_REJECTED; if (hardwareAccelerated) { // Clear INVALIDATED flag to allow invalidation to occur during rendering, but @@ -15376,7 +15472,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * playing the application would like to go into a complete full-screen mode, * to use as much of the display as possible for the video. When in this state * the user can not interact with the application; the system intercepts - * touching on the screen to pop the UI out of full screen mode. + * touching on the screen to pop the UI out of full screen mode. See + * {@link #fitSystemWindows(Rect)} for a sample layout that goes with this code. * * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java * content} @@ -15458,11 +15555,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - void updateLocalSystemUiVisibility(int localValue, int localChanges) { + boolean updateLocalSystemUiVisibility(int localValue, int localChanges) { int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges); if (val != mSystemUiVisibility) { setSystemUiVisibility(val); + return true; } + return false; } /** @hide */ @@ -16861,7 +16960,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Interface definition for a callback to be invoked when the status bar changes * visibility. This reports <strong>global</strong> changes to the system UI - * state, not just what the application is requesting. + * state, not what the application is requesting. * * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ @@ -16870,10 +16969,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Called when the status bar changes visibility because of a call to * {@link View#setSystemUiVisibility(int)}. * - * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or - * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. This tells you the - * <strong>global</strong> state of the UI visibility flags, not what your - * app is currently applying. + * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE}, + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, and {@link #SYSTEM_UI_FLAG_FULLSCREEN}. + * This tells you the <strong>global</strong> state of these UI visibility + * flags, not what your app is currently applying. */ public void onSystemUiVisibilityChange(int visibility); } @@ -17159,6 +17258,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mDisabledSystemUiVisibility; /** + * Last global system UI visibility reported by the window manager. + */ + int mGlobalSystemUiVisibility; + + /** * True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener * attached. */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index acfca26..b3c8895 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1317,15 +1317,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - void updateLocalSystemUiVisibility(int localValue, int localChanges) { - super.updateLocalSystemUiVisibility(localValue, localChanges); + boolean updateLocalSystemUiVisibility(int localValue, int localChanges) { + boolean changed = super.updateLocalSystemUiVisibility(localValue, localChanges); final int count = mChildrenCount; final View[] children = mChildren; for (int i=0; i <count; i++) { final View child = children[i]; - child.updateLocalSystemUiVisibility(localValue, localChanges); + changed |= child.updateLocalSystemUiVisibility(localValue, localChanges); } + return changed; } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b43db14..1fcb2c3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3795,13 +3795,15 @@ public final class ViewRootImpl implements ViewParent, } if (mView == null) return; if (args.localChanges != 0) { - if (mAttachInfo != null) { - mAttachInfo.mRecomputeGlobalAttributes = true; - } mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges); - scheduleTraversals(); } - mView.dispatchSystemUiVisibilityChanged(args.globalVisibility); + if (mAttachInfo != null) { + int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS; + if (visibility != mAttachInfo.mGlobalSystemUiVisibility) { + mAttachInfo.mGlobalSystemUiVisibility = visibility; + mView.dispatchSystemUiVisibilityChanged(visibility); + } + } } public void handleDispatchDoneAnimating() { diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 730ad08..62bc502 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -104,7 +104,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView // After we return from this we can't use the surface any more. // The current Video View will be destroy when we play a new video. pauseAndDispatch(mProxy); + // TODO: handle full screen->inline mode transition without a reload. mPlayer.release(); + mPlayer = null; mSurfaceHolder = null; if (mMediaController != null) { mMediaController.hide(); @@ -128,12 +130,12 @@ public class HTML5VideoFullScreen extends HTML5VideoView return mVideoSurfaceView; } - HTML5VideoFullScreen(Context context, int videoLayerId, int position) { + HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) { mVideoSurfaceView = new VideoSurfaceView(context); mFullScreenMode = FULLSCREEN_OFF; mVideoWidth = 0; mVideoHeight = 0; - init(videoLayerId, position); + init(videoLayerId, position, skipPrepare); } private void setMediaController(MediaController m) { @@ -156,8 +158,6 @@ public class HTML5VideoFullScreen extends HTML5VideoView } private void prepareForFullScreen() { - // So in full screen, we reset the MediaPlayer - mPlayer.reset(); MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout); mc.setSystemUiVisibility(mLayout.getSystemUiVisibility()); setMediaController(mc); @@ -198,6 +198,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // after reading the MetaData if (mMediaController != null) { mMediaController.setEnabled(true); + mMediaController.show(); } if (mProgressView != null) { @@ -243,7 +244,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // Don't show the controller after exiting the full screen. mMediaController = null; - mCurrentState = STATE_RELEASED; + mCurrentState = STATE_RESETTED; } }; @@ -320,6 +321,13 @@ public class HTML5VideoFullScreen extends HTML5VideoView return 0; } + @Override + public void showControllerInFullScreen() { + if (mMediaController != null) { + mMediaController.show(0); + } + } + // Other listeners functions: private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java index 62e812e..2c7ea5d 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -21,7 +21,7 @@ public class HTML5VideoInline extends HTML5VideoView{ // associated with the surface texture can be used for showing the screen // shot when paused, so they are not singleton. private static SurfaceTexture mSurfaceTexture = null; - private int[] mTextureNames; + private static int[] mTextureNames = null; // Every time when the VideoLayer Id change, we need to recreate the // SurfaceTexture in order to delete the old video's decoder memory. private static int mVideoLayerUsingSurfaceTexture = -1; @@ -35,8 +35,7 @@ public class HTML5VideoInline extends HTML5VideoView{ } HTML5VideoInline(int videoLayerId, int position) { - init(videoLayerId, position); - mTextureNames = null; + init(videoLayerId, position, false); } @Override @@ -69,15 +68,14 @@ public class HTML5VideoInline extends HTML5VideoView{ // Inline Video specific FUNCTIONS: - @Override - public SurfaceTexture getSurfaceTexture(int videoLayerId) { + public static SurfaceTexture getSurfaceTexture(int videoLayerId) { // Create the surface texture. if (videoLayerId != mVideoLayerUsingSurfaceTexture || mSurfaceTexture == null || mTextureNames == null) { - if (mTextureNames != null) { - GLES20.glDeleteTextures(1, mTextureNames, 0); - } + // The GL texture will store in the VideoLayerManager at native side. + // They will be clean up when requested. + // The reason we recreated GL texture name is for screen shot support. mTextureNames = new int[1]; GLES20.glGenTextures(1, mTextureNames, 0); mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 0d3b755..371feea 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -31,11 +31,10 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side. // Please keep them in sync when changed. static final int STATE_INITIALIZED = 0; - static final int STATE_NOTPREPARED = 1; + static final int STATE_PREPARING = 1; static final int STATE_PREPARED = 2; static final int STATE_PLAYING = 3; - static final int STATE_RELEASED = 4; - protected int mCurrentState; + static final int STATE_RESETTED = 4; protected HTML5VideoViewProxy mProxy; @@ -46,11 +45,11 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // This is used to find the VideoLayer on the native side. protected int mVideoLayerId; - // Every video will have one MediaPlayer. Given the fact we only have one - // SurfaceTexture, there is only one MediaPlayer in action. Every time we - // switch videos, a new instance of MediaPlayer will be created in reset(). - // Switching between inline and full screen will also create a new instance. - protected MediaPlayer mPlayer; + // Given the fact we only have one SurfaceTexture, we cannot support multiple + // player at the same time. We may recreate a new one and abandon the old + // one at transition time. + protected static MediaPlayer mPlayer = null; + protected static int mCurrentState = -1; // We need to save such info. protected Uri mUri; @@ -60,10 +59,12 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate protected static Timer mTimer; + protected boolean mPauseDuringPreparing; + // The spec says the timer should fire every 250 ms or less. private static final int TIMEUPDATE_PERIOD = 250; // ms + private boolean mSkipPrepare = false; - protected boolean mPauseDuringPreparing; // common Video control FUNCTIONS: public void start() { if (mCurrentState == STATE_PREPARED) { @@ -83,7 +84,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { public void pause() { if (isPlaying()) { mPlayer.pause(); - } else if (mCurrentState == STATE_NOTPREPARED) { + } else if (mCurrentState == STATE_PREPARING) { mPauseDuringPreparing = true; } // Delete the Timer to stop it since there is no stop call. @@ -124,11 +125,11 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } } - public void release() { - if (mCurrentState != STATE_RELEASED) { - mPlayer.release(); + public void reset() { + if (mCurrentState != STATE_RESETTED) { + mPlayer.reset(); } - mCurrentState = STATE_RELEASED; + mCurrentState = STATE_RESETTED; } public void stopPlayback() { @@ -142,9 +143,16 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } // Every time we start a new Video, we create a VideoView and a MediaPlayer - public void init(int videoLayerId, int position) { - mPlayer = new MediaPlayer(); - mCurrentState = STATE_INITIALIZED; + public void init(int videoLayerId, int position, boolean skipPrepare) { + if (mPlayer == null) { + mPlayer = new MediaPlayer(); + mCurrentState = STATE_INITIALIZED; + } + mSkipPrepare = skipPrepare; + // If we want to skip the prepare, then we keep the state. + if (!mSkipPrepare) { + mCurrentState = STATE_INITIALIZED; + } mProxy = null; mVideoLayerId = videoLayerId; mSaveSeekTime = position; @@ -195,17 +203,28 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public void prepareDataCommon(HTML5VideoViewProxy proxy) { - try { - mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders); - mPlayer.prepareAsync(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + if (!mSkipPrepare) { + try { + mPlayer.reset(); + mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders); + mPlayer.prepareAsync(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + mCurrentState = STATE_PREPARING; + } else { + // If we skip prepare and the onPrepared happened in inline mode, we + // don't need to call prepare again, we just need to call onPrepared + // to refresh the state here. + if (mCurrentState >= STATE_PREPARED) { + onPrepared(mPlayer); + } + mSkipPrepare = false; } - mCurrentState = STATE_NOTPREPARED; } public void reprepareData(HTML5VideoViewProxy proxy) { @@ -294,10 +313,6 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return false; } - public SurfaceTexture getSurfaceTexture(int videoLayerId) { - return null; - } - public void deleteSurfaceTexture() { } @@ -332,14 +347,17 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return false; } - private boolean m_startWhenPrepared = false; + private boolean mStartWhenPrepared = false; public void setStartWhenPrepared(boolean willPlay) { - m_startWhenPrepared = willPlay; + mStartWhenPrepared = willPlay; } public boolean getStartWhenPrepared() { - return m_startWhenPrepared; + return mStartWhenPrepared; + } + + public void showControllerInFullScreen() { } } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 5fa4bad..90db308 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -112,13 +112,14 @@ class HTML5VideoViewProxy extends Handler mBaseLayer = layer; int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); - SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(currentVideoLayerId); + SurfaceTexture surfTexture = + HTML5VideoInline.getSurfaceTexture(currentVideoLayerId); int textureName = mHTML5VideoView.getTextureName(); if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) { int playerState = mHTML5VideoView.getCurrentState(); if (mHTML5VideoView.getPlayerBuffering()) - playerState = HTML5VideoView.STATE_NOTPREPARED; + playerState = HTML5VideoView.STATE_PREPARING; boolean foundInTree = nativeSendSurfaceTexture(surfTexture, layer, currentVideoLayerId, textureName, playerState); @@ -145,6 +146,7 @@ class HTML5VideoViewProxy extends Handler HTML5VideoViewProxy proxy, WebViewClassic webView) { // Save the inline video info and inherit it in the full screen int savePosition = 0; + boolean canSkipPrepare = false; if (mHTML5VideoView != null) { // We don't allow enter full screen mode while the previous // full screen video hasn't finished yet. @@ -156,15 +158,20 @@ class HTML5VideoViewProxy extends Handler // save the current position. if (layerId == mHTML5VideoView.getVideoLayerId()) { savePosition = mHTML5VideoView.getCurrentPosition(); + int playerState = mHTML5VideoView.getCurrentState(); + canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING + || playerState == HTML5VideoView.STATE_PREPARED + || playerState == HTML5VideoView.STATE_PLAYING) + && !mHTML5VideoView.isFullScreenMode(); + } + if (!canSkipPrepare) { + mHTML5VideoView.reset(); } - mHTML5VideoView.release(); } mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(), - layerId, savePosition); + layerId, savePosition, canSkipPrepare); mCurrentProxy = proxy; - mHTML5VideoView.setVideoURI(url, mCurrentProxy); - mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView); } @@ -217,8 +224,7 @@ class HTML5VideoViewProxy extends Handler if (!backFromFullScreenMode) { mHTML5VideoView.pauseAndDispatch(mCurrentProxy); } - // release the media player to avoid finalize error - mHTML5VideoView.release(); + mHTML5VideoView.reset(); } mCurrentProxy = proxy; mHTML5VideoView = new HTML5VideoInline(videoLayerId, time); @@ -273,6 +279,7 @@ class HTML5VideoViewProxy extends Handler } public static void end() { + mHTML5VideoView.showControllerInFullScreen(); if (mCurrentProxy != null) { if (isVideoSelfEnded) mCurrentProxy.dispatchOnEnded(); diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java index 9541435..9328d8c 100644 --- a/core/java/android/webkit/WebViewInputDispatcher.java +++ b/core/java/android/webkit/WebViewInputDispatcher.java @@ -334,6 +334,7 @@ final class WebViewInputDispatcher { DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0, webKitXOffset, webKitYOffset, webKitScale); + updateStateTrackersLocked(d, event); enqueueEventLocked(d); } return true; @@ -787,7 +788,6 @@ final class WebViewInputDispatcher { flags = d.mFlags; - updateStateTrackersLocked(d, event); if (event == d.mEvent) { d.mEvent = null; // retain ownership of event, don't recycle it yet } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index ebf8a4a..7ca02e1 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -343,6 +343,36 @@ public class SpellChecker implements SpellCheckerSessionListener { if (!isInDictionary && looksLikeTypo) { createMisspelledSuggestionSpan( editable, suggestionsInfo, spellCheckSpan, offset, length); + } else { + // Valid word -- isInDictionary || !looksLikeTypo + if (mIsSentenceSpellCheckSupported) { + // Allow the spell checker to remove existing misspelled span by + // overwriting the span over the same place + final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); + final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); + final int start; + final int end; + if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { + start = spellCheckSpanStart + offset; + end = start + length; + } else { + start = spellCheckSpanStart; + end = spellCheckSpanEnd; + } + if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart + && end > start) { + final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); + final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); + if (tempSuggestionSpan != null) { + if (DBG) { + Log.i(TAG, "Remove existing misspelled span. " + + editable.subSequence(start, end)); + } + editable.removeSpan(tempSuggestionSpan); + mSuggestionSpanCache.remove(key); + } + } + } } return spellCheckSpan; } @@ -473,8 +503,16 @@ public class SpellChecker implements SpellCheckerSessionListener { private Object mRange = new Object(); public void parse(int start, int end) { - if (end > start) { - setRangeSpan((Editable) mTextView.getText(), start, end); + final int max = mTextView.length(); + final int parseEnd; + if (end > max) { + Log.w(TAG, "Parse invalid region, from " + start + " to " + end); + parseEnd = max; + } else { + parseEnd = end; + } + if (parseEnd > start) { + setRangeSpan((Editable) mTextView.getText(), start, parseEnd); parse(); } } @@ -612,6 +650,8 @@ public class SpellChecker implements SpellCheckerSessionListener { break; } if (spellCheckEnd <= spellCheckStart) { + Log.w(TAG, "Trying to spellcheck invalid region, from " + + start + " to " + end); break; } if (createSpellCheckSpan) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 555c974..098a034 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -154,6 +154,7 @@ import java.util.Locale; * @attr ref android.R.styleable#TextView_textColorLink * @attr ref android.R.styleable#TextView_textSize * @attr ref android.R.styleable#TextView_textScaleX + * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle * @attr ref android.R.styleable#TextView_cursorVisible @@ -464,6 +465,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ColorStateList textColorHint = null; ColorStateList textColorLink = null; int textSize = 15; + String fontFamily = null; int typefaceIndex = -1; int styleIndex = -1; boolean allCaps = false; @@ -516,6 +518,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener typefaceIndex = appearance.getInt(attr, -1); break; + case com.android.internal.R.styleable.TextAppearance_fontFamily: + fontFamily = appearance.getString(attr); + break; + case com.android.internal.R.styleable.TextAppearance_textStyle: styleIndex = appearance.getInt(attr, -1); break; @@ -781,6 +787,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener styleIndex = a.getInt(attr, styleIndex); break; + case com.android.internal.R.styleable.TextView_fontFamily: + fontFamily = a.getString(attr); + break; + case com.android.internal.R.styleable.TextView_password: password = a.getBoolean(attr, password); break; @@ -1051,7 +1061,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener typefaceIndex = MONOSPACE; } - setTypefaceByIndex(typefaceIndex, styleIndex); + setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex); if (shadowcolor != 0) { setShadowLayer(r, dx, dy, shadowcolor); @@ -1111,8 +1121,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { + private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) { Typeface tf = null; + if (familyName != null) { + tf = Typeface.create(familyName, styleIndex); + if (tf != null) { + setTypeface(tf); + return; + } + } switch (typefaceIndex) { case SANS: tf = Typeface.SANS_SERIF; @@ -2160,14 +2177,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setLinkTextColor(colors); } + String familyName; int typefaceIndex, styleIndex; + familyName = appearance.getString(com.android.internal.R.styleable. + TextAppearance_fontFamily); typefaceIndex = appearance.getInt(com.android.internal.R.styleable. TextAppearance_typeface, -1); styleIndex = appearance.getInt(com.android.internal.R.styleable. TextAppearance_textStyle, -1); - setTypefaceByIndex(typefaceIndex, styleIndex); + setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex); if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps, false)) { @@ -2269,6 +2289,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @see #getTypeface() * + * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ @@ -2290,6 +2311,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @see #setTypeface(Typeface) * + * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ @@ -3690,15 +3712,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean forceUpdate = false; if (isPassword) { setTransformationMethod(PasswordTransformationMethod.getInstance()); - setTypefaceByIndex(MONOSPACE, 0); + setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); } else if (isVisiblePassword) { if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } - setTypefaceByIndex(MONOSPACE, 0); + setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); } else if (wasPassword || wasVisiblePassword) { // not in password mode, clean up typeface and transformation - setTypefaceByIndex(-1, -1); + setTypefaceFromAttrs(null /* fontFamily */, -1, -1); if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index a74ecd3..d6ffba2 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -69,16 +69,19 @@ public class MultiWaveView extends View { public void onGrabbedStateChange(View v, int handle); } - // Tune-able parameters + // Tuneable parameters for animation private static final int CHEVRON_INCREMENTAL_DELAY = 160; private static final int CHEVRON_ANIMATION_DURATION = 850; private static final int RETURN_TO_HOME_DELAY = 1200; private static final int RETURN_TO_HOME_DURATION = 300; private static final int HIDE_ANIMATION_DELAY = 200; - private static final int HIDE_ANIMATION_DURATION = RETURN_TO_HOME_DELAY; - private static final int SHOW_ANIMATION_DURATION = 0; + private static final int HIDE_ANIMATION_DURATION = 200; + private static final int SHOW_ANIMATION_DURATION = 200; private static final int SHOW_ANIMATION_DELAY = 0; private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f; + private static final long RING_EXPAND_DURATION = 200; + private static final float TARGET_INITIAL_POSITION_SCALE = 0.8f; + private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut; private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); @@ -149,6 +152,7 @@ public class MultiWaveView extends View { private int mHorizontalInset; private int mVerticalInset; private int mGravity = Gravity.TOP; + private boolean mInitialLayout = true; public MultiWaveView(Context context) { this(context, null); @@ -177,23 +181,37 @@ public class MultiWaveView extends View { mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false); mGravity = a.getInt(R.styleable.MultiWaveView_gravity, Gravity.TOP); - // Read chevron animation drawables - final int chevrons[] = { R.styleable.MultiWaveView_leftChevronDrawable, - R.styleable.MultiWaveView_rightChevronDrawable, - R.styleable.MultiWaveView_topChevronDrawable, - R.styleable.MultiWaveView_bottomChevronDrawable - }; - - for (int chevron : chevrons) { - TypedValue typedValue = a.peekValue(chevron); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - typedValue != null ? new TargetDrawable(res, typedValue.resourceId) : null); + // Read array of chevron drawables + TypedValue outValue = new TypedValue(); + if (a.getValue(R.styleable.MultiWaveView_chevronDrawables, outValue)) { + ArrayList<TargetDrawable> chevrons = loadDrawableArray(outValue.resourceId); + for (int i = 0; i < chevrons.size(); i++) { + final TargetDrawable chevron = chevrons.get(i); + for (int k = 0; k < mFeedbackCount; k++) { + mChevronDrawables.add(chevron == null ? null : new TargetDrawable(chevron)); + } + } + } + + // Support old-style chevron specification if new specification not found + if (mChevronDrawables.size() == 0) { + final int chevronResIds[] = { + R.styleable.MultiWaveView_rightChevronDrawable, + R.styleable.MultiWaveView_topChevronDrawable, + R.styleable.MultiWaveView_leftChevronDrawable, + R.styleable.MultiWaveView_bottomChevronDrawable + }; + + for (int i = 0; i < chevronResIds.length; i++) { + TypedValue typedValue = a.peekValue(chevronResIds[i]); + for (int k = 0; k < mFeedbackCount; k++) { + mChevronDrawables.add( + typedValue != null ? new TargetDrawable(res, typedValue.resourceId) : null); + } } } // Read array of target drawables - TypedValue outValue = new TypedValue(); if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) { internalSetTargetResources(outValue.resourceId); } @@ -274,7 +292,7 @@ public class MultiWaveView extends View { final int minimumHeight = getSuggestedMinimumHeight(); int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth); int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight); - setupGravity((computedWidth - minimumWidth), (computedHeight - minimumHeight)); + computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight)); setMeasuredDimension(computedWidth, computedHeight); } @@ -314,23 +332,24 @@ public class MultiWaveView extends View { * mFeedbackCount items in the order: left, right, top, bottom. */ private void startChevronAnimation() { - final float r = mHandleDrawable.getWidth() * 0.4f; - final float chevronAnimationDistance = mOuterRadius * 0.9f; - final float from[][] = { - {mWaveCenterX - r, mWaveCenterY}, // left - {mWaveCenterX + r, mWaveCenterY}, // right - {mWaveCenterX, mWaveCenterY - r}, // top - {mWaveCenterX, mWaveCenterY + r} }; // bottom - final float to[][] = { - {mWaveCenterX - chevronAnimationDistance, mWaveCenterY}, // left - {mWaveCenterX + chevronAnimationDistance, mWaveCenterY}, // right - {mWaveCenterX, mWaveCenterY - chevronAnimationDistance}, // top - {mWaveCenterX, mWaveCenterY + chevronAnimationDistance} }; // bottom - + final float chevronStartDistance = mHandleDrawable.getWidth() * 0.8f; + final float chevronStopDistance = mOuterRadius * 0.9f / 2.0f; mChevronAnimations.clear(); final float startScale = 0.5f; final float endScale = 2.0f; - for (int direction = 0; direction < 4; direction++) { + + final int directionCount = mFeedbackCount > 0 ? mChevronDrawables.size()/mFeedbackCount : 0; + + // Add an animation for all chevron drawables. There are mFeedbackCount drawables + // in each direction and directionCount directions. + for (int direction = 0; direction < directionCount; direction++) { + double angle = 2.0 * Math.PI * direction / directionCount; + final float sx = (float) Math.cos(angle); + final float sy = 0.0f - (float) Math.sin(angle); + final float[] xrange = new float[] + {sx * chevronStartDistance, sx * chevronStopDistance}; + final float[] yrange = new float[] + {sy * chevronStartDistance, sy * chevronStopDistance}; for (int count = 0; count < mFeedbackCount; count++) { int delay = count * CHEVRON_INCREMENTAL_DELAY; final TargetDrawable icon = mChevronDrawables.get(direction*mFeedbackCount + count); @@ -340,8 +359,8 @@ public class MultiWaveView extends View { mChevronAnimations.add(Tweener.to(icon, CHEVRON_ANIMATION_DURATION, "ease", mChevronAnimationInterpolator, "delay", delay, - "x", new float[] { from[direction][0], to[direction][0] }, - "y", new float[] { from[direction][1], to[direction][1] }, + "x", xrange, + "y", yrange, "alpha", new float[] {1.0f, 0.0f}, "scaleX", new float[] {startScale, endScale}, "scaleY", new float[] {startScale, endScale}, @@ -416,32 +435,25 @@ public class MultiWaveView extends View { mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f); if (targetHit) { mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); - hideUnselected(activeTarget); // Inform listener of any active targets. Typically only one will be active. if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); dispatchTriggerEvent(mActiveTarget); - mHandleAnimation = Tweener.to(mHandleDrawable, 0, - "ease", Ease.Quart.easeOut, - "delay", RETURN_TO_HOME_DELAY, - "alpha", 1.0f, - "x", mWaveCenterX, - "y", mWaveCenterY, - "onUpdate", mUpdateListener, - "onComplete", mResetListener); - } else { - // Animate finger outline back to home position - mHandleAnimation = Tweener.to(mHandleDrawable, RETURN_TO_HOME_DURATION, - "ease", Ease.Quart.easeOut, - "delay", 0, - "alpha", 1.0f, - "x", mWaveCenterX, - "y", mWaveCenterY, - "onUpdate", mUpdateListener, - "onComplete", mDragging ? mResetListenerWithPing : mResetListener); } + // Animate handle back to the center based on current state. + int delay = targetHit ? RETURN_TO_HOME_DELAY : 0; + int duration = targetHit ? 0 : RETURN_TO_HOME_DURATION; + mHandleAnimation = Tweener.to(mHandleDrawable, duration, + "ease", Ease.Quart.easeOut, + "delay", delay, + "alpha", 1.0f, + "x", 0, + "y", 0, + "onUpdate", mUpdateListener, + "onComplete", (mDragging && !targetHit) ? mResetListenerWithPing : mResetListener); + setGrabbedState(OnTriggerListener.NO_HANDLE); } @@ -461,27 +473,30 @@ public class MultiWaveView extends View { // Note: these animations should complete at the same time so that we can swap out // the target assets asynchronously from the setTargetResources() call. mAnimatingTargets = animate; - if (animate) { - final int duration = animate ? HIDE_ANIMATION_DURATION : 0; - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, duration, - "alpha", 0.0f, - "delay", HIDE_ANIMATION_DELAY, - "onUpdate", mUpdateListener)); - } - mTargetAnimations.add(Tweener.to(mOuterRing, duration, + final int duration = animate ? HIDE_ANIMATION_DURATION : 0; + final int delay = animate ? HIDE_ANIMATION_DELAY : 0; + final int length = mTargetDrawables.size(); + for (int i = 0; i < length; i++) { + TargetDrawable target = mTargetDrawables.get(i); + target.setState(TargetDrawable.STATE_INACTIVE); + mTargetAnimations.add(Tweener.to(target, duration, + "ease", Ease.Cubic.easeOut, "alpha", 0.0f, - "delay", HIDE_ANIMATION_DELAY, - "onUpdate", mUpdateListener, - "onComplete", mTargetUpdateListener)); - } else { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - target.setAlpha(0.0f); - } - mOuterRing.setAlpha(0.0f); + "scaleX", TARGET_INITIAL_POSITION_SCALE, + "scaleY", TARGET_INITIAL_POSITION_SCALE, + "delay", delay, + "onUpdate", mUpdateListener)); } + + float ringScaleTarget = mActiveTarget != -1 ? 1.5f : 0.5f; + mTargetAnimations.add(Tweener.to(mOuterRing, duration, + "ease", Ease.Cubic.easeOut, + "alpha", 0.0f, + "scaleX", ringScaleTarget, + "scaleY", ringScaleTarget, + "delay", delay, + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); } private void showTargets(boolean animate) { @@ -489,26 +504,31 @@ public class MultiWaveView extends View { stopTargetAnimation(); } mAnimatingTargets = animate; - if (animate) { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, SHOW_ANIMATION_DURATION, - "alpha", 1.0f, - "delay", SHOW_ANIMATION_DELAY, - "onUpdate", mUpdateListener)); - } - mTargetAnimations.add(Tweener.to(mOuterRing, SHOW_ANIMATION_DURATION, + final int delay = animate ? SHOW_ANIMATION_DELAY : 0; + final int length = mTargetDrawables.size(); + for (int i = 0; i < length; i++) { + TargetDrawable target = mTargetDrawables.get(i); + target.setState(TargetDrawable.STATE_INACTIVE); + target.setScaleX(TARGET_INITIAL_POSITION_SCALE); + target.setScaleY(TARGET_INITIAL_POSITION_SCALE); + mTargetAnimations.add(Tweener.to(target, animate ? SHOW_ANIMATION_DURATION : 0, + "ease", Ease.Cubic.easeOut, "alpha", 1.0f, - "delay", SHOW_ANIMATION_DELAY, - "onUpdate", mUpdateListener, - "onComplete", mTargetUpdateListener)); - } else { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - target.setAlpha(1.0f); - } - mOuterRing.setAlpha(1.0f); + "scaleX", 1.0f, + "scaleY", 1.0f, + "delay", delay, + "onUpdate", mUpdateListener)); } + mOuterRing.setScaleX(0.5f); + mOuterRing.setScaleY(0.5f); + mTargetAnimations.add(Tweener.to(mOuterRing, animate ? RING_EXPAND_DURATION : 0, + "ease", Ease.Cubic.easeOut, + "alpha", 1.0f, + "scaleX", 1.0f, + "scaleY", 1.0f, + "delay", delay, + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); } private void stopTargetAnimation() { @@ -524,30 +544,39 @@ public class MultiWaveView extends View { } } - private void internalSetTargetResources(int resourceId) { + private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) { Resources res = getContext().getResources(); TypedArray array = res.obtainTypedArray(resourceId); - int count = array.length(); - ArrayList<TargetDrawable> targetDrawables = new ArrayList<TargetDrawable>(count); + final int count = array.length(); + ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count); + for (int i = 0; i < count; i++) { + TypedValue value = array.peekValue(i); + TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0); + drawables.add(target); + } + array.recycle(); + return drawables; + } + + private void internalSetTargetResources(int resourceId) { + mTargetDrawables = loadDrawableArray(resourceId); + mTargetResourceId = resourceId; + final int count = mTargetDrawables.size(); int maxWidth = mHandleDrawable.getWidth(); int maxHeight = mHandleDrawable.getHeight(); for (int i = 0; i < count; i++) { - TypedValue value = array.peekValue(i); - TargetDrawable target= new TargetDrawable(res, value != null ? value.resourceId : 0); - targetDrawables.add(target); + TargetDrawable target = mTargetDrawables.get(i); maxWidth = Math.max(maxWidth, target.getWidth()); maxHeight = Math.max(maxHeight, target.getHeight()); } - mTargetResourceId = resourceId; - mTargetDrawables = targetDrawables; if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) { mMaxTargetWidth = maxWidth; mMaxTargetHeight = maxHeight; requestLayout(); // required to resize layout and call updateTargetPositions() } else { - updateTargetPositions(); + updateTargetPositions(mWaveCenterX, mWaveCenterY); + updateChevronPositions(mWaveCenterX, mWaveCenterY); } - array.recycle(); } /** @@ -645,8 +674,8 @@ public class MultiWaveView extends View { stopTargetAnimation(); hideChevrons(); hideTargets(animate); - mHandleDrawable.setX(mWaveCenterX); - mHandleDrawable.setY(mWaveCenterY); + mHandleDrawable.setX(0); + mHandleDrawable.setY(0); mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); Tweener.reset(); } @@ -677,7 +706,7 @@ public class MultiWaveView extends View { case MotionEvent.ACTION_CANCEL: if (DEBUG) Log.v(TAG, "*** CANCEL ***"); - // handleMove(event); + handleMove(event); handleCancel(event); handled = true; break; @@ -687,7 +716,6 @@ public class MultiWaveView extends View { } private void moveHandleTo(float x, float y, boolean animate) { - // TODO: animate the handle based on the current state/position mHandleDrawable.setX(x); mHandleDrawable.setY(y); } @@ -707,7 +735,14 @@ public class MultiWaveView extends View { private void handleCancel(MotionEvent event) { if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL"); - mActiveTarget = -1; // Drop the active target if canceled. + + // We should drop the active target here but it interferes with + // moving off the screen in the direction of the navigation bar. At some point we may + // want to revisit how we handle this. For now we'll allow a canceled event to + // activate the current target. + + // mActiveTarget = -1; // Drop the active target if canceled. + switchToState(STATE_FINISH, event.getX(), event.getY()); } @@ -719,24 +754,25 @@ public class MultiWaveView extends View { int activeTarget = -1; final int historySize = event.getHistorySize(); + final boolean singleTarget = mTargetDrawables.size() == 1; + float x = 0.0f; + float y = 0.0f; for (int k = 0; k < historySize + 1; k++) { - float x = k < historySize ? event.getHistoricalX(k) : event.getX(); - float y = k < historySize ? event.getHistoricalY(k) : event.getY(); - float tx = x - mWaveCenterX; - float ty = y - mWaveCenterY; + float eventX = k < historySize ? event.getHistoricalX(k) : event.getX(); + float eventY = k < historySize ? event.getHistoricalY(k) : event.getY(); + // tx and ty are relative to wave center + float tx = eventX - mWaveCenterX; + float ty = eventY - mWaveCenterY; float touchRadius = (float) Math.sqrt(dist2(tx, ty)); final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f; - float limitX = mWaveCenterX + tx * scale; - float limitY = mWaveCenterY + ty * scale; + float limitX = tx * scale; + float limitY = ty * scale; - boolean singleTarget = mTargetDrawables.size() == 1; if (singleTarget) { // Snap to outer ring if there's only one target float snapRadius = mOuterRadius - mSnapMargin; if (touchRadius > snapRadius) { activeTarget = 0; - x = limitX; - y = limitY; } } else { // If there's more than one target, snap to the closest one less than hitRadius away. @@ -753,34 +789,47 @@ public class MultiWaveView extends View { best = dist2; } } - x = limitX; - y = limitY; - } - if (activeTarget != -1) { - switchToState(STATE_SNAP, x,y); - float newX = singleTarget ? limitX : mTargetDrawables.get(activeTarget).getX(); - float newY = singleTarget ? limitY : mTargetDrawables.get(activeTarget).getY(); - moveHandleTo(newX, newY, false); - TargetDrawable currentTarget = mTargetDrawables.get(activeTarget); - if (currentTarget.hasState(TargetDrawable.STATE_FOCUSED)) { - currentTarget.setState(TargetDrawable.STATE_FOCUSED); - mHandleDrawable.setAlpha(0.0f); - } - } else { - switchToState(STATE_TRACKING, x, y); - moveHandleTo(x, y, false); - mHandleDrawable.setAlpha(1.0f); } + x = limitX; + y = limitY; + } + + if (activeTarget != -1) { + switchToState(STATE_SNAP, x,y); + TargetDrawable target = mTargetDrawables.get(activeTarget); + float newX = singleTarget ? x : target.getX(); + float newY = singleTarget ? y : target.getY(); + moveHandleTo(newX, newY, false); + } else { + switchToState(STATE_TRACKING, x, y); + moveHandleTo(x, y, false); + mHandleDrawable.setAlpha(1.0f); } // Draw handle outside parent's bounds invalidateGlobalRegion(mHandleDrawable); - if (mActiveTarget != activeTarget && activeTarget != -1) { - dispatchGrabbedEvent(activeTarget); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - String targetContentDescription = getTargetDescription(activeTarget); - announceText(targetContentDescription); + if (mActiveTarget != activeTarget) { + // Defocus the old target + if (mActiveTarget != -1) { + TargetDrawable target = mTargetDrawables.get(mActiveTarget); + if (target.hasState(TargetDrawable.STATE_FOCUSED)) { + target.setState(TargetDrawable.STATE_INACTIVE); + mHandleDrawable.setAlpha(1.0f); + } + } + // Focus the new target + if (activeTarget != -1) { + TargetDrawable target = mTargetDrawables.get(activeTarget); + if (target.hasState(TargetDrawable.STATE_FOCUSED)) { + target.setState(TargetDrawable.STATE_FOCUSED); + mHandleDrawable.setAlpha(0.0f); + } + dispatchGrabbedEvent(activeTarget); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + String targetContentDescription = getTargetDescription(activeTarget); + announceText(targetContentDescription); + } } } mActiveTarget = activeTarget; @@ -831,21 +880,21 @@ public class MultiWaveView extends View { private boolean trySwitchToFirstTouchState(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); - final float dx = x - mWaveCenterX; - final float dy = y - mWaveCenterY; - if (mAlwaysTrackFinger || dist2(dx,dy) <= getScaledTapRadiusSquared()) { + final float tx = x - mWaveCenterX; + final float ty = y - mWaveCenterY; + if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledTapRadiusSquared()) { if (DEBUG) Log.v(TAG, "** Handle HIT"); switchToState(STATE_FIRST_TOUCH, x, y); - moveHandleTo(x, y, false); + moveHandleTo(tx, ty, false); mDragging = true; return true; } return false; } - private void performInitialLayout(float centerX, float centerY) { + private void assignDefaultsIfNeeded(float centerX, float centerY) { if (mOuterRadius == 0.0f) { - mOuterRadius = 0.5f*(float) Math.sqrt(dist2(centerX, centerY)); + mOuterRadius = 0.5f*(float) Math.hypot(centerX, centerY); } if (mHitRadius == 0.0f) { // Use the radius of inscribed circle of the first target. @@ -855,12 +904,9 @@ public class MultiWaveView extends View { mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); } - hideChevrons(); - hideTargets(false); - moveHandleTo(centerX, centerY, false); } - private void setupGravity(int dx, int dy) { + private void computeInsets(int dx, int dy) { final int layoutDirection = getResolvedLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); @@ -899,29 +945,49 @@ public class MultiWaveView extends View { + Math.max(width, mMaxTargetWidth + mOuterRing.getWidth()) / 2; float newWaveCenterY = mVerticalOffset + mVerticalInset + Math.max(height, + mMaxTargetHeight + mOuterRing.getHeight()) / 2; - if (newWaveCenterX != mWaveCenterX || newWaveCenterY != mWaveCenterY) { - if (mWaveCenterX == 0 && mWaveCenterY == 0) { - performInitialLayout(newWaveCenterX, newWaveCenterY); - } - mWaveCenterX = newWaveCenterX; - mWaveCenterY = newWaveCenterY; - mOuterRing.setX(mWaveCenterX); - mOuterRing.setY(Math.max(mWaveCenterY, mWaveCenterY)); + assignDefaultsIfNeeded(newWaveCenterX, newWaveCenterY); + + if (mInitialLayout) { + hideChevrons(); + hideTargets(false); + moveHandleTo(0, 0, false); + mInitialLayout = false; } - updateTargetPositions(); + + mOuterRing.setPositionX(newWaveCenterX); + mOuterRing.setPositionY(newWaveCenterY); + + mHandleDrawable.setPositionX(newWaveCenterX); + mHandleDrawable.setPositionY(newWaveCenterY); + + updateTargetPositions(newWaveCenterX, newWaveCenterY); + updateChevronPositions(newWaveCenterX, newWaveCenterY); + + mWaveCenterX = newWaveCenterX; + mWaveCenterY = newWaveCenterY; + if (DEBUG) dump(); } - private void updateTargetPositions() { + private void updateTargetPositions(float centerX, float centerY) { // Reposition the target drawables if the view changed. for (int i = 0; i < mTargetDrawables.size(); i++) { final TargetDrawable targetIcon = mTargetDrawables.get(i); double angle = -2.0f * Math.PI * i / mTargetDrawables.size(); - float xPosition = mWaveCenterX + mOuterRadius * (float) Math.cos(angle); - float yPosition = mWaveCenterY + mOuterRadius * (float) Math.sin(angle); - targetIcon.setX(xPosition); - targetIcon.setY(yPosition); + targetIcon.setPositionX(centerX); + targetIcon.setPositionY(centerY); + targetIcon.setX(mOuterRadius * (float) Math.cos(angle)); + targetIcon.setY(mOuterRadius * (float) Math.sin(angle)); + } + } + + private void updateChevronPositions(float centerX, float centerY) { + for (TargetDrawable target : mChevronDrawables) { + if (target != null) { + target.setPositionX(centerX); + target.setPositionY(centerY); + } } } diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java index ec2c945..6392093 100644 --- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java +++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java @@ -32,10 +32,13 @@ public class TargetDrawable { public static final int[] STATE_INACTIVE = { android.R.attr.state_enabled, -android.R.attr.state_active }; public static final int[] STATE_FOCUSED = - { android.R.attr.state_enabled, android.R.attr.state_focused }; + { android.R.attr.state_enabled, -android.R.attr.state_active, + android.R.attr.state_focused }; private float mTranslationX = 0.0f; private float mTranslationY = 0.0f; + private float mPositionX = 0.0f; + private float mPositionY = 0.0f; private float mScaleX = 1.0f; private float mScaleY = 1.0f; private float mAlpha = 1.0f; @@ -82,6 +85,14 @@ public class TargetDrawable { setState(STATE_INACTIVE); } + public TargetDrawable(TargetDrawable other) { + mResourceId = other.mResourceId; + // Mutate the drawable so we can animate shared drawable properties. + mDrawable = other.mDrawable != null ? other.mDrawable.mutate() : null; + resizeDrawables(); + setState(STATE_INACTIVE); + } + public void setState(int [] state) { if (mDrawable instanceof StateListDrawable) { StateListDrawable d = (StateListDrawable) mDrawable; @@ -196,6 +207,22 @@ public class TargetDrawable { return mAlpha; } + public void setPositionX(float x) { + mPositionX = x; + } + + public void setPositionY(float y) { + mPositionY = y; + } + + public float getPositionX() { + return mPositionX; + } + + public float getPositionY() { + return mPositionY; + } + public int getWidth() { return mDrawable != null ? mDrawable.getIntrinsicWidth() : 0; } @@ -209,8 +236,8 @@ public class TargetDrawable { return; } canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mTranslationX, mTranslationY); - canvas.scale(mScaleX, mScaleY); + canvas.scale(mScaleX, mScaleY, mPositionX, mPositionY); + canvas.translate(mTranslationX + mPositionX, mTranslationY + mPositionY); canvas.translate(-0.5f * getWidth(), -0.5f * getHeight()); mDrawable.setAlpha((int) Math.round(mAlpha * 255f)); mDrawable.draw(canvas); |
