diff options
61 files changed, 4444 insertions, 480 deletions
diff --git a/api/current.txt b/api/current.txt index 10377fd..d8461e7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -596,7 +596,7 @@ package android { field public static final int layerType = 16843604; // 0x1010354 field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec - field public static final int layoutDirection = 16843690; // 0x10103aa + field public static final int layoutDirection = 16843691; // 0x10103ab field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 field public static final int layout_alignBottom = 16843146; // 0x101018a @@ -618,10 +618,10 @@ package android { field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa - field public static final int layout_marginEnd = 16843694; // 0x10103ae + field public static final int layout_marginEnd = 16843695; // 0x10103af field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 - field public static final int layout_marginStart = 16843693; // 0x10103ad + field public static final int layout_marginStart = 16843694; // 0x10103ae field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c @@ -717,10 +717,10 @@ package android { field public static final int packageNames = 16843649; // 0x1010381 field public static final int padding = 16842965; // 0x10100d5 field public static final int paddingBottom = 16842969; // 0x10100d9 - field public static final int paddingEnd = 16843692; // 0x10103ac + field public static final int paddingEnd = 16843693; // 0x10103ad field public static final int paddingLeft = 16842966; // 0x10100d6 field public static final int paddingRight = 16842968; // 0x10100d8 - field public static final int paddingStart = 16843691; // 0x10103ab + field public static final int paddingStart = 16843692; // 0x10103ac field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 @@ -962,6 +962,7 @@ package android { field public static final int tension = 16843370; // 0x101026a field public static final int testOnly = 16843378; // 0x1010272 field public static final int text = 16843087; // 0x101014f + field public static final int textAlignment = 16843690; // 0x10103aa field public static final int textAllCaps = 16843660; // 0x101038c field public static final int textAppearance = 16842804; // 0x1010034 field public static final int textAppearanceButton = 16843271; // 0x1010207 @@ -23210,6 +23211,7 @@ package android.view { method public void buildLayer(); method public boolean callOnClick(); method public boolean canResolveLayoutDirection(); + method public boolean canResolveTextAlignment(); method public boolean canResolveTextDirection(); method public boolean canScrollHorizontally(int); method public boolean canScrollVertically(int); @@ -23312,6 +23314,8 @@ package android.view { method public final int getMeasuredState(); method public final int getMeasuredWidth(); method public final int getMeasuredWidthAndState(); + method public int getMinimumHeight(); + method public int getMinimumWidth(); method public int getNextFocusDownId(); method public int getNextFocusForwardId(); method public int getNextFocusLeftId(); @@ -23330,6 +23334,7 @@ package android.view { method public float getPivotY(); method public int getResolvedLayoutDirection(); method public int getResolvedLayoutDirection(android.graphics.drawable.Drawable); + method public int getResolvedTextAlignment(); method public int getResolvedTextDirection(); method public android.content.res.Resources getResources(); method public final int getRight(); @@ -23341,6 +23346,9 @@ package android.view { method public float getRotationY(); method public float getScaleX(); method public float getScaleY(); + method public int getScrollBarDefaultDelayBeforeFade(); + method public int getScrollBarFadeDuration(); + method public int getScrollBarSize(); method public int getScrollBarStyle(); method public final int getScrollX(); method public final int getScrollY(); @@ -23350,6 +23358,7 @@ package android.view { method public int getSystemUiVisibility(); method public java.lang.Object getTag(); method public java.lang.Object getTag(int); + method public int getTextAlignment(); method public int getTextDirection(); method public final int getTop(); method protected float getTopFadingEdgeStrength(); @@ -23410,6 +23419,7 @@ package android.view { method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); + method public boolean isScrollContainer(); method public boolean isScrollbarFadingEnabled(); method public boolean isSelected(); method public boolean isShown(); @@ -23457,6 +23467,8 @@ package android.view { method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public void onResolvedLayoutDirectionChanged(); method public void onResolvedLayoutDirectionReset(); + method public void onResolvedTextAlignmentChanged(); + method public void onResolvedTextAlignmentReset(); method public void onResolvedTextDirectionChanged(); method public void onResolvedTextDirectionReset(); method protected void onRestoreInstanceState(android.os.Parcelable); @@ -23497,11 +23509,13 @@ package android.view { method public boolean requestRectangleOnScreen(android.graphics.Rect); method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean); method public void resetResolvedLayoutDirection(); + method public void resetResolvedTextAlignment(); method public void resetResolvedTextDirection(); method public void resolveLayoutDirection(); method public void resolvePadding(); method public static int resolveSize(int, int); method public static int resolveSizeAndState(int, int, int); + method public void resolveTextAlignment(); method public void resolveTextDirection(); method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>); method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>); @@ -23514,8 +23528,9 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); + method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); - method public void setBackgroundDrawable(android.graphics.drawable.Drawable); + method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setBackgroundResource(int); method public final void setBottom(int); method public void setCameraDistance(float); @@ -23575,6 +23590,9 @@ package android.view { method public void setSaveFromParentEnabled(boolean); method public void setScaleX(float); method public void setScaleY(float); + method public void setScrollBarDefaultDelayBeforeFade(int); + method public void setScrollBarFadeDuration(int); + method public void setScrollBarSize(int); method public void setScrollBarStyle(int); method public void setScrollContainer(boolean); method public void setScrollX(int); @@ -23585,6 +23603,7 @@ package android.view { method public void setSystemUiVisibility(int); method public void setTag(java.lang.Object); method public void setTag(int, java.lang.Object); + method public void setTextAlignment(int); method public void setTextDirection(int); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); @@ -23695,6 +23714,15 @@ package android.view { field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1 field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0 field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600 + field public static final int TEXT_ALIGNMENT_CENTER = 4; // 0x4 + field protected static int TEXT_ALIGNMENT_DEFAULT; + field public static final int TEXT_ALIGNMENT_GRAVITY = 1; // 0x1 + field public static final int TEXT_ALIGNMENT_INHERIT = 0; // 0x0 + field public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = 131072; // 0x20000 + field public static final int TEXT_ALIGNMENT_TEXT_END = 3; // 0x3 + field public static final int TEXT_ALIGNMENT_TEXT_START = 2; // 0x2 + field public static final int TEXT_ALIGNMENT_VIEW_END = 6; // 0x6 + field public static final int TEXT_ALIGNMENT_VIEW_START = 5; // 0x5 field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2 field protected static int TEXT_DIRECTION_DEFAULT; field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1 @@ -24120,15 +24148,18 @@ package android.view { } public final class ViewTreeObserver { + method public void addOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); method public void addOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener); method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener); method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener); method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); + method public final void dispatchOnDraw(); method public final void dispatchOnGlobalLayout(); method public final boolean dispatchOnPreDraw(); method public boolean isAlive(); method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); + method public void removeOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); method public void removeOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener); method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener); @@ -24136,6 +24167,10 @@ package android.view { method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); } + public static abstract interface ViewTreeObserver.OnDrawListener { + method public abstract void onDraw(); + } + public static abstract interface ViewTreeObserver.OnGlobalFocusChangeListener { method public abstract void onGlobalFocusChanged(android.view.View, android.view.View); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 096af93..04c64a0 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; import android.text.TextUtils; import android.util.IntProperty; import android.util.Log; @@ -808,6 +809,7 @@ public class Notification implements Parcelable @Deprecated public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { + // TODO: rewrite this to use Builder RemoteViews contentView = new RemoteViews(context.getPackageName(), R.layout.notification_template_base); if (this.icon != 0) { @@ -820,6 +822,7 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.text, contentText); } if (this.when != 0) { + contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", when); } @@ -942,6 +945,7 @@ public class Notification implements Parcelable private ArrayList<Action> mActions = new ArrayList<Action>(3); private boolean mCanHasIntruder; private boolean mIntruderActionsShowText; + private boolean mUseChronometer; /** * Constructs a new Builder with the defaults: @@ -983,6 +987,18 @@ public class Notification implements Parcelable } /** + * @hide + * + * Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp. + * + * @see Notification#when + */ + public Builder setUsesChronometer(boolean b) { + mUseChronometer = b; + return this; + } + + /** * Set the small icon resource, which will be used to represent the notification in the * status bar. * @@ -1434,7 +1450,15 @@ public class Notification implements Parcelable } } if (mWhen != 0) { - contentView.setLong(R.id.time, "setTime", mWhen); + if (mUseChronometer) { + contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); + contentView.setLong(R.id.chronometer, "setBase", + mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); + contentView.setBoolean(R.id.chronometer, "setStarted", true); + } else { + contentView.setViewVisibility(R.id.time, View.VISIBLE); + contentView.setLong(R.id.time, "setTime", mWhen); + } } contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE); return contentView; diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl index e60a5b0..4e79822 100644 --- a/core/java/android/nfc/INdefPushCallback.aidl +++ b/core/java/android/nfc/INdefPushCallback.aidl @@ -17,6 +17,7 @@ package android.nfc; import android.nfc.NdefMessage; +import android.net.Uri; /** * @hide @@ -24,5 +25,7 @@ import android.nfc.NdefMessage; interface INdefPushCallback { NdefMessage createMessage(); + Uri getUri(); + String getMimeType(); void onNdefPushComplete(); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 2c73056..f80dae4 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -18,6 +18,7 @@ package android.nfc; import android.app.Activity; import android.app.Application; +import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; @@ -107,10 +108,16 @@ public final class NfcActivityManager extends INdefPushCallback.Stub NdefMessage ndefMessage = null; // static NDEF message NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; + Uri uri = null; + String mimeType = null; public NfcActivityState(Activity activity) { if (activity.getWindow().isDestroyed()) { throw new IllegalStateException("activity is already destroyed"); } + // Check if activity is resumed right now, as we will not + // immediately get a callback for that. + resumed = activity.isResumed(); + this.activity = activity; registerApplication(activity.getApplication()); } @@ -121,12 +128,14 @@ public final class NfcActivityManager extends INdefPushCallback.Stub ndefMessage = null; ndefMessageCallback = null; onNdefPushCompleteCallback = null; + uri = null; + mimeType = null; } @Override public String toString() { StringBuilder s = new StringBuilder("[").append(" "); s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); - s.append(onNdefPushCompleteCallback).append("]"); + s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); return s.toString(); } } @@ -175,6 +184,19 @@ public final class NfcActivityManager extends INdefPushCallback.Stub mDefaultEvent = new NfcEvent(mAdapter); } + public void setNdefPushContentUri(Activity activity, String mimeType, Uri uri) { + boolean isResumed; + synchronized (NfcActivityManager.this) { + NfcActivityState state = getActivityState(activity); + state.uri = uri; + state.mimeType = mimeType; + isResumed = state.resumed; + } + if (isResumed) { + requestNfcServiceCallback(true); + } + } + public void setNdefPushMessage(Activity activity, NdefMessage message) { boolean isResumed; synchronized (NfcActivityManager.this) { @@ -249,6 +271,26 @@ public final class NfcActivityManager extends INdefPushCallback.Stub /** Callback from NFC service, usually on binder thread */ @Override + public Uri getUri() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.uri; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override + public String getMimeType() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.mimeType; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override public void onNdefPushComplete() { NfcAdapter.OnNdefPushCompleteCallback callback; synchronized (NfcActivityManager.this) { diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index d78e06c..917751c 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.Uri; import android.nfc.tech.MifareClassic; import android.nfc.tech.Ndef; import android.nfc.tech.NfcA; @@ -555,6 +556,18 @@ public final class NfcAdapter { } } + //TODO: Consider a callback alternative + //TOOD: See if we get rid of mimeType + //TODO: make sure NFC service has permission for URI + //TODO: javadoc + /** @hide */ + public void setBeamPushUri(String mimeType, Uri uri, Activity activity) { + if (activity == null) { + throw new NullPointerException("activity cannot be null"); + } + mNfcActivityManager.setNdefPushContentUri(activity, mimeType, uri); + } + /** * Set a static {@link NdefMessage} to send using Android Beam (TM). * diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java index 9d1e6a1..8c92288 100644 --- a/core/java/android/nfc/tech/MifareClassic.java +++ b/core/java/android/nfc/tech/MifareClassic.java @@ -150,6 +150,7 @@ public final class MifareClassic extends BasicTagTechnology { mIsEmulated = false; switch (a.getSak()) { + case 0x01: case 0x08: mType = TYPE_CLASSIC; mSize = SIZE_1K; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d74ccb8..830a85f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1506,13 +1506,22 @@ public final class Settings { public static final String VOLUME_MASTER = "volume_master"; /** - * Whether the notifications should use the ring volume (value of 1) or a separate - * notification volume (value of 0). In most cases, users will have this enabled so the - * notification and ringer volumes will be the same. However, power users can disable this - * and use the separate notification volume control. + * Master volume mute (int 1 = mute, 0 = not muted). + * + * @hide + */ + public static final String VOLUME_MASTER_MUTE = "volume_master_mute"; + + /** + * Whether the notifications should use the ring volume (value of 1) or + * a separate notification volume (value of 0). In most cases, users + * will have this enabled so the notification and ringer volumes will be + * the same. However, power users can disable this and use the separate + * notification volume control. * <p> - * Note: This is a one-off setting that will be removed in the future when there is profile - * support. For this reason, it is kept hidden from the public APIs. + * Note: This is a one-off setting that will be removed in the future + * when there is profile support. For this reason, it is kept hidden + * from the public APIs. * * @hide * @deprecated diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c40a7d5..5d7c8cd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -71,6 +71,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; import static android.os.Build.VERSION_CODES.*; +import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; @@ -126,7 +127,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * example, all views will let you set a listener to be notified when the view * gains or loses focus. You can register such a listener using * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}. - * Other view subclasses offer more specialized listeners. For example, a Button + * Other view subclasses offer more specialized listeners. For example, a Button * exposes a listener to notify clients when the button is clicked.</li> * <li><strong>Set visibility:</strong> You can hide or show views using * {@link #setVisibility(int)}.</li> @@ -579,6 +580,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_duplicateParentState * @attr ref android.R.styleable#View_id * @attr ref android.R.styleable#View_requiresFadingEdge + * @attr ref android.R.styleable#View_fadeScrollbars * @attr ref android.R.styleable#View_fadingEdgeLength * @attr ref android.R.styleable#View_filterTouchesWhenObscured * @attr ref android.R.styleable#View_fitsSystemWindows @@ -624,6 +626,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag + * @attr ref android.R.styleable#View_textAlignment * @attr ref android.R.styleable#View_transformPivotX * @attr ref android.R.styleable#View_transformPivotY * @attr ref android.R.styleable#View_translationX @@ -1827,15 +1830,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int TEXT_DIRECTION_LOCALE = 5; /** - * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) - * @hide + * Default text direction is inherited */ - static final int TEXT_DIRECTION_MASK_SHIFT = 6; + protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; /** - * Default text direction is inherited + * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) + * @hide */ - protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + static final int TEXT_DIRECTION_MASK_SHIFT = 6; /** * Mask for use with private flags indicating bits used for text direction. @@ -1882,6 +1885,113 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + /* + * Default text alignment. The text alignment of this View is inherited from its parent. + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_INHERIT = 0; + + /** + * Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL, + * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_GRAVITY = 1; + + /** + * Align to the start of the paragraph, e.g. ALIGN_NORMAL. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_START = 2; + + /** + * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_END = 3; + + /** + * Center the paragraph, e.g. ALIGN_CENTER. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_CENTER = 4; + + /** + * Align to the start of the view, which is ALIGN_LEFT if the view’s resolved + * layoutDirection is LTR, and ALIGN_RIGHT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_START = 5; + + /** + * Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved + * layoutDirection is LTR, and ALIGN_LEFT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_END = 6; + + /** + * Default text alignment is inherited + */ + protected static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; + + /** + * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) + * @hide + */ + static final int TEXT_ALIGNMENT_MASK_SHIFT = 13; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_MASK = 0x00000007 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Array of text direction flags for mapping attribute "textAlignment" to correct + * flag value. + * @hide + */ + private static final int[] TEXT_ALIGNMENT_FLAGS = { + TEXT_ALIGNMENT_INHERIT << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_END << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_CENTER << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_END << TEXT_ALIGNMENT_MASK_SHIFT + }; + + /** + * Indicates whether the view text alignment has been resolved. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED = 0x00000008 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Bit shift to get the resolved text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + + /** + * Indicates whether if the view text alignment has been resolved to gravity + */ + public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + /* End of masks for mPrivateFlags2 */ @@ -1926,7 +2036,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * system UI to enter an unobtrusive "low profile" mode. * * <p>This is for use in games, book readers, video players, or any other - * "immersive" application where the usual system chrome is deemed too distracting. + * "immersive" application where the usual system chrome is deemed too distracting. * * <p>In low profile mode, the status bar and/or navigation icons may dim. * @@ -1942,7 +2052,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause * those to disappear. This is useful (in conjunction with the - * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and + * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN} * window flags) for displaying content using every last pixel on the display. * @@ -2339,7 +2449,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ private int mPrevWidth = -1; private int mPrevHeight = -1; - + /** * The degrees rotation around the vertical axis through the pivot point. */ @@ -2546,7 +2656,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ int mOldHeightMeasureSpec = Integer.MIN_VALUE; - private Drawable mBGDrawable; + private Drawable mBackground; private int mBackgroundResource; private boolean mBackgroundSizeChanged; @@ -2620,7 +2730,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Set to true when drawing cache is enabled and cannot be created. - * + * * @hide */ public boolean mCachingFailed; @@ -2841,7 +2951,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; // Set layout and text direction defaults mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) | - (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT); + (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) | + (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = -1; @@ -3222,6 +3333,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection]; } break; + case R.styleable.View_textAlignment: + // Clear any text alignment flag already set + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + // Set the text alignment flag depending on the value of the attribute + final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT); + mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment]; + break; } } @@ -3230,7 +3348,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal setOverScrollMode(overScrollMode); if (background != null) { - setBackgroundDrawable(background); + setBackground(background); } // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved @@ -3494,6 +3612,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + private ScrollabilityCache getScrollCache() { + initScrollCache(); + return mScrollCache; + } + /** * Set the position of the vertical scroll bar. Should be one of * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or @@ -3909,8 +4032,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p> * <strong>Note:</strong> When a View clears focus the framework is trying * to give focus to the first focusable View from the top. Hence, if this - * View is the first from the top that can take focus, then its focus will - * not be cleared nor will the focus change callback be invoked. + * View is the first from the top that can take focus, then all callbacks + * related to clearing focus will be invoked after wich the framework will + * give focus to this view. * </p> */ public void clearFocus() { @@ -3927,25 +4051,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onFocusChanged(false, 0, null); refreshDrawableState(); + + ensureInputFocusOnFirstFocusable(); } } - /** - * Called to clear the focus of a view that is about to be removed. - * Doesn't call clearChildFocus, which prevents this view from taking - * focus again before it has been removed from the parent - */ - void clearFocusForRemoval() { - if ((mPrivateFlags & FOCUSED) != 0) { - mPrivateFlags &= ~FOCUSED; - - onFocusChanged(false, 0, null); - refreshDrawableState(); - - // The view cleared focus and invoked the callbacks, so now is the - // time to give focus to the the first focusable from the top to - // ensure that the gain focus is announced after clear focus. - getRootView().requestFocus(FOCUS_FORWARD); + void ensureInputFocusOnFirstFocusable() { + View root = getRootView(); + if (root != null) { + // Find the first focusble from the top. + View next = root.focusSearch(FOCUS_FORWARD); + if (next != null) { + // Giving focus to the found focusable will not + // perform a search since we found a view that is + // guaranteed to be able to take focus. + next.requestFocus(FOCUS_FORWARD); + } } } @@ -4562,11 +4683,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Indicates whether this view is one of the set of scrollable containers in + * its window. + * + * @return whether this view is one of the set of scrollable containers in + * its window + * + * @attr ref android.R.styleable#View_isScrollContainer + */ + public boolean isScrollContainer() { + return (mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0; + } + + /** * Change whether this view is one of the set of scrollable containers in * its window. This will be used to determine whether the window can * resize or must pan when a soft input area is open -- scrollable * containers allow the window to use resize mode since the container * will appropriately shrink. + * + * @attr ref android.R.styleable#View_isScrollContainer */ public void setScrollContainer(boolean isScrollContainer) { if (isScrollContainer) { @@ -4898,7 +5034,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @RemotableViewMethod public void setVisibility(int visibility) { setFlags(visibility, VISIBILITY_MASK); - if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false); + if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); } /** @@ -5279,7 +5415,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter * the pressed state. * - * @see #setPressed(boolean) + * @see #setPressed(boolean) * @see #isClickable() * @see #setClickable(boolean) * @@ -5965,7 +6101,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Dispatch a hover event. * <p> - * Do not call this method directly. + * Do not call this method directly. * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead. * </p> * @@ -6152,7 +6288,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param visibility The new visibility of the window. * - * @see #onWindowVisibilityChanged(int) + * @see #onWindowVisibilityChanged(int) */ public void dispatchWindowVisibilityChanged(int visibility) { onWindowVisibilityChanged(visibility); @@ -6228,7 +6364,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param newConfig The new resource configuration. * - * @see #onConfigurationChanged(android.content.res.Configuration) + * @see #onConfigurationChanged(android.content.res.Configuration) */ public void dispatchConfigurationChanged(Configuration newConfig) { onConfigurationChanged(newConfig); @@ -7096,7 +7232,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { - if (mBGDrawable != null) { + if (mBackground != null) { mPrivateFlags &= ~SKIP_DRAW; mPrivateFlags |= ONLY_DRAWS_BACKGROUND; } else { @@ -7484,39 +7620,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * views are drawn) from the camera to this view. The camera's distance * affects 3D transformations, for instance rotations around the X and Y * axis. If the rotationX or rotationY properties are changed and this view is - * large (more than half the size of the screen), it is recommended to always + * large (more than half the size of the screen), it is recommended to always * use a camera distance that's greater than the height (X axis rotation) or * the width (Y axis rotation) of this view.</p> - * + * * <p>The distance of the camera from the view plane can have an affect on the * perspective distortion of the view when it is rotated around the x or y axis. * For example, a large distance will result in a large viewing angle, and there * will not be much perspective distortion of the view as it rotates. A short - * distance may cause much more perspective distortion upon rotation, and can + * distance may cause much more perspective distortion upon rotation, and can * also result in some drawing artifacts if the rotated view ends up partially * behind the camera (which is why the recommendation is to use a distance at * least as far as the size of the view, if the view is to be rotated.)</p> - * + * * <p>The distance is expressed in "depth pixels." The default distance depends * on the screen density. For instance, on a medium density display, the * default distance is 1280. On a high density display, the default distance * is 1920.</p> - * + * * <p>If you want to specify a distance that leads to visually consistent * results across various densities, use the following formula:</p> * <pre> * float scale = context.getResources().getDisplayMetrics().density; * view.setCameraDistance(distance * scale); * </pre> - * + * * <p>The density scale factor of a high density display is 1.5, * and 1920 = 1280 * 1.5.</p> - * + * * @param distance The distance in "depth pixels", if negative the opposite * value is used - * - * @see #setRotationX(float) - * @see #setRotationY(float) + * + * @see #setRotationX(float) + * @see #setRotationY(float) */ public void setCameraDistance(float distance) { invalidateViewProperty(true, false); @@ -7541,10 +7677,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * The degrees that the view is rotated around the pivot point. * - * @see #setRotation(float) + * @see #setRotation(float) * @see #getPivotX() * @see #getPivotY() - * + * * @return The degrees of rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7557,12 +7693,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * result in clockwise rotation. * * @param rotation The degrees of rotation. - * - * @see #getRotation() + * + * @see #getRotation() * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * @see #setRotationY(float) + * @see #setRotationX(float) + * @see #setRotationY(float) * * @attr ref android.R.styleable#View_rotation */ @@ -7586,8 +7722,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationY(float) - * + * @see #setRotationY(float) + * * @return The degrees of Y rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7599,18 +7735,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the vertical axis through the pivot point. * Increasing values result in counter-clockwise rotation from the viewpoint of looking * down the y axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationY The degrees of Y rotation. - * - * @see #getRotationY() + * + * @see #getRotationY() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationX(float) - * @see #setCameraDistance(float) + * @see #setRotationX(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationY */ @@ -7633,8 +7769,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * + * @see #setRotationX(float) + * * @return The degrees of X rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7646,18 +7782,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the horizontal axis through the pivot point. * Increasing values result in clockwise rotation from the viewpoint of looking down the * x axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationX The degrees of X rotation. - * - * @see #getRotationX() + * + * @see #getRotationX() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationY(float) - * @see #setCameraDistance(float) + * @see #setRotationY(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationX */ @@ -7762,6 +7898,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The x location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotX */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotX() { @@ -7807,6 +7945,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The y location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotY */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotY() { @@ -9022,7 +9162,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // - Background is opaque // - Doesn't have scrollbars or scrollbars are inside overlay - if (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE) { + if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) { mPrivateFlags |= OPAQUE_BACKGROUND; } else { mPrivateFlags &= ~OPAQUE_BACKGROUND; @@ -9070,7 +9210,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9094,7 +9234,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9168,7 +9308,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Removes the specified Runnable from the message queue.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9200,7 +9340,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> - * + * * @see #invalidate() */ public void postInvalidate() { @@ -9210,7 +9350,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Use this to invalidate the View from a non-UI thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9229,7 +9369,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate to happen on a subsequent cycle through the event * loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9248,7 +9388,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9358,6 +9498,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setHorizontalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isHorizontalFadingEdgeEnabled() { @@ -9373,6 +9514,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * horizontally * * @see #isHorizontalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) { @@ -9393,6 +9535,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setVerticalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isVerticalFadingEdgeEnabled() { @@ -9408,6 +9551,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * vertically * * @see #isVerticalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) { @@ -9550,6 +9694,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param fadeScrollbars wheter to enable fading * + * @attr ref android.R.styleable#View_fadeScrollbars */ public void setScrollbarFadingEnabled(boolean fadeScrollbars) { initScrollCache(); @@ -9567,12 +9712,86 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Returns true if scrollbars will fade when this view is not scrolling * * @return true if scrollbar fading is enabled + * + * @attr ref android.R.styleable#View_fadeScrollbars */ public boolean isScrollbarFadingEnabled() { return mScrollCache != null && mScrollCache.fadeScrollBars; } /** + * + * Returns the delay before scrollbars fade. + * + * @return the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public int getScrollBarDefaultDelayBeforeFade() { + return mScrollCache == null ? ViewConfiguration.getScrollDefaultDelay() : + mScrollCache.scrollBarDefaultDelayBeforeFade; + } + + /** + * Define the delay before scrollbars fade. + * + * @param scrollBarDefaultDelayBeforeFade - the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public void setScrollBarDefaultDelayBeforeFade(int scrollBarDefaultDelayBeforeFade) { + getScrollCache().scrollBarDefaultDelayBeforeFade = scrollBarDefaultDelayBeforeFade; + } + + /** + * + * Returns the scrollbar fade duration. + * + * @return the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public int getScrollBarFadeDuration() { + return mScrollCache == null ? ViewConfiguration.getScrollBarFadeDuration() : + mScrollCache.scrollBarFadeDuration; + } + + /** + * Define the scrollbar fade duration. + * + * @param scrollBarFadeDuration - the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public void setScrollBarFadeDuration(int scrollBarFadeDuration) { + getScrollCache().scrollBarFadeDuration = scrollBarFadeDuration; + } + + /** + * + * Returns the scrollbar size. + * + * @return the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public int getScrollBarSize() { + return mScrollCache == null ? ViewConfiguration.getScrollBarSize() : + mScrollCache.scrollBarSize; + } + + /** + * Define the scrollbar size. + * + * @param scrollBarSize - the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public void setScrollBarSize(int scrollBarSize) { + getScrollCache().scrollBarSize = scrollBarSize; + } + + /** * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or * inset. When inset, they add to the padding of the view. And the scrollbars * can be drawn inside the padding area or on the edge of the view. For example, @@ -9588,6 +9807,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ public void setScrollBarStyle(int style) { if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) { @@ -9604,6 +9825,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ @ViewDebug.ExportedProperty(mapping = { @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"), @@ -9989,6 +10212,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resolveLayoutDirection(); resolvePadding(); resolveTextDirection(); + resolveTextAlignment(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -10218,6 +10442,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mCurrentAnimation = null; resetResolvedLayoutDirection(); + resetResolvedTextAlignment(); } /** @@ -10348,9 +10573,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() */ public void saveHierarchyState(SparseArray<Parcelable> container) { dispatchSaveInstanceState(container); @@ -10363,9 +10588,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #onSaveInstanceState() */ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { @@ -10399,9 +10624,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns a Parcelable object containing the view's current dynamic * state, or null if there is nothing interesting to save. The * default implementation returns null. - * @see #onRestoreInstanceState(android.os.Parcelable) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) * @see #setSaveEnabled(boolean) */ protected Parcelable onSaveInstanceState() { @@ -10414,9 +10639,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously frozen states. * - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ public void restoreHierarchyState(SparseArray<Parcelable> container) { dispatchRestoreInstanceState(container); @@ -10430,9 +10655,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously saved state. * - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID) { @@ -10458,9 +10683,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param state The frozen state that had previously been returned by * {@link #onSaveInstanceState}. * - * @see #onSaveInstanceState() - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) */ protected void onRestoreInstanceState(Parcelable state) { mPrivateFlags |= SAVE_STATE_CALLED; @@ -10614,7 +10839,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #LAYER_TYPE_HARDWARE} * * @see #setLayerType(int, android.graphics.Paint) - * @see #buildLayer() + * @see #buildLayer() * @see #LAYER_TYPE_NONE * @see #LAYER_TYPE_SOFTWARE * @see #LAYER_TYPE_HARDWARE @@ -10627,14 +10852,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Forces this view's layer to be created and this view to be rendered * into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE}, * invoking this method will have no effect. - * + * * This method can for instance be used to render a view into its layer before * starting an animation. If this view is complex, rendering into the layer * before starting the animation will avoid skipping frames. - * + * * @throws IllegalStateException If this view is not attached to a window - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) */ public void buildLayer() { if (mLayerType == LAYER_TYPE_NONE) return; @@ -10656,7 +10881,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; } } - + // Make sure the HardwareRenderer.validate() was invoked before calling this method void flushLayer() { if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) { @@ -10675,7 +10900,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal !mAttachInfo.mHardwareRenderer.isEnabled()) { return null; } - + if (!mAttachInfo.mHardwareRenderer.validate()) return null; final int width = mRight - mLeft; @@ -10709,10 +10934,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Destroys this View's hardware layer if possible. - * + * * @return True if the layer was destroyed, false otherwise. - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) * @see #LAYER_TYPE_HARDWARE */ boolean destroyLayer(boolean valid) { @@ -10736,11 +10961,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Destroys all hardware rendering resources. This method is invoked * when the system needs to reclaim resources. Upon execution of this * method, you should free any OpenGL resources created by the view. - * + * * Note: you <strong>must</strong> call * <code>super.destroyHardwareResources()</code> when overriding * this method. - * + * * @hide */ protected void destroyHardwareResources() { @@ -11451,17 +11676,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (offsetRequired) top += getTopPaddingOffset(); return top; } - + /** * @hide * @param offsetRequired */ protected int getFadeHeight(boolean offsetRequired) { int padding = mPaddingTop; - if (offsetRequired) padding += getTopPaddingOffset(); + if (offsetRequired) padding += getTopPaddingOffset(); return mBottom - mTop - mPaddingBottom - padding; } - + /** * <p>Indicates whether this view is attached to a hardware accelerated * window or not.</p> @@ -11962,7 +12187,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int saveCount; if (!dirtyOpaque) { - final Drawable background = mBGDrawable; + final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; @@ -12036,7 +12261,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } final ScrollabilityCache scrollabilityCache = mScrollCache; - final float fadeHeight = scrollabilityCache.fadingEdgeLength; + final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; // clip the fade length if top and bottom fades overlap @@ -12143,8 +12368,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha * should be set to 0xFF. * - * @see #setVerticalFadingEdgeEnabled(boolean) - * @see #setHorizontalFadingEdgeEnabled(boolean) + * @see #setVerticalFadingEdgeEnabled(boolean) + * @see #setHorizontalFadingEdgeEnabled(boolean) * * @return The known solid color background for this view, or 0 if the color may vary */ @@ -12493,7 +12718,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param who the Drawable to query */ public int getResolvedLayoutDirection(Drawable who) { - return (who == mBGDrawable) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; + return (who == mBackground) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; } /** @@ -12512,11 +12737,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return boolean If true than the Drawable is being displayed in the * view; else false and it is not allowed to animate. * - * @see #unscheduleDrawable(android.graphics.drawable.Drawable) - * @see #drawableStateChanged() + * @see #unscheduleDrawable(android.graphics.drawable.Drawable) + * @see #drawableStateChanged() */ protected boolean verifyDrawable(Drawable who) { - return who == mBGDrawable; + return who == mBackground; } /** @@ -12526,10 +12751,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Be sure to call through to the superclass when overriding this * function. * - * @see Drawable#setState(int[]) + * @see Drawable#setState(int[]) */ protected void drawableStateChanged() { - Drawable d = mBGDrawable; + Drawable d = mBackground; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } @@ -12559,9 +12784,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The current drawable state * - * @see Drawable#setState(int[]) - * @see #drawableStateChanged() - * @see #onCreateDrawableState(int) + * @see Drawable#setState(int[]) + * @see #drawableStateChanged() + * @see #onCreateDrawableState(int) */ public final int[] getDrawableState() { if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) { @@ -12586,7 +12811,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns an array holding the current {@link Drawable} state of * the view. * - * @see #mergeDrawableStates(int[], int[]) + * @see #mergeDrawableStates(int[], int[]) */ protected int[] onCreateDrawableState(int extraSpace) { if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE && @@ -12662,7 +12887,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return As a convenience, the <var>baseState</var> array you originally * passed into the function is returned. * - * @see #onCreateDrawableState(int) + * @see #onCreateDrawableState(int) */ protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { final int N = baseState.length; @@ -12679,8 +12904,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * on all Drawable objects associated with this view. */ public void jumpDrawablesToCurrentState() { - if (mBGDrawable != null) { - mBGDrawable.jumpToCurrentState(); + if (mBackground != null) { + mBackground.jumpToCurrentState(); } } @@ -12690,10 +12915,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @RemotableViewMethod public void setBackgroundColor(int color) { - if (mBGDrawable instanceof ColorDrawable) { - ((ColorDrawable) mBGDrawable).setColor(color); + if (mBackground instanceof ColorDrawable) { + ((ColorDrawable) mBackground).setColor(color); } else { - setBackgroundDrawable(new ColorDrawable(color)); + setBackground(new ColorDrawable(color)); } } @@ -12701,6 +12926,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Set the background to a given resource. The resource should refer to * a Drawable object or 0 to remove the background. * @param resid The identifier of the resource. + * * @attr ref android.R.styleable#View_background */ @RemotableViewMethod @@ -12713,7 +12939,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (resid != 0) { d = mResources.getDrawable(resid); } - setBackgroundDrawable(d); + setBackground(d); mBackgroundResource = resid; } @@ -12725,11 +12951,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * touched. If setting the padding is desired, please use * {@link #setPadding(int, int, int, int)}. * - * @param d The Drawable to use as the background, or null to remove the + * @param background The Drawable to use as the background, or null to remove the * background */ - public void setBackgroundDrawable(Drawable d) { - if (d == mBGDrawable) { + public void setBackground(Drawable background) { + setBackgroundDrawable(background); + } + + /** + * @deprecated use {@link #setBackground(Drawable)} instead + */ + @Deprecated + public void setBackgroundDrawable(Drawable background) { + if (background == mBackground) { return; } @@ -12741,19 +12975,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Regardless of whether we're setting a new background or not, we want * to clear the previous drawable. */ - if (mBGDrawable != null) { - mBGDrawable.setCallback(null); - unscheduleDrawable(mBGDrawable); + if (mBackground != null) { + mBackground.setCallback(null); + unscheduleDrawable(mBackground); } - if (d != null) { + if (background != null) { Rect padding = sThreadLocal.get(); if (padding == null) { padding = new Rect(); sThreadLocal.set(padding); } - if (d.getPadding(padding)) { - switch (d.getResolvedLayoutDirectionSelf()) { + if (background.getPadding(padding)) { + switch (background.getResolvedLayoutDirectionSelf()) { case LAYOUT_DIRECTION_RTL: setPadding(padding.right, padding.top, padding.left, padding.bottom); break; @@ -12765,17 +12999,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or // if it has a different minimum size, we should layout again - if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || - mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { + if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || + mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true; } - d.setCallback(this); - if (d.isStateful()) { - d.setState(getDrawableState()); + background.setCallback(this); + if (background.isStateful()) { + background.setState(getDrawableState()); } - d.setVisible(getVisibility() == VISIBLE, false); - mBGDrawable = d; + background.setVisible(getVisibility() == VISIBLE, false); + mBackground = background; if ((mPrivateFlags & SKIP_DRAW) != 0) { mPrivateFlags &= ~SKIP_DRAW; @@ -12784,7 +13018,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } else { /* Remove the background */ - mBGDrawable = null; + mBackground = null; if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { /* @@ -12820,10 +13054,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Gets the background drawable + * * @return The drawable used as the background for this view, if any. + * + * @see #setBackground(Drawable) + * + * @attr ref android.R.styleable#View_background */ public Drawable getBackground() { - return mBGDrawable; + return mBackground; } /** @@ -13364,8 +13603,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * number. * * @see #NO_ID - * @see #getId() - * @see #findViewById(int) + * @see #getId() + * @see #findViewById(int) * * @param id a number used to identify the view * @@ -13404,8 +13643,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return a positive integer used to identify the view or {@link #NO_ID} * if the view has no ID * - * @see #setId(int) - * @see #findViewById(int) + * @see #setId(int) + * @see #findViewById(int) * @attr ref android.R.styleable#View_id */ @ViewDebug.CapturedViewProperty @@ -13917,16 +14156,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum height of the view. */ protected int getSuggestedMinimumHeight() { - int suggestedMinHeight = mMinHeight; + return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); - if (mBGDrawable != null) { - final int bgMinHeight = mBGDrawable.getMinimumHeight(); - if (suggestedMinHeight < bgMinHeight) { - suggestedMinHeight = bgMinHeight; - } - } - - return suggestedMinHeight; } /** @@ -13941,16 +14172,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum width of the view. */ protected int getSuggestedMinimumWidth() { - int suggestedMinWidth = mMinWidth; - - if (mBGDrawable != null) { - final int bgMinWidth = mBGDrawable.getMinimumWidth(); - if (suggestedMinWidth < bgMinWidth) { - suggestedMinWidth = bgMinWidth; - } - } + return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); + } - return suggestedMinWidth; + /** + * Returns the minimum height of the view. + * + * @return the minimum height the view will try to be. + * + * @see #setMinimumHeight(int) + * + * @attr ref android.R.styleable#View_minHeight + */ + public int getMinimumHeight() { + return mMinHeight; } /** @@ -13959,9 +14194,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available height). * * @param minHeight The minimum height the view will try to be. + * + * @see #getMinimumHeight() + * + * @attr ref android.R.styleable#View_minHeight */ public void setMinimumHeight(int minHeight) { mMinHeight = minHeight; + requestLayout(); + } + + /** + * Returns the minimum width of the view. + * + * @return the minimum width the view will try to be. + * + * @see #setMinimumWidth(int) + * + * @attr ref android.R.styleable#View_minWidth + */ + public int getMinimumWidth() { + return mMinWidth; } /** @@ -13970,9 +14223,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available width). * * @param minWidth The minimum width the view will try to be. + * + * @see #getMinimumWidth() + * + * @attr ref android.R.styleable#View_minWidth */ public void setMinimumWidth(int minWidth) { mMinWidth = minWidth; + requestLayout(); + } /** @@ -14091,11 +14350,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal getLocationInWindow(location); region.op(location[0], location[1], location[0] + mRight - mLeft, location[1] + mBottom - mTop, Region.Op.DIFFERENCE); - } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) { + } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) { // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable // exists, so we remove the background drawable's non-transparent // parts from this transparent region. - applyDrawableToTransparentRegion(mBGDrawable, region); + applyDrawableToTransparentRegion(mBackground, region); } } return true; @@ -14766,7 +15025,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ @ViewDebug.ExportedProperty(category = "text", mapping = { @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @@ -14790,7 +15049,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public void setTextDirection(int textDirection) { if (getTextDirection() != textDirection) { @@ -14799,6 +15058,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resetResolvedTextDirection(); // Set the new text direction mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK); + // Refresh requestLayout(); invalidate(true); } @@ -14818,7 +15078,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public int getResolvedTextDirection() { // The text direction will be resolved only if needed @@ -14927,6 +15187,199 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public void onResolvedTextDirectionReset() { } + /** + * Return the value specifying the text alignment or policy that was set with + * {@link #setTextAlignment(int)}. + * + * @return the defined text alignment. It can be one of: + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getTextAlignment() { + return (mPrivateFlags2 & TEXT_ALIGNMENT_MASK) >> TEXT_ALIGNMENT_MASK_SHIFT; + } + + /** + * Set the text alignment. + * + * @param textAlignment The text alignment to set. Should be one of + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + * + * @attr ref android.R.styleable#View_textAlignment + */ + public void setTextAlignment(int textAlignment) { + if (textAlignment != getTextAlignment()) { + // Reset the current and resolved text alignment + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + resetResolvedTextAlignment(); + // Set the new text alignment + mPrivateFlags2 |= ((textAlignment << TEXT_ALIGNMENT_MASK_SHIFT) & TEXT_ALIGNMENT_MASK); + // Refresh + requestLayout(); + invalidate(true); + } + } + + /** + * Return the resolved text alignment. + * + * The resolved text alignment. This needs resolution if the value is + * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is + * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view. + * + * @return the resolved text alignment. Returns one of: + * + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getResolvedTextAlignment() { + // If text alignment is not resolved, then resolve it + if ((mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED) != TEXT_ALIGNMENT_RESOLVED) { + resolveTextAlignment(); + } + return (mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED_MASK) >> TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + } + + /** + * Resolve the text alignment. Will call {@link View#onResolvedTextAlignmentChanged} when + * resolution is done. + */ + public void resolveTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + + if (hasRtlSupport()) { + // Set resolved text alignment flag depending on text alignment flag + final int textAlignment = getTextAlignment(); + switch (textAlignment) { + case TEXT_ALIGNMENT_INHERIT: + // Check if we can resolve the text alignment + if (canResolveLayoutDirection() && mParent instanceof View) { + View view = (View) mParent; + + final int parentResolvedTextAlignment = view.getResolvedTextAlignment(); + switch (parentResolvedTextAlignment) { + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as the parent resolved + // text alignment + mPrivateFlags2 |= + (parentResolvedTextAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } + else { + // We cannot do the resolution if there is no parent so use the default + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + break; + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as text alignment + mPrivateFlags2 |= (textAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } else { + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + + // Set the resolved + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED; + onResolvedTextAlignmentChanged(); + } + + /** + * Check if text alignment resolution can be done. + * + * @return true if text alignment resolution can be done otherwise return false. + */ + public boolean canResolveTextAlignment() { + switch (getTextAlignment()) { + case TEXT_DIRECTION_INHERIT: + return (mParent != null); + default: + return true; + } + } + + /** + * Called when text alignment has been resolved. Subclasses that care about text alignment + * resolution should override this method. + * + * The default implementation does nothing. + */ + public void onResolvedTextAlignmentChanged() { + } + + /** + * Reset resolved text alignment. Text alignment can be resolved with a call to + * getResolvedTextAlignment(). Will call {@link View#onResolvedTextAlignmentReset} when + * reset is done. + */ + public void resetResolvedTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + onResolvedTextAlignmentReset(); + } + + /** + * Called when text alignment is reset. Subclasses that care about text alignment reset should + * override this method and do a reset of the text alignment of their children. The default + * implementation does nothing. + */ + public void onResolvedTextAlignmentReset() { + } + // // Properties // @@ -15419,7 +15872,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * visibility. This reports <strong>global</strong> changes to the system UI * state, not just what the application is requesting. * - * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) + * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ public interface OnSystemUiVisibilityChangeListener { /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d5c783f..121b544 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3375,7 +3375,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean clearChildFocus = false; if (view == mFocused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = true; } @@ -3398,6 +3398,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus) { clearChildFocus(view); + ensureInputFocusOnFirstFocusable(); } } @@ -3450,7 +3451,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3474,6 +3475,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -3519,7 +3521,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3542,6 +3544,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -5056,6 +5059,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public void onResolvedTextAlignmentReset() { + // Take care of resetting the children resolution too + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getTextAlignment() == TEXT_ALIGNMENT_INHERIT) { + child.resetResolvedTextAlignment(); + } + } + } + /** * Return true if the pressed state should be delayed for children or descendants of this * ViewGroup. Generally, this should be done for containers that can scroll, such as a List. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d72f3b7..899fb32 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2039,9 +2039,10 @@ public final class ViewRootImpl implements ViewParent, scrollToRectOrFocus(null, false); - if (mAttachInfo.mViewScrollChanged) { - mAttachInfo.mViewScrollChanged = false; - mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo.mViewScrollChanged) { + attachInfo.mViewScrollChanged = false; + attachInfo.mTreeObserver.dispatchOnScrollChanged(); } int yoff; @@ -2056,8 +2057,8 @@ public final class ViewRootImpl implements ViewParent, fullRedrawNeeded = true; } - final float appScale = mAttachInfo.mApplicationScale; - final boolean scalingRequired = mAttachInfo.mScalingRequired; + final float appScale = attachInfo.mApplicationScale; + final boolean scalingRequired = attachInfo.mScalingRequired; int resizeAlpha = 0; if (mResizeBuffer != null) { @@ -2086,7 +2087,7 @@ public final class ViewRootImpl implements ViewParent, } if (fullRedrawNeeded) { - mAttachInfo.mIgnoreDirtyState = true; + attachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } @@ -2099,8 +2100,10 @@ public final class ViewRootImpl implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } + attachInfo.mTreeObserver.dispatchOnDraw(); + if (!dirty.isEmpty() || mIsAnimating) { - if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { + if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; @@ -2111,154 +2114,164 @@ public final class ViewRootImpl implements ViewParent, mPreviousDirty.set(dirty); dirty.setEmpty(); - if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, + if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty)) { mPreviousDirty.set(0, 0, mWidth, mHeight); } - } else { - // Draw with software renderer. - Canvas canvas; - try { - int left = dirty.left; - int top = dirty.top; - int right = dirty.right; - int bottom = dirty.bottom; - - final long lockCanvasStartTime; - if (ViewDebug.DEBUG_LATENCY) { - lockCanvasStartTime = System.nanoTime(); - } - - canvas = mSurface.lockCanvas(dirty); + } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { + return; + } + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " - + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); - } + if (animating) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + } - if (left != dirty.left || top != dirty.top || right != dirty.right || - bottom != dirty.bottom) { - mAttachInfo.mIgnoreDirtyState = true; - } + /** + * @return true if drawing was succesfull, false if an error occurred + */ + private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, + boolean scalingRequired, Rect dirty) { - // TODO: Do this in native - canvas.setDensity(mDensity); - } catch (Surface.OutOfResourcesException e) { - Log.e(TAG, "OutOfResourcesException locking surface", e); - try { - if (!sWindowSession.outOfMemory(mWindow)) { - Slog.w(TAG, "No processes killed for memory; killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { - } - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } catch (IllegalArgumentException e) { - Log.e(TAG, "IllegalArgumentException locking surface", e); - // Don't assume this is due to out of memory, it could be - // something else, and if it is something else then we could - // kill stuff (or ourself) for no reason. - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } + // Draw with software renderer. + Canvas canvas; + try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; - try { - if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" - + canvas.getWidth() + ", h=" + canvas.getHeight()); - //canvas.drawARGB(255, 255, 0, 0); - } + final long lockCanvasStartTime; + if (ViewDebug.DEBUG_LATENCY) { + lockCanvasStartTime = System.nanoTime(); + } - long startTime = 0L; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - startTime = SystemClock.elapsedRealtime(); - } + canvas = mSurface.lockCanvas(dirty); - // If this bitmap's format includes an alpha channel, we - // need to clear it before drawing so that the child will - // properly re-composite its drawing on a transparent - // background. This automatically respects the clip/dirty region - // or - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " + + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); + } - dirty.setEmpty(); - mIsAnimating = false; - mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + attachInfo.mIgnoreDirtyState = true; + } - if (DEBUG_DRAW) { - Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + cxt.getResources().getDisplayMetrics() + - ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); - } - try { - canvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(canvas); - } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); - mAttachInfo.mSetIgnoreDirtyState = false; + // TODO: Do this in native + canvas.setDensity(mDensity); + } catch (Surface.OutOfResourcesException e) { + Log.e(TAG, "OutOfResourcesException locking surface", e); + try { + if (!sWindowSession.outOfMemory(mWindow)) { + Slog.w(TAG, "No processes killed for memory; killing self"); + Process.killProcess(Process.myPid()); + } + } catch (RemoteException ex) { + } + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } catch (IllegalArgumentException e) { + Log.e(TAG, "IllegalArgumentException locking surface", e); + // Don't assume this is due to out of memory, it could be + // something else, and if it is something else then we could + // kill stuff (or ourself) for no reason. + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } - final long drawStartTime; - if (ViewDebug.DEBUG_LATENCY) { - drawStartTime = System.nanoTime(); - } + try { + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } - mView.draw(canvas); + long startTime = 0L; + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + startTime = SystemClock.elapsedRealtime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " - + ((now - drawStartTime) * 0.000001f) + "ms"); - } - } finally { - if (!mAttachInfo.mSetIgnoreDirtyState) { - // Only clear the flag if it was not set during the mView.draw() call - mAttachInfo.mIgnoreDirtyState = false; - } - } + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } - if (false && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } + dirty.setEmpty(); + mIsAnimating = false; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.mPrivateFlags |= View.DRAWN; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); - } - } finally { - final long unlockCanvasAndPostStartTime; - if (ViewDebug.DEBUG_LATENCY) { - unlockCanvasAndPostStartTime = System.nanoTime(); - } + if (DEBUG_DRAW) { + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); + } + try { + canvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired + ? DisplayMetrics.DENSITY_DEVICE : 0); + attachInfo.mSetIgnoreDirtyState = false; - surface.unlockCanvasAndPost(canvas); + final long drawStartTime; + if (ViewDebug.DEBUG_LATENCY) { + drawStartTime = System.nanoTime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " - + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); - } + mView.draw(canvas); - if (LOCAL_LOGV) { - Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " + + ((now - drawStartTime) * 0.000001f) + "ms"); + } + } finally { + if (!attachInfo.mSetIgnoreDirtyState) { + // Only clear the flag if it was not set during the mView.draw() call + attachInfo.mIgnoreDirtyState = false; } } - } - if (animating) { - mFullRedrawNeeded = true; - scheduleTraversals(); + if (false && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } + + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } + } finally { + final long unlockCanvasAndPostStartTime; + if (ViewDebug.DEBUG_LATENCY) { + unlockCanvasAndPostStartTime = System.nanoTime(); + } + + surface.unlockCanvasAndPost(canvas); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " + + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); + } + + if (LOCAL_LOGV) { + Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + } } + return true; } void invalidateDisplayLists() { @@ -3830,30 +3843,33 @@ public final class ViewRootImpl implements ViewParent, if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mAdded) { - mAdded = false; dispatchDetachedFromWindow(); } if (mAdded && !mFirst) { destroyHardwareRenderer(); - int viewVisibility = mView.getVisibility(); - boolean viewVisibilityChanged = mViewVisibility != viewVisibility; - if (mWindowAttributesChanged || viewVisibilityChanged) { - // If layout params have been changed, first give them - // to the window manager to make sure it has the correct - // animation info. - try { - if ((relayoutWindow(mWindowAttributes, viewVisibility, false) - & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { - sWindowSession.finishDrawing(mWindow); + if (mView != null) { + int viewVisibility = mView.getVisibility(); + boolean viewVisibilityChanged = mViewVisibility != viewVisibility; + if (mWindowAttributesChanged || viewVisibilityChanged) { + // If layout params have been changed, first give them + // to the window manager to make sure it has the correct + // animation info. + try { + if ((relayoutWindow(mWindowAttributes, viewVisibility, false) + & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { + sWindowSession.finishDrawing(mWindow); + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } + + mSurface.release(); } - - mSurface.release(); } + + mAdded = false; } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 7fd3389..1c5d436 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -38,6 +38,7 @@ public final class ViewTreeObserver { private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -90,6 +91,27 @@ public final class ViewTreeObserver { } /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnDrawListener { + /** + * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, + * views cannot be modified in any way.</p> + * + * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the + * current drawing pass.</p> + * + * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> + * from this method.</p> + * + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public void onDraw(); + } + + /** * Interface definition for a callback to be invoked when the touch mode changes. */ public interface OnTouchModeChangeListener { @@ -171,11 +193,7 @@ public final class ViewTreeObserver { public void setTouchableInsets(int val) { mTouchableInsets = val; } - - public int getTouchableInsets() { - return mTouchableInsets; - } - + int mTouchableInsets; void reset() { @@ -184,29 +202,28 @@ public final class ViewTreeObserver { touchableRegion.setEmpty(); mTouchableInsets = TOUCHABLE_INSETS_FRAME; } - + + @Override + public int hashCode() { + int result = contentInsets != null ? contentInsets.hashCode() : 0; + result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); + result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); + result = 31 * result + mTouchableInsets; + return result; + } + @Override public boolean equals(Object o) { - try { - if (o == null) { - return false; - } - InternalInsetsInfo other = (InternalInsetsInfo)o; - if (mTouchableInsets != other.mTouchableInsets) { - return false; - } - if (!contentInsets.equals(other.contentInsets)) { - return false; - } - if (!visibleInsets.equals(other.visibleInsets)) { - return false; - } - return touchableRegion.equals(other.touchableRegion); - } catch (ClassCastException e) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InternalInsetsInfo other = (InternalInsetsInfo)o; + return mTouchableInsets == other.mTouchableInsets && + contentInsets.equals(other.contentInsets) && + visibleInsets.equals(other.visibleInsets) && + touchableRegion.equals(other.touchableRegion); } - + void set(InternalInsetsInfo other) { contentInsets.set(other.contentInsets); visibleInsets.set(other.visibleInsets); @@ -420,6 +437,44 @@ public final class ViewTreeObserver { } /** + * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnDrawListener(OnDrawListener listener) { + checkIsAlive(); + + if (mOnDrawListeners == null) { + mOnDrawListeners = new ArrayList<OnDrawListener>(); + } + + mOnDrawListeners.add(listener); + } + + /** + * <p>Remove a previously installed pre-draw callback.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnDrawListener(OnDrawListener) + */ + public void removeOnDrawListener(OnDrawListener victim) { + checkIsAlive(); + if (mOnDrawListeners == null) { + return; + } + mOnDrawListeners.remove(victim); + } + + /** * Register a callback to be invoked when a view has been scrolled. * * @param listener The callback to add @@ -601,6 +656,7 @@ public final class ViewTreeObserver { * * @return True if the current draw should be canceled and resceduled, false otherwise. */ + @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { // NOTE: we *must* clone the listener list to perform the dispatching. // The clone is a safe guard against listeners that @@ -619,6 +675,19 @@ public final class ViewTreeObserver { } /** + * Notifies registered listeners that the drawing pass is about to start. + */ + public final void dispatchOnDraw() { + if (mOnDrawListeners != null) { + final ArrayList<OnDrawListener> listeners = mOnDrawListeners; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; ++i) { + listeners.get(i).onDraw(); + } + } + } + + /** * Notifies registered listeners that the touch mode has changed. * * @param inTouchMode True if the touch mode is now enabled, false otherwise. diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 94b46fc..aa3d8d3 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -33,7 +33,7 @@ import java.util.Locale; */ public class WebSettingsClassic extends WebSettings { // TODO: Keep this up to date - private static final String PREVIOUS_VERSION = "4.0.3"; + private static final String PREVIOUS_VERSION = "4.0.4"; // WebView associated with this WebSettings. private WebViewClassic mWebView; diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index 0370049..9c9eb4b 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -25,6 +25,7 @@ import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -247,6 +248,7 @@ public class Chronometer extends TextView { } } setText(text); + Slog.v("Chronometer", "updateText: sec=" + seconds + " mFormat=" + mFormat + " text=" + text); } private void updateRunning() { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d2a1755..9867e47 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5340,24 +5340,63 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } + @Override + public void onResolvedLayoutDirectionReset() { + if (mLayoutAlignment != null) { + int resolvedTextAlignment = getResolvedTextAlignment(); + if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || + resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { + mLayoutAlignment = null; + } + } + } + private Layout.Alignment getLayoutAlignment() { if (mLayoutAlignment == null) { - switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { - case Gravity.START: + int textAlign = getResolvedTextAlignment(); + switch (textAlign) { + case TEXT_ALIGNMENT_GRAVITY: + switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { + case Gravity.START: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + case Gravity.END: + mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case Gravity.LEFT: + mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + break; + case Gravity.RIGHT: + mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + break; + case Gravity.CENTER_HORIZONTAL: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + break; + default: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + } + break; + case TEXT_ALIGNMENT_TEXT_START: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; - case Gravity.END: + case TEXT_ALIGNMENT_TEXT_END: mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; break; - case Gravity.LEFT: - mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + case TEXT_ALIGNMENT_CENTER: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; break; - case Gravity.RIGHT: - mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + case TEXT_ALIGNMENT_VIEW_START: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; break; - case Gravity.CENTER_HORIZONTAL: - mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + case TEXT_ALIGNMENT_VIEW_END: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; break; + case TEXT_ALIGNMENT_INHERIT: + // This should never happen as we have already resolved the text alignment + // but better safe than sorry so we just fall through default: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 5e73a5f..3c27caf 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -236,7 +236,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, 0, 0, width, height, bitmap);
}
- return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL);
+ return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL);
}
static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,
@@ -248,7 +248,7 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src, return NULL;
}
- return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL);
+ return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL, NULL);
}
static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {
@@ -407,7 +407,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { bitmap->unlockPixels();
blob.release();
- return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, density);
+ return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, NULL, density);
}
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
@@ -485,7 +485,7 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, env->ReleaseIntArrayElements(offsetXY, array, 0);
}
- return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL);
+ return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL, NULL);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index dcd1d28..dd59444 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -35,6 +35,7 @@ jfieldID gOptions_mimeFieldID; jfieldID gOptions_mCancelID; jfieldID gOptions_bitmapFieldID; jfieldID gBitmap_nativeBitmapFieldID; +jfieldID gBitmap_layoutBoundsFieldID; #if 0 #define TRACE_BITMAP(code) code @@ -276,7 +277,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } jbyteArray ninePatchChunk = NULL; - if (peeker.fPatchIsValid) { + if (peeker.fPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } @@ -296,6 +297,18 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } + jintArray layoutBounds = NULL; + if (peeker.fLayoutBounds != NULL) { + layoutBounds = env->NewIntArray(4); + if (layoutBounds == NULL) { + return nullObjectReturn("layoutBounds == null"); + } + + env->SetIntArrayRegion(layoutBounds, 0, 4, (jint*) peeker.fLayoutBounds); + if (javaBitmap != NULL) { + env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds); + } + } // detach bitmap from its autodeleter, since we want to own it now adb.detach(); @@ -321,7 +334,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } if (padding) { - if (peeker.fPatchIsValid) { + if (peeker.fPatch != NULL) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); @@ -350,7 +363,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } // now create the java bitmap return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(), - isMutable, ninePatchChunk); + isMutable, ninePatchChunk, layoutBounds, -1); } static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, @@ -576,7 +589,7 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { jclass bitmap_class = env->FindClass("android/graphics/Bitmap"); SkASSERT(bitmap_class); gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I"); - + gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mLayoutBounds", "[I"); int ret = AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory$Options", gOptionsMethods, diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 682877a..dd8e84f 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -244,7 +244,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); jbyteArray buff = allocator->getStorageObjAndReset(); - return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1); + return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, NULL, -1); } static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index a1d41ee..d4c7600 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -345,14 +345,14 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) /////////////////////////////////////////////////////////////////////////////////////////// jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, int density) + bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, + int density) { SkASSERT(bitmap); SkASSERT(bitmap->pixelRef()); - jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), - buffer, isMutable, ninepatch, density); + buffer, isMutable, ninepatch, layoutbounds, density); hasException(env); // For the side effect of logging. return obj; } @@ -360,7 +360,7 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buff jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density) { - return createBitmap(env, bitmap, NULL, isMutable, ninepatch, density); + return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density); } @@ -587,7 +587,7 @@ int register_android_graphics_Graphics(JNIEnv* env) gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I"); gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", - "(I[BZ[BI)V"); + "(I[BZ[B[II)V"); gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder"); gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index cc32f44..c5b06f5 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -53,7 +53,8 @@ public: storage array (may be null). */ static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, int density = -1); + bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, + int density = -1); static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density = -1); diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp index 365d985..df996af 100644 --- a/core/jni/android/graphics/NinePatchPeeker.cpp +++ b/core/jni/android/graphics/NinePatchPeeker.cpp @@ -31,14 +31,11 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { // this relies on deserialization being done in place Res_png_9patch::deserialize(patchNew); patchNew->fileToDevice(); - if (fPatchIsValid) { - free(fPatch); - } + free(fPatch); fPatch = patchNew; //printf("9patch: (%d,%d)-(%d,%d)\n", // fPatch.sizeLeft, fPatch.sizeTop, // fPatch.sizeRight, fPatch.sizeBottom); - fPatchIsValid = true; // now update our host to force index or 32bit config // 'cause we don't want 565 predithered, since as a 9patch, we know @@ -52,8 +49,9 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { SkBitmap::kARGB_8888_Config, }; fHost->setPrefConfigTable(gNo565Pref); - } else { - fPatch = NULL; + } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) { + fLayoutBounds = new int[4]; + memcpy(fLayoutBounds, data, sizeof(int) * 4); } return true; // keep on decoding } diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h index 207536c..10d268a 100644 --- a/core/jni/android/graphics/NinePatchPeeker.h +++ b/core/jni/android/graphics/NinePatchPeeker.h @@ -28,17 +28,17 @@ public: NinePatchPeeker(SkImageDecoder* host) { // the host lives longer than we do, so a raw ptr is safe fHost = host; - fPatchIsValid = false; + fPatch = NULL; + fLayoutBounds = NULL; } ~NinePatchPeeker() { - if (fPatchIsValid) { - free(fPatch); - } + free(fPatch); + delete fLayoutBounds; } - bool fPatchIsValid; Res_png_9patch* fPatch; + int *fLayoutBounds; virtual bool peek(const char tag[], const void* data, size_t length); }; diff --git a/core/res/res/layout/notification_template_base.xml b/core/res/res/layout/notification_template_base.xml index 93843fd..b9710d6 100644 --- a/core/res/res/layout/notification_template_base.xml +++ b/core/res/res/layout/notification_template_base.xml @@ -53,15 +53,21 @@ android:fadingEdge="horizontal" android:layout_weight="1" /> - <DateTimeView android:id="@+id/time" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + <ViewStub android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="0" - android:singleLine="true" - android:gravity="center" - android:paddingLeft="8dp" + android:visibility="gone" + android:layout="@layout/notification_template_part_time" + /> + <ViewStub android:id="@+id/chronometer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:visibility="gone" + android:layout="@layout/notification_template_part_chronometer" /> </LinearLayout> <TextView android:id="@+id/text2" diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml new file mode 100644 index 0000000..382b0e4 --- /dev/null +++ b/core/res/res/layout/notification_template_part_chronometer.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<Chronometer android:id="@+id/chronometer" xmlns:android="http://schemas.android.com/apk/res/android" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml new file mode 100644 index 0000000..410fcaf --- /dev/null +++ b/core/res/res/layout/notification_template_part_time.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<DateTimeView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 438c141..2b27585 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2079,6 +2079,7 @@ <!-- Locale --> <enum name="locale" value="3" /> </attr> + <!-- Direction of the text. A heuristic is used to determine the resolved text direction of paragraphs. --> <attr name="textDirection" format="integer"> @@ -2099,6 +2100,29 @@ <!-- The paragraph direction is coming from the system Locale. --> <enum name="locale" value="5" /> </attr> + + <!-- Alignment of the text. A heuristic is used to determine the resolved + text alignment. --> + <attr name="textAlignment" format="integer"> + <!-- Default --> + <enum name="inherit" value="0" /> + <!-- Default for the root view. The gravity determines the alignment, ALIGN_NORMAL, + ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s + text direction --> + <enum name="gravity" value="1" /> + <!-- Align to the start of the paragraph, e.g. ALIGN_NORMAL. --> + <enum name="textStart" value="2" /> + <!-- Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. --> + <enum name="textEnd" value="3" /> + <!-- Center the paragraph, e.g. ALIGN_CENTER. --> + <enum name="center" value="4" /> + <!-- Align to the start of the view, which is ALIGN_LEFT if the view’s resolved + layoutDirection is LTR, and ALIGN_RIGHT otherwise. --> + <enum name="viewStart" value="5" /> + <!-- Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved + layoutDirection is LTR, and ALIGN_LEFT otherwise --> + <enum name="viewEnd" value="6" /> + </attr> </declare-styleable> <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 565d7d2..f010a02 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -202,6 +202,7 @@ <java-symbol type="id" name="action2" /> <java-symbol type="id" name="big_picture" /> <java-symbol type="id" name="big_text" /> + <java-symbol type="id" name="chronometer" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -1079,6 +1080,8 @@ <java-symbol type="layout" name="notification_intruder_content" /> <java-symbol type="layout" name="notification_template_base" /> <java-symbol type="layout" name="notification_template_big_picture" /> + <java-symbol type="layout" name="notification_template_part_time" /> + <java-symbol type="layout" name="notification_template_part_chronometer" /> <java-symbol type="anim" name="slide_in_child_bottom" /> <java-symbol type="anim" name="slide_in_right" /> @@ -3561,6 +3564,7 @@ <public type="attr" name="supportsRtl" id="0x010103a8" /> <public type="attr" name="textDirection"/> + <public type="attr" name="textAlignment"/> <public type="attr" name="layoutDirection" /> diff --git a/docs/html/guide/developing/testing/testing_otheride.jd b/docs/html/guide/developing/testing/testing_otheride.jd index 93af979..7745ae7 100644 --- a/docs/html/guide/developing/testing/testing_otheride.jd +++ b/docs/html/guide/developing/testing/testing_otheride.jd @@ -209,7 +209,7 @@ $ android create test-project -m ../HelloAndroid -n HelloAndroidTest -p HelloAnd <p> To update a test project with the <code>android</code> tool, enter: </p> -<pre>android update-test-project -m <main_path> -p <test_path></pre> +<pre>android update test-project -m <main_path> -p <test_path></pre> <table> <tr> diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index bbbe6fb..5297c23 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -299,6 +299,31 @@ class="new"> new!</span></span> </li> </ul> </li> + + <li class="toggle-list"> + <div><a href="<?cs var:toroot ?>training/displaying-bitmaps/index.html"> + <span class="en">Displaying Bitmaps Efficiently<span class="new"> new!</span></span> + </a> + </div> + <ul> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/load-bitmap.html"> + <span class="en">Loading Large Bitmaps Efficiently</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/process-bitmap.html"> + <span class="en">Processing Bitmaps Off the UI Thread</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/cache-bitmap.html"> + <span class="en">Caching Bitmaps</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/display-bitmap.html"> + <span class="en">Displaying Bitmaps in Your UI</span> + </a> + </li> + </ul> + </li> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>training/accessibility/index.html"> diff --git a/docs/html/shareables/training/BitmapFun.zip b/docs/html/shareables/training/BitmapFun.zip Binary files differnew file mode 100644 index 0000000..e7e71f9 --- /dev/null +++ b/docs/html/shareables/training/BitmapFun.zip diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd new file mode 100644 index 0000000..94abe21 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd @@ -0,0 +1,337 @@ +page.title=Caching Bitmaps +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Displaying Bitmaps in Your UI +next.link=display-bitmap.html +previous.title=Processing Bitmaps Off the UI Thread +previous.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#memory-cache">Use a Memory Cache</a></li> + <li><a href="#disk-cache">Use a Disk Cache</a></li> + <li><a href="#config-changes">Handle Configuration Changes</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Loading a single bitmap into your user interface (UI) is straightforward, however things get more +complicated if you need to load a larger set of images at once. In many cases (such as with +components like {@link android.widget.ListView}, {@link android.widget.GridView} or {@link +android.support.v4.view.ViewPager }), the total number of images on-screen combined with images that +might soon scroll onto the screen are essentially unlimited.</p> + +<p>Memory usage is kept down with components like this by recycling the child views as they move +off-screen. The garbage collector also frees up your loaded bitmaps, assuming you don't keep any +long lived references. This is all good and well, but in order to keep a fluid and fast-loading UI +you want to avoid continually processing these images each time they come back on-screen. A memory +and disk cache can often help here, allowing components to quickly reload processed images.</p> + +<p>This lesson walks you through using a memory and disk bitmap cache to improve the responsiveness +and fluidity of your UI when loading multiple bitmaps.</p> + +<h2 id="memory-cache">Use a Memory Cache</h2> + +<p>A memory cache offers fast access to bitmaps at the cost of taking up valuable application +memory. The {@link android.util.LruCache} class (also available in the <a +href="{@docRoot}reference/android/support/v4/util/LruCache.html">Support Library</a> for use back +to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently +referenced objects in a strong referenced {@link java.util.LinkedHashMap} and evicting the least +recently used member before the cache exceeds its designated size.</p> + +<p class="note"><strong>Note:</strong> In the past, a popular memory cache implementation was a +{@link java.lang.ref.SoftReference} or {@link java.lang.ref.WeakReference} bitmap cache, however +this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more +aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, +prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which +is not released in a predictable manner, potentially causing an application to briefly exceed its +memory limits and crash.</p> + +<p>In order to choose a suitable size for a {@link android.util.LruCache}, a number of factors +should be taken into consideration, for example:</p> + +<ul> + <li>How memory intensive is the rest of your activity and/or application?</li> + <li>How many images will be on-screen at once? How many need to be available ready to come + on-screen?</li> + <li>What is the screen size and density of the device? An extra high density screen (xhdpi) device + like <a href="http://www.android.com/devices/detail/galaxy-nexus">Galaxy Nexus</a> will need a + larger cache to hold the same number of images in memory compared to a device like <a + href="http://www.android.com/devices/detail/nexus-s">Nexus S</a> (hdpi).</li> + <li>What dimensions and configuration are the bitmaps and therefore how much memory will each take + up?</li> + <li>How frequently will the images be accessed? Will some be accessed more frequently than others? + If so, perhaps you may want to keep certain items always in memory or even have multiple {@link + android.util.LruCache} objects for different groups of bitmaps.</li> + <li>Can you balance quality against quantity? Sometimes it can be more useful to store a larger + number of lower quality bitmaps, potentially loading a higher quality version in another + background task.</li> +</ul> + +<p>There is no specific size or formula that suits all applications, it's up to you to analyze your +usage and come up with a suitable solution. A cache that is too small causes additional overhead with +no benefit, a cache that is too large can once again cause {@code java.lang.OutOfMemory} exceptions +and leave the rest of your app little memory to work with.</p> + +<p>Here’s an example of setting up a {@link android.util.LruCache} for bitmaps:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Get memory class of this device, exceeding this amount will throw an + // OutOfMemory exception. + final int memClass = ((ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE)).getMemoryClass(); + + // Use 1/8th of the available memory for this memory cache. + final int cacheSize = 1024 * 1024 * memClass / 8; + + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap bitmap) { + // The cache size will be measured in bytes rather than number of items. + return bitmap.getByteCount(); + } + }; + ... +} + +public void addBitmapToMemoryCache(String key, Bitmap bitmap) { + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromMemCache(String key) { + return mMemoryCache.get(key); +} +</pre> + +<p class="note"><strong>Note:</strong> In this example, one eighth of the application memory is +allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full +screen {@link android.widget.GridView} filled with images on a device with 800x480 resolution would +use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in +memory.</p> + +<p>When loading a bitmap into an {@link android.widget.ImageView}, the {@link android.util.LruCache} +is checked first. If an entry is found, it is used immediately to update the {@link +android.widget.ImageView}, otherwise a background thread is spawned to process the image:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = getBitmapFromMemCache(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } +} +</pre> + +<p>The <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> also needs to be +updated to add entries to the memory cache:</p> + +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); + return bitmap; + } + ... +} +</pre> + +<h2 id="disk-cache">Use a Disk Cache</h2> + +<p>A memory cache is useful in speeding up access to recently viewed bitmaps, however you cannot +rely on images being available in this cache. Components like {@link android.widget.GridView} with +larger datasets can easily fill up a memory cache. Your application could be interrupted by another +task like a phone call, and while in the background it might be killed and the memory cache +destroyed. Once the user resumes, your application it has to process each image again.</p> + +<p>A disk cache can be used in these cases to persist processed bitmaps and help decrease loading +times where images are no longer available in a memory cache. Of course, fetching images from disk +is slower than loading from memory and should be done in a background thread, as disk read times can +be unpredictable.</p> + +<p class="note"><strong>Note:</strong> A {@link android.content.ContentProvider} might be a more +appropriate place to store cached images if they are accessed more frequently, for example in an +image gallery application.</p> + +<p>Included in the sample code of this class is a basic {@code DiskLruCache} implementation. +However, a more robust and recommended {@code DiskLruCache} solution is included in the Android 4.0 +source code ({@code libcore/luni/src/main/java/libcore/io/DiskLruCache.java}). Back-porting this +class for use on previous Android releases should be fairly straightforward (a <a +href="http://www.google.com/search?q=disklrucache">quick search</a> shows others who have already +implemented this solution).</p> + +<p>Here’s updated example code that uses the simple {@code DiskLruCache} included in the sample +application of this class:</p> + +<pre> +private DiskLruCache mDiskCache; +private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB +private static final String DISK_CACHE_SUBDIR = "thumbnails"; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Initialize memory cache + ... + File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); + mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); + ... +} + +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final String imageKey = String.valueOf(params[0]); + + // Check disk cache in background thread + Bitmap bitmap = getBitmapFromDiskCache(imageKey); + + if (bitmap == null) { // Not found in disk cache + // Process as normal + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + } + + // Add final bitmap to caches + addBitmapToCache(String.valueOf(imageKey, bitmap); + + return bitmap; + } + ... +} + +public void addBitmapToCache(String key, Bitmap bitmap) { + // Add to memory cache as before + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } + + // Also add to disk cache + if (!mDiskCache.containsKey(key)) { + mDiskCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromDiskCache(String key) { + return mDiskCache.get(key); +} + +// Creates a unique subdirectory of the designated app cache directory. Tries to use external +// but if not mounted, falls back on internal storage. +public static File getCacheDir(Context context, String uniqueName) { + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED + || !Environment.isExternalStorageRemovable() ? + context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); + + return new File(cachePath + File.separator + uniqueName); +} +</pre> + +<p>While the memory cache is checked in the UI thread, the disk cache is checked in the background +thread. Disk operations should never take place on the UI thread. When image processing is +complete, the final bitmap is added to both the memory and disk cache for future use.</p> + +<h2 id="config-changes">Handle Configuration Changes</h2> + +<p>Runtime configuration changes, such as a screen orientation change, cause Android to destroy and +restart the running activity with the new configuration (For more information about this behavior, +see <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a>). +You want to avoid having to process all your images again so the user has a smooth and fast +experience when a configuration change occurs.</p> + +<p>Luckily, you have a nice memory cache of bitmaps that you built in the <a +href="#memory-cache">Use a Memory Cache</a> section. This cache can be passed through to the new +activity instance using a {@link android.app.Fragment} which is preserved by calling {@link +android.app.Fragment#setRetainInstance setRetainInstance(true)}). After the activity has been +recreated, this retained {@link android.app.Fragment} is reattached and you gain access to the +existing cache object, allowing images to be quickly fetched and re-populated into the {@link +android.widget.ImageView} objects.</p> + +<p>Here’s an example of retaining a {@link android.util.LruCache} object across configuration +changes using a {@link android.app.Fragment}:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + RetainFragment mRetainFragment = + RetainFragment.findOrCreateRetainFragment(getFragmentManager()); + mMemoryCache = RetainFragment.mRetainedCache; + if (mMemoryCache == null) { + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + ... // Initialize cache here as usual + } + mRetainFragment.mRetainedCache = mMemoryCache; + } + ... +} + +class RetainFragment extends Fragment { + private static final String TAG = "RetainFragment"; + public LruCache<String, Bitmap> mRetainedCache; + + public RetainFragment() {} + + public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { + RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); + if (fragment == null) { + fragment = new RetainFragment(); + } + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + <strong>setRetainInstance(true);</strong> + } +} +</pre> + +<p>To test this out, try rotating a device both with and without retaining the {@link +android.app.Fragment}. You should notice little to no lag as the images populate the activity almost +instantly from memory when you retain the cache. Any images not found in the memory cache are +hopefully available in the disk cache, if not, they are processed as usual.</p> diff --git a/docs/html/training/displaying-bitmaps/display-bitmap.jd b/docs/html/training/displaying-bitmaps/display-bitmap.jd new file mode 100644 index 0000000..7a93313 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/display-bitmap.jd @@ -0,0 +1,400 @@ +page.title=Displaying Bitmaps in Your UI +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +previous.title=Caching Bitmaps +previous.link=cache-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#viewpager">Load Bitmaps into a ViewPager Implementation</a></li> + <li><a href="#gridview">Load Bitmaps into a GridView Implementation</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> + <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p></p> + +<p>This lesson brings together everything from previous lessons, showing you how to load multiple +bitmaps into {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} +components using a background thread and bitmap cache, while dealing with concurrency and +configuration changes.</p> + +<h2 id="viewpager">Load Bitmaps into a ViewPager Implementation</h2> + +<p>The <a href="{@docRoot}design/patterns/swipe-views.html">swipe view pattern</a> is an excellent +way to navigate the detail view of an image gallery. You can implement this pattern using a {@link +android.support.v4.view.ViewPager} component backed by a {@link +android.support.v4.view.PagerAdapter}. However, a more suitable backing adapter is the subclass +{@link android.support.v4.app.FragmentStatePagerAdapter} which automatically destroys and saves +state of the {@link android.app.Fragment Fragments} in the {@link android.support.v4.view.ViewPager} +as they disappear off-screen, keeping memory usage down.</p> + +<p class="note"><strong>Note:</strong> If you have a smaller number of images and are confident they +all fit within the application memory limit, then using a regular {@link +android.support.v4.view.PagerAdapter} or {@link android.support.v4.app.FragmentPagerAdapter} might +be more appropriate.</p> + +<p>Here’s an implementation of a {@link android.support.v4.view.ViewPager} with {@link +android.widget.ImageView} children. The main activity holds the {@link +android.support.v4.view.ViewPager} and the adapter:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + public static final String EXTRA_IMAGE = "extra_image"; + + private ImagePagerAdapter mAdapter; + private ViewPager mPager; + + // A static dataset to back the ViewPager adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image_detail_pager); // Contains just a ViewPager + + mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length); + mPager = (ViewPager) findViewById(R.id.pager); + mPager.setAdapter(mAdapter); + } + + public static class ImagePagerAdapter extends FragmentStatePagerAdapter { + private final int mSize; + + public ImagePagerAdapter(FragmentManager fm, int size) { + super(fm); + mSize = size; + } + + @Override + public int getCount() { + return mSize; + } + + @Override + public Fragment getItem(int position) { + return ImageDetailFragment.newInstance(position); + } + } +} +</pre> + +<p>The details {@link android.app.Fragment} holds the {@link android.widget.ImageView} children:</p> + +<pre> +public class ImageDetailFragment extends Fragment { + private static final String IMAGE_DATA_EXTRA = "resId"; + private int mImageNum; + private ImageView mImageView; + + static ImageDetailFragment newInstance(int imageNum) { + final ImageDetailFragment f = new ImageDetailFragment(); + final Bundle args = new Bundle(); + args.putInt(IMAGE_DATA_EXTRA, imageNum); + f.setArguments(args); + return f; + } + + // Empty constructor, required as per Fragment docs + public ImageDetailFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // image_detail_fragment.xml contains just an ImageView + final View v = inflater.inflate(R.layout.image_detail_fragment, container, false); + mImageView = (ImageView) v.findViewById(R.id.imageView); + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + <strong>mImageView.setImageResource(resId);</strong> // Load image into ImageView + } +} +</pre> + +<p>Hopefully you noticed the issue with this implementation; The images are being read from +resources on the UI thread which can lead to an application hanging and being force closed. Using an +{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps Off +the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a background +thread:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + + public void loadBitmap(int resId, ImageView imageView) { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + + ... // include <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> class +} + +public class ImageDetailFragment extends Fragment { + ... + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (ImageDetailActivity.class.isInstance(getActivity())) { + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + // Call out to ImageDetailActivity to load the bitmap in a background thread + ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView); + } + } +} +</pre> + +<p>Any additional processing (such as resizing or fetching images from the network) can take place +in the <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> without affecting +responsiveness of the main UI. If the background thread is doing more than just loading an image +directly from disk, it can also be beneficial to add a memory and/or disk cache as described in the +lesson <a href="cache-bitmap.html#memory-cache">Caching Bitmaps</a>. Here's the additional +modifications for a memory cache:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + private LruCache<String, Bitmap> mMemoryCache; + + @Override + public void onCreate(Bundle savedInstanceState) { + ... + // initialize LruCache as per <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section + } + + public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = mMemoryCache.get(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + } + + ... // include updated BitmapWorkerTask from <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section +} +</pre> + +<p>Putting all these pieces together gives you a responsive {@link +android.support.v4.view.ViewPager} implementation with minimal image loading latency and the ability +to do as much or as little background processing on your images as needed.</p> + +<h2 id="gridview">Load Bitmaps into a GridView Implementation</h2> + +<p>The <a href="{@docRoot}design/building-blocks/grid-lists.html">grid list building block</a> is +useful for showing image data sets and can be implemented using a {@link android.widget.GridView} +component in which many images can be on-screen at any one time and many more need to be ready to +appear if the user scrolls up or down. When implementing this type of control, you must ensure the +UI remains fluid, memory usage remains under control and concurrency is handled correctly (due to +the way {@link android.widget.GridView} recycles its children views).</p> + +<p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link +android.widget.ImageView} children placed inside a {@link android.app.Fragment}:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + private ImageAdapter mAdapter; + + // A static dataset to back the GridView adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + // Empty constructor as per Fragment docs + public ImageGridFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAdapter = new ImageAdapter(getActivity()); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.image_grid_fragment, container, false); + final GridView mGridView = (GridView) v.findViewById(R.id.gridView); + mGridView.setAdapter(mAdapter); + mGridView.setOnItemClickListener(this); + return v; + } + + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { + final Intent i = new Intent(getActivity(), ImageDetailActivity.class); + i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position); + startActivity(i); + } + + private class ImageAdapter extends BaseAdapter { + private final Context mContext; + + public ImageAdapter(Context context) { + super(); + mContext = context; + } + + @Override + public int getCount() { + return imageResIds.length; + } + + @Override + public Object getItem(int position) { + return imageResIds[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ImageView imageView; + if (convertView == null) { // if it's not recycled, initialize some attributes + imageView = new ImageView(mContext); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(new GridView.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + } else { + imageView = (ImageView) convertView; + } + <strong>imageView.setImageResource(imageResIds[position]);</strong> // Load image into ImageView + return imageView; + } + } +} +</pre> + +<p>Once again, the problem with this implementation is that the image is being set in the UI thread. +While this may work for small, simple images (due to system resource loading and caching), if any +additional processing needs to be done, your UI grinds to a halt.</p> + +<p>The same asynchronous processing and caching methods from the previous section can be implemented +here. However, you also need to wary of concurrency issues as the {@link android.widget.GridView} +recycles its children views. To handle this, use the techniques discussed in the <a +href="process-bitmap#concurrency">Processing Bitmaps Off the UI Thread</a> lesson. Here is the updated +solution:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + ... + + private class ImageAdapter extends BaseAdapter { + ... + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ... + <strong>loadBitmap(imageResIds[position], imageView)</strong> + return imageView; + } + } + + public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + ... // include updated <a href="process-bitmap.html#BitmapWorkerTaskUpdated">{@code BitmapWorkerTask}</a> class +</pre> + +<p class="note"><strong>Note:</strong> The same code can easily be adapted to work with {@link +android.widget.ListView} as well.</p> + +<p>This implementation allows for flexibility in how the images are processed and loaded without +impeding the smoothness of the UI. In the background task you can load images from the network or +resize large digital camera photos and the images appear as the tasks finish processing.</p> + +<p>For a full example of this and other concepts discussed in this lesson, please see the included +sample application.</p> diff --git a/docs/html/training/displaying-bitmaps/index.jd b/docs/html/training/displaying-bitmaps/index.jd new file mode 100644 index 0000000..6755c24 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/index.jd @@ -0,0 +1,78 @@ +page.title=Displaying Bitmaps Efficiently + +trainingnavtop=true +startpage=true +next.title=Loading Large Bitmaps Efficiently +next.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.1 (API Level 7) or higher</li> + <li><a href="{@docRoot}sdk/compatibility-library.html">Support Library</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>This class covers some common techniques for processing and loading {@link +android.graphics.Bitmap} objects in a way that keeps your user interface (UI) components responsive +and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly +consume your available memory budget leading to an application crash due to the dreaded +exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget}.</p> + +<p>There are a number of reasons why loading bitmaps in your Android application is tricky:</p> + +<ul> + <li>Mobile devices typically have constrained system resources. Android devices can have as little + as 16MB of memory available to a single application. The <a + href="http://source.android.com/compatibility/downloads.html">Android Compatibility Definition + Document</a> (CDD), <i>Section 3.7. Virtual Machine Compatibility</i> gives the required minimum + application memory for various screen sizes and densities. Applications should be optimized to + perform under this minimum memory limit. However, keep in mind many devices are configured with + higher limits.</li> + <li>Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the + camera on the <a href="http://www.google.com/nexus/">Galaxy Nexus</a> takes photos up to 2592x1936 + pixels (5 megapixels). If the bitmap configuration used is {@link + android.graphics.Bitmap.Config ARGB_8888} (the default from the Android 2.3 onward) then loading + this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the + per-app limit on some devices.</li> + <li>Android app UI’s frequently require several bitmaps to be loaded at once. Components such as + {@link android.widget.ListView}, {@link android.widget.GridView} and {@link + android.support.v4.view.ViewPager} commonly include multiple bitmaps on-screen at once with many + more potentially off-screen ready to show at the flick of a finger.</li> +</ul> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="load-bitmap.html">Loading Large Bitmaps Efficiently</a></b></dt> + <dd>This lesson walks you through decoding large bitmaps without exceeding the per application + memory limit.</dd> + + <dt><b><a href="process-bitmap.html">Processing Bitmaps Off the UI Thread</a></b></dt> + <dd>Bitmap processing (resizing, downloading from a remote source, etc.) should never take place + on the main UI thread. This lesson walks you through processing bitmaps in a background thread + using {@link android.os.AsyncTask} and explains how to handle concurrency issues.</dd> + + <dt><b><a href="cache-bitmap.html">Caching Bitmaps</a></b></dt> + <dd>This lesson walks you through using a memory and disk bitmap cache to improve the + responsiveness and fluidity of your UI when loading multiple bitmaps.</dd> + + <dt><b><a href="display-bitmap.html">Displaying Bitmaps in Your UI</a></b></dt> + <dd>This lesson brings everything together, showing you how to load multiple bitmaps into + components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} + using a background thread and bitmap cache.</dd> + +</dl>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/load-bitmap.jd b/docs/html/training/displaying-bitmaps/load-bitmap.jd new file mode 100644 index 0000000..c0a5709 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/load-bitmap.jd @@ -0,0 +1,165 @@ +page.title=Loading Large Bitmaps Efficiently +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Processing Bitmaps Off the UI Thread +next.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#read-bitmap">Read Bitmap Dimensions and Type</a></li> + <li><a href="#load-bitmap">Load a Scaled Down Version into Memory</a></li> +</ol> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Images come in all shapes and sizes. In many cases they are larger than required for a typical +application user interface (UI). For example, the system Gallery application displays photos taken +using your Android devices's camera which are typically much higher resolution than the screen +density of your device.</p> + +<p>Given that you are working with limited memory, ideally you only want to load a lower resolution +version in memory. The lower resolution version should match the size of the UI component that +displays it. An image with a higher resolution does not provide any visible benefit, but still takes +up precious memory and incurs additional performance overhead due to additional on the fly +scaling.</p> + +<p>This lesson walks you through decoding large bitmaps without exceeding the per application +memory limit by loading a smaller subsampled version in memory.</p> + +<h2 id="read-bitmap">Read Bitmap Dimensions and Type</h2> + +<p>The {@link android.graphics.BitmapFactory} class provides several decoding methods ({@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +decodeByteArray()}, {@link +android.graphics.BitmapFactory#decodeFile(java.lang.String,android.graphics.BitmapFactory.Options) +decodeFile()}, {@link +android.graphics.BitmapFactory#decodeResource(android.content.res.Resources,int,android.graphics.BitmapFactory.Options) +decodeResource()}, etc.) for creating a {@link android.graphics.Bitmap} from various sources. Choose +the most appropriate decode method based on your image data source. These methods attempt to +allocate memory for the constructed bitmap and therefore can easily result in an {@code OutOfMemory} +exception. Each type of decode method has additional signatures that let you specify decoding +options via the {@link android.graphics.BitmapFactory.Options} class. Setting the {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} property to {@code true} while decoding +avoids memory allocation, returning {@code null} for the bitmap object but setting {@link +android.graphics.BitmapFactory.Options#outWidth}, {@link +android.graphics.BitmapFactory.Options#outHeight} and {@link +android.graphics.BitmapFactory.Options#outMimeType}. This technique allows you to read the +dimensions and type of the image data prior to construction (and memory allocation) of the +bitmap.</p> + +<pre> +BitmapFactory.Options options = new BitmapFactory.Options(); +options.inJustDecodeBounds = true; +BitmapFactory.decodeResource(getResources(), R.id.myimage, options); +int imageHeight = options.outHeight; +int imageWidth = options.outWidth; +String imageType = options.outMimeType; +</pre> + +<p>To avoid {@code java.lang.OutOfMemory} exceptions, check the dimensions of a bitmap before +decoding it, unless you absolutely trust the source to provide you with predictably sized image data +that comfortably fits within the available memory.</p> + +<h2 id="load-bitmap">Load a Scaled Down Version into Memory</h2> + +<p>Now that the image dimensions are known, they can be used to decide if the full image should be +loaded into memory or if a subsampled version should be loaded instead. Here are some factors to +consider:</p> + +<ul> + <li>Estimated memory usage of loading the full image in memory.</li> + <li>Amount of memory you are willing to commit to loading this image given any other memory + requirements of your application.</li> + <li>Dimensions of the target {@link android.widget.ImageView} or UI component that the image + is to be loaded into.</li> + <li>Screen size and density of the current device.</li> +</ul> + +<p>For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be +displayed in a 128x96 pixel thumbnail in an {@link android.widget.ImageView}.</p> + +<p>To tell the decoder to subsample the image, loading a smaller version into memory, set {@link +android.graphics.BitmapFactory.Options#inSampleSize} to {@code true} in your {@link +android.graphics.BitmapFactory.Options} object. For example, an image with resolution 2048x1536 that +is decoded with an {@link android.graphics.BitmapFactory.Options#inSampleSize} of 4 produces a +bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full +image (assuming a bitmap configuration of {@link android.graphics.Bitmap.Config ARGB_8888}). Here’s +a method to calculate a the sample size value based on a target width and height:</p> + +<pre> +public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + if (width > height) { + inSampleSize = Math.round((float)height / (float)reqHeight); + } else { + inSampleSize = Math.round((float)width / (float)reqWidth); + } + } + return inSampleSize; +} +</pre> + +<p class="note"><strong>Note:</strong> Using powers of 2 for {@link +android.graphics.BitmapFactory.Options#inSampleSize} values is faster and more efficient for the +decoder. However, if you plan to cache the resized versions in memory or on disk, it’s usually still +worth decoding to the most appropriate image dimensions to save space.</p> + +<p>To use this method, first decode with {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code true}, pass the options +through and then decode again using the new {@link +android.graphics.BitmapFactory.Options#inSampleSize} value and {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code false}:</p> + +<a name="decodeSampledBitmapFromResource"></a> +<pre> +public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); +} +</pre> + +<p>This method makes it easy to load a bitmap of arbitrarily large size into an {@link +android.widget.ImageView} that displays a 100x100 pixel thumbnail, as shown in the following example +code:</p> + +<pre> +mImageView.setImageBitmap( + decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); +</pre> + +<p>You can follow a similar process to decode bitmaps from other sources, by substituting the +appropriate {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} method as needed.</p>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd new file mode 100644 index 0000000..c1450b4 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd @@ -0,0 +1,239 @@ +page.title=Processing Bitmaps Off the UI Thread +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Caching Bitmaps +next.link=cache-bitmap.html +previous.title=Loading Large Bitmaps Efficiently +previous.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#async-task">Use an AsyncTask</a></li> + <li><a href="#concurrency">Handle Concurrency</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a></li> + <li><a + href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading + for Performance</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>The {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps +Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from +disk or a network location (or really any source other than memory). The time this data takes to +load is unpredictable and depends on a variety of factors (speed of reading from disk or network, +size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags +your application as non-responsive and the user has the option of closing it (see <a +href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a> for +more information).</p> + +<p>This lesson walks you through processing bitmaps in a background thread using +{@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p> + +<h2 id="async-task">Use an AsyncTask</h2> + +<p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background +thread and publish the results back on the UI thread. To use it, create a subclass and override the +provided methods. Here’s an example of loading a large image into an {@link +android.widget.ImageView} using {@link android.os.AsyncTask} and <a +href="load-bitmap.html#decodeSampledBitmapFromResource">{@code +decodeSampledBitmapFromResource()}</a>: </p> + +<a name="BitmapWorkerTask"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private int data = 0; + + public BitmapWorkerTask(ImageView imageView) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<ImageView>(imageView); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + data = params[0]; + return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the +{@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it +references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView} +is still around when the task finishes, so you must also check the reference in {@link +android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView} +may no longer exist, if for example, the user navigates away from the activity or if a +configuration change happens before the task finishes.</p> + +<p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + BitmapWorkerTask task = new BitmapWorkerTask(imageView); + task.execute(resId); +} +</pre> + +<h2 id="concurrency">Handle Concurrency</h2> + +<p>Common view components such as {@link android.widget.ListView} and {@link +android.widget.GridView} introduce another issue when used in conjunction with the {@link +android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory, +these components recycle child views as the user scrolls. If each child view triggers an {@link +android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not +already been recycled for use in another child view. Furthermore, there is no guarantee that the +order in which asynchronous tasks are started is the order that they complete.</p> + +<p>The blog post <a +href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading +for Performance</a> further discusses dealing with concurrency, and offers a solution where the +{@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask} +which can later be checked when the task completes. Using a similar method, the {@link +android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p> + +<p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference +back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so +that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task +completes:</p> + +<a name="AsyncDrawable"></a> +<pre> +static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } +} +</pre> + +<p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a +href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link +android.widget.ImageView}:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } +} +</pre> + +<p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another +running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to +cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number +of cases, the new task data matches the existing task and nothing further needs to happen. Here is +the implementation of {@code cancelPotentialWork}:</p> + +<pre> +public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; +} +</pre> + +<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated +with a particular {@link android.widget.ImageView}:</p> + +<pre> +private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; +} +</pre> + +<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code +BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the +one associated with the {@link android.widget.ImageView}:</p> + +<a name="BitmapWorkerTaskUpdated"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + + @Override + protected void onPostExecute(Bitmap bitmap) { + <strong>if (isCancelled()) { + bitmap = null; + }</strong> + + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + <strong>final BitmapWorkerTask bitmapWorkerTask = + getBitmapWorkerTask(imageView);</strong> + if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link +android.widget.GridView} components as well as any other components that recycle their child +views. Simply call {@code loadBitmap} where you normally set an image to your {@link +android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this +would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
\ No newline at end of file diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6f939be..ed5b2f6 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -16,9 +16,12 @@ package android.graphics; +import android.os.Debug; import android.os.Parcel; import android.os.Parcelable; import android.util.DisplayMetrics; +import android.util.Log; + import java.io.OutputStream; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -57,6 +60,7 @@ public final class Bitmap implements Parcelable { private final boolean mIsMutable; private byte[] mNinePatchChunk; // may be null + private int[] mLayoutBounds; // may be null private int mWidth = -1; private int mHeight = -1; private boolean mRecycled; @@ -95,6 +99,19 @@ public final class Bitmap implements Parcelable { */ /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, int density) { + this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density); + } + + /** + * @noinspection UnusedDeclaration + */ + /* Private constructor that must received an already allocated native + bitmap int (pointer). + + This can be called from JNI code. + */ + /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, + int[] layoutBounds, int density) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } @@ -106,6 +123,7 @@ public final class Bitmap implements Parcelable { mIsMutable = isMutable; mNinePatchChunk = ninePatchChunk; + mLayoutBounds = layoutBounds; if (density >= 0) { mDensity = density; } @@ -164,6 +182,16 @@ public final class Bitmap implements Parcelable { } /** + * Sets the layout bounds as an array of left, top, right, bottom integers + * @param padding the array containing the padding values + * + * @hide + */ + public void setLayoutBounds(int[] bounds) { + mLayoutBounds = bounds; + } + + /** * Free the native object associated with this bitmap, and clear the * reference to the pixel data. This will not free the pixel data synchronously; * it simply allows it to be garbage collected if there are no other references. @@ -690,6 +718,14 @@ public final class Bitmap implements Parcelable { } /** + * @hide + * @return the layout padding [left, right, top, bottom] + */ + public int[] getLayoutBounds() { + return mLayoutBounds; + } + + /** * Specifies the known formats a bitmap can be compressed into */ public enum CompressFormat { diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index c5705f6..1599e40 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -424,6 +424,7 @@ public class BitmapFactory { throw new ArrayIndexOutOfBoundsException(); } Bitmap bm = nativeDecodeByteArray(data, offset, length, opts); + if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } @@ -554,7 +555,6 @@ public class BitmapFactory { if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { return bm; } - byte[] np = bm.getNinePatchChunk(); final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); if (opts.inScaled || isNinePatch) { diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 043adae..86e824b 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -773,7 +773,13 @@ public abstract class Drawable { np = null; pad = null; } - return drawableFromBitmap(res, bm, np, pad, srcName); + int[] layoutBounds = bm.getLayoutBounds(); + Rect layoutBoundsRect = null; + if (layoutBounds != null) { + layoutBoundsRect = new Rect(layoutBounds[0], layoutBounds[1], + layoutBounds[2], layoutBounds[3]); + } + return drawableFromBitmap(res, bm, np, pad, layoutBoundsRect, srcName); } return null; } @@ -875,7 +881,7 @@ public abstract class Drawable { Bitmap bm = BitmapFactory.decodeFile(pathName); if (bm != null) { - return drawableFromBitmap(null, bm, null, null, pathName); + return drawableFromBitmap(null, bm, null, null, null, pathName); } return null; @@ -956,10 +962,12 @@ public abstract class Drawable { } private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np, - Rect pad, String srcName) { + Rect pad, Rect layoutBounds, String srcName) { if (np != null) { - return new NinePatchDrawable(res, bm, np, pad, srcName); + NinePatchDrawable npd = new NinePatchDrawable(res, bm, np, pad, srcName); + npd.setLayoutBounds(layoutBounds); + return npd; } return new BitmapDrawable(res, bm); diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 18b8bc7..1272071 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -47,6 +47,7 @@ public class NinePatchDrawable extends Drawable { private NinePatchState mNinePatchState; private NinePatch mNinePatch; private Rect mPadding; + private Rect mLayoutBounds; private Paint mPaint; private boolean mMutated; @@ -98,6 +99,13 @@ public class NinePatchDrawable extends Drawable { mNinePatchState.mTargetDensity = mTargetDensity; } + /** + * @hide + */ + void setLayoutBounds(Rect layoutBounds) { + mLayoutBounds = layoutBounds; + } + private void setNinePatchState(NinePatchState state, Resources res) { mNinePatchState = state; mNinePatch = state.mNinePatch; @@ -258,7 +266,7 @@ public class NinePatchDrawable extends Drawable { } options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE; - final Rect padding = new Rect(); + final Rect padding = new Rect(); Bitmap bitmap = null; try { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 82dd308..c7e71eb 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -903,9 +903,18 @@ public class AudioManager { * @hide */ public void setMasterMute(boolean state) { + setMasterMute(state, FLAG_SHOW_UI); + } + + /** + * set master mute state with optional flags. + * + * @hide + */ + public void setMasterMute(boolean state, int flags) { IAudioService service = getService(); try { - service.setMasterMute(state, mICallBack); + service.setMasterMute(state, flags, mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in setMasterMute", e); } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index c66a03f..2e456f0 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -126,6 +126,7 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_RCDISPLAY_CLEAR = 13; private static final int MSG_RCDISPLAY_UPDATE = 14; private static final int MSG_SET_ALL_VOLUMES = 15; + private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 16; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be @@ -501,6 +502,10 @@ public class AudioService extends IAudioService.Stub { System.MUTE_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM))); + boolean masterMute = System.getInt(cr, System.VOLUME_MASTER_MUTE, 0) == 1; + AudioSystem.setMasterMute(masterMute); + broadcastMasterMuteStatus(masterMute); + // Each stream will read its own persisted settings // Broadcast the sticky intent @@ -740,9 +745,14 @@ public class AudioService extends IAudioService.Stub { // UI update and Broadcast Intent private void sendMasterMuteUpdate(boolean muted, int flags) { mVolumePanel.postMasterMuteChanged(flags); + broadcastMasterMuteStatus(muted); + } + private void broadcastMasterMuteStatus(boolean muted) { Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); long origCallerIdentityToken = Binder.clearCallingIdentity(); mContext.sendStickyBroadcast(intent); Binder.restoreCallingIdentity(origCallerIdentityToken); @@ -817,10 +827,13 @@ public class AudioService extends IAudioService.Stub { } /** @see AudioManager#setMasterMute(boolean, IBinder) */ - public void setMasterMute(boolean state, IBinder cb) { + public void setMasterMute(boolean state, int flags, IBinder cb) { if (state != AudioSystem.getMasterMute()) { AudioSystem.setMasterMute(state); - sendMasterMuteUpdate(state, AudioManager.FLAG_SHOW_UI); + // Post a persist master volume msg + sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1 + : 0, 0, null, PERSIST_DELAY); + sendMasterMuteUpdate(state, flags); } } @@ -2551,6 +2564,11 @@ public class AudioService extends IAudioService.Stub { (float)msg.arg1 / (float)1000.0); break; + case MSG_PERSIST_MASTER_VOLUME_MUTE: + Settings.System.putInt(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, + msg.arg1); + break; + case MSG_PERSIST_RINGER_MODE: // note that the value persisted is the current ringer mode, not the // value of ringer mode as of the time the request was made to persist @@ -3038,11 +3056,6 @@ public class AudioService extends IAudioService.Stub { adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP); } - - if (mUseMasterVolume) { - // Send sticky broadcast for initial master mute state - sendMasterMuteUpdate(false, 0); - } } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // a package is being removed, not replaced diff --git a/media/java/android/media/Crypto.java b/media/java/android/media/Crypto.java new file mode 100644 index 0000000..43e34fb --- /dev/null +++ b/media/java/android/media/Crypto.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 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.media; + +/** + * Crypto class can be used in conjunction with MediaCodec to decode + * encrypted media data. + * @hide +*/ +public final class Crypto { + public static final native boolean isCryptoSchemeSupported(byte[] uuid); + + public Crypto(byte[] uuid, byte[] initData) { + native_setup(uuid, initData); + } + + public final native boolean requiresSecureDecoderComponent(String mime); + + @Override + protected void finalize() { + native_finalize(); + } + + public native final void release(); + private static native final void native_init(); + private native final void native_setup(byte[] uuid, byte[] initData); + private native final void native_finalize(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + + private int mNativeContext; +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 17d8e4d..b775095 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -45,7 +45,7 @@ interface IAudioService { boolean isStreamMute(int streamType); - void setMasterMute(boolean state, IBinder cb); + void setMasterMute(boolean state, int flags, IBinder cb); boolean isMasterMute(); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 7629d60..66cea9d4 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,6 +16,7 @@ package android.media; +import android.media.Crypto; import android.view.Surface; import java.nio.ByteBuffer; import java.util.Map; @@ -25,8 +26,7 @@ import java.util.Map; * encoder/decoder components. * @hide */ -public class MediaCodec -{ +final public class MediaCodec { /** Per buffer metadata includes an offset and size specifying the range of valid data in the associated codec buffer. */ @@ -113,11 +113,14 @@ public class MediaCodec * * @param surface Specify a surface on which to render the output of this * decoder. + * @param crypto Specify a crypto object to facilitate secure decryption + * of the media data. * @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the * component as an encoder. */ public void configure( - Map<String, Object> format, Surface surface, int flags) { + Map<String, Object> format, + Surface surface, Crypto crypto, int flags) { String[] keys = null; Object[] values = null; @@ -133,11 +136,12 @@ public class MediaCodec } } - native_configure(keys, values, surface, flags); + native_configure(keys, values, surface, crypto, flags); } private native final void native_configure( - String[] keys, Object[] values, Surface surface, int flags); + String[] keys, Object[] values, + Surface surface, Crypto crypto, int flags); /** After successfully configuring the component, call start. On return * you can query the component for its input/output buffers. diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 9ea3d0e..9c3b6a7 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -23,8 +23,7 @@ import java.util.Map; * MediaExtractor * @hide */ -public class MediaExtractor -{ +final public class MediaExtractor { public MediaExtractor(String path) { native_setup(path); } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 2f4ed89..26089ad 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -609,6 +609,10 @@ public class MediaScanner mCompilation = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("isdrm")) { mIsDrm = (parseSubstring(value, 0, 0) == 1); + } else if (name.equalsIgnoreCase("width")) { + mWidth = parseSubstring(value, 0, 0); + } else if (name.equalsIgnoreCase("height")) { + mHeight = parseSubstring(value, 0, 0); } else { //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")"); } @@ -734,9 +738,11 @@ public class MediaScanner map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm); + String resolution = null; if (mWidth > 0 && mHeight > 0) { map.put(MediaStore.MediaColumns.WIDTH, mWidth); map.put(MediaStore.MediaColumns.HEIGHT, mHeight); + resolution = mWidth + "x" + mHeight; } if (!mNoMedia) { @@ -746,7 +752,9 @@ public class MediaScanner map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaStore.UNKNOWN_STRING)); map.put(Video.Media.DURATION, mDuration); - // FIXME - add RESOLUTION + if (resolution != null) { + map.put(Video.Media.RESOLUTION, resolution); + } } else if (MediaFile.isImageFileType(mFileType)) { // FIXME - add DESCRIPTION } else if (MediaFile.isAudioFileType(mFileType)) { diff --git a/media/jni/Android.mk b/media/jni/Android.mk index dd1e505..a3361d4 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_Crypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ android_media_MediaExtractor.cpp \ diff --git a/media/jni/android_media_Crypto.cpp b/media/jni/android_media_Crypto.cpp new file mode 100644 index 0000000..e1a60a1 --- /dev/null +++ b/media/jni/android_media_Crypto.cpp @@ -0,0 +1,291 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "Crypto-JNI" +#include <utils/Log.h> + +#include "android_media_Crypto.h" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <binder/IServiceManager.h> +#include <media/ICrypto.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/foundation/ADebug.h> + +namespace android { + +struct fields_t { + jfieldID context; +}; + +static fields_t gFields; + +static sp<JCrypto> getCrypto(JNIEnv *env, jobject thiz) { + return (JCrypto *)env->GetIntField(thiz, gFields.context); +} + +JCrypto::JCrypto( + JNIEnv *env, jobject thiz, + const uint8_t uuid[16], const void *initData, size_t initSize) { + mObject = env->NewWeakGlobalRef(thiz); + + mCrypto = MakeCrypto(uuid, initData, initSize); +} + +JCrypto::~JCrypto() { + mCrypto.clear(); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; +} + +// static +sp<ICrypto> JCrypto::MakeCrypto() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<IBinder> binder = + sm->getService(String16("media.player")); + + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + if (service == NULL) { + return NULL; + } + + sp<ICrypto> crypto = service->makeCrypto(); + + if (crypto == NULL || crypto->initCheck() != OK) { + return NULL; + } + + return crypto; +} + +// static +sp<ICrypto> JCrypto::MakeCrypto( + const uint8_t uuid[16], const void *initData, size_t initSize) { + sp<ICrypto> crypto = MakeCrypto(); + + if (crypto == NULL) { + return NULL; + } + + status_t err = crypto->createPlugin(uuid, initData, initSize); + + if (err != OK) { + return NULL; + } + + return crypto; +} + +bool JCrypto::requiresSecureDecoderComponent(const char *mime) const { + if (mCrypto == NULL) { + return false; + } + + return mCrypto->requiresSecureDecoderComponent(mime); +} + +// static +bool JCrypto::IsCryptoSchemeSupported(const uint8_t uuid[16]) { + sp<ICrypto> crypto = MakeCrypto(); + + if (crypto == NULL) { + return false; + } + + return crypto->isCryptoSchemeSupported(uuid); +} + +status_t JCrypto::initCheck() const { + return mCrypto == NULL ? NO_INIT : OK; +} + +// static +sp<ICrypto> JCrypto::GetCrypto(JNIEnv *env, jobject obj) { + jclass clazz = env->FindClass("android/media/Crypto"); + CHECK(clazz != NULL); + + if (!env->IsInstanceOf(obj, clazz)) { + return NULL; + } + + sp<JCrypto> jcrypto = getCrypto(env, obj); + + if (jcrypto == NULL) { + return NULL; + } + + return jcrypto->mCrypto; +} + +} // namespace android + +using namespace android; + +static sp<JCrypto> setCrypto( + JNIEnv *env, jobject thiz, const sp<JCrypto> &crypto) { + sp<JCrypto> old = (JCrypto *)env->GetIntField(thiz, gFields.context); + if (crypto != NULL) { + crypto->incStrong(thiz); + } + if (old != NULL) { + old->decStrong(thiz); + } + env->SetIntField(thiz, gFields.context, (int)crypto.get()); + + return old; +} + +static void android_media_Crypto_release(JNIEnv *env, jobject thiz) { + setCrypto(env, thiz, NULL); +} + +static void android_media_Crypto_native_init(JNIEnv *env) { + jclass clazz = env->FindClass("android/media/Crypto"); + CHECK(clazz != NULL); + + gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + CHECK(gFields.context != NULL); +} + +static void android_media_Crypto_native_setup( + JNIEnv *env, jobject thiz, + jbyteArray uuidObj, jbyteArray initDataObj) { + jsize uuidLength = env->GetArrayLength(uuidObj); + + if (uuidLength != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + NULL); + return; + } + + jboolean isCopy; + jbyte *uuid = env->GetByteArrayElements(uuidObj, &isCopy); + + jsize initDataLength = env->GetArrayLength(initDataObj); + jbyte *initData = env->GetByteArrayElements(initDataObj, &isCopy); + + sp<JCrypto> crypto = new JCrypto( + env, thiz, (const uint8_t *)uuid, initData, initDataLength); + + status_t err = crypto->initCheck(); + + env->ReleaseByteArrayElements(initDataObj, initData, 0); + initData = NULL; + + env->ReleaseByteArrayElements(uuidObj, uuid, 0); + uuid = NULL; + + if (err != OK) { + jniThrowException( + env, + "java/io/IOException", + "Failed to instantiate crypto object."); + return; + } + + setCrypto(env,thiz, crypto); +} + +static void android_media_Crypto_native_finalize( + JNIEnv *env, jobject thiz) { + android_media_Crypto_release(env, thiz); +} + +static jboolean android_media_Crypto_isCryptoSchemeSupported( + JNIEnv *env, jobject thiz, jbyteArray uuidObj) { + jsize uuidLength = env->GetArrayLength(uuidObj); + + if (uuidLength != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + NULL); + return false; + } + + jboolean isCopy; + jbyte *uuid = env->GetByteArrayElements(uuidObj, &isCopy); + + bool result = JCrypto::IsCryptoSchemeSupported((const uint8_t *)uuid); + + env->ReleaseByteArrayElements(uuidObj, uuid, 0); + uuid = NULL; + + return result; +} + +static jboolean android_media_Crypto_requiresSecureDecoderComponent( + JNIEnv *env, jobject thiz, jstring mimeObj) { + if (mimeObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + sp<JCrypto> crypto = getCrypto(env, thiz); + + if (crypto == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + const char *mime = env->GetStringUTFChars(mimeObj, NULL); + + if (mime == NULL) { + return false; + } + + bool result = crypto->requiresSecureDecoderComponent(mime); + + env->ReleaseStringUTFChars(mimeObj, mime); + mime = NULL; + + return result; +} + +static JNINativeMethod gMethods[] = { + { "release", "()V", (void *)android_media_Crypto_release }, + { "native_init", "()V", (void *)android_media_Crypto_native_init }, + + { "native_setup", "([B[B)V", + (void *)android_media_Crypto_native_setup }, + + { "native_finalize", "()V", + (void *)android_media_Crypto_native_finalize }, + + { "isCryptoSchemeSupported", "([B)Z", + (void *)android_media_Crypto_isCryptoSchemeSupported }, + + { "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z", + (void *)android_media_Crypto_requiresSecureDecoderComponent }, +}; + +int register_android_media_Crypto(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/Crypto", gMethods, NELEM(gMethods)); +} + diff --git a/media/jni/android_media_Crypto.h b/media/jni/android_media_Crypto.h new file mode 100644 index 0000000..505725e --- /dev/null +++ b/media/jni/android_media_Crypto.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012, 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_MEDIA_CRYPTO_H_ +#define _ANDROID_MEDIA_CRYPTO_H_ + +#include "jni.h" + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +struct ICrypto; + +struct JCrypto : public RefBase { + static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); + + JCrypto(JNIEnv *env, jobject thiz, + const uint8_t uuid[16], const void *initData, size_t initSize); + + status_t initCheck() const; + + bool requiresSecureDecoderComponent(const char *mime) const; + + static sp<ICrypto> GetCrypto(JNIEnv *env, jobject obj); + +protected: + virtual ~JCrypto(); + +private: + jweak mObject; + sp<ICrypto> mCrypto; + + static sp<ICrypto> MakeCrypto(); + + static sp<ICrypto> MakeCrypto( + const uint8_t uuid[16], const void *initData, size_t initSize); + + DISALLOW_EVIL_CONSTRUCTORS(JCrypto); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_CRYPTO_H_ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 4b7a811..217216a 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -20,6 +20,7 @@ #include "android_media_MediaCodec.h" +#include "android_media_Crypto.h" #include "android_media_Utils.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" @@ -98,12 +99,13 @@ JMediaCodec::~JMediaCodec() { status_t JMediaCodec::configure( const sp<AMessage> &format, const sp<ISurfaceTexture> &surfaceTexture, + const sp<ICrypto> &crypto, int flags) { sp<SurfaceTextureClient> client; if (surfaceTexture != NULL) { client = new SurfaceTextureClient(surfaceTexture); } - return mCodec->configure(format, client, NULL /* crypto */, flags); + return mCodec->configure(format, client, crypto, flags); } status_t JMediaCodec::start() { @@ -256,6 +258,7 @@ static void android_media_MediaCodec_native_configure( jobject thiz, jobjectArray keys, jobjectArray values, jobject jsurface, + jobject jcrypto, jint flags) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -286,7 +289,12 @@ static void android_media_MediaCodec_native_configure( } } - err = codec->configure(format, surfaceTexture, flags); + sp<ICrypto> crypto; + if (jcrypto != NULL) { + crypto = JCrypto::GetCrypto(env, jcrypto); + } + + err = codec->configure(format, surfaceTexture, crypto, flags); throwExceptionAsNecessary(env, err); } @@ -513,7 +521,8 @@ static JNINativeMethod gMethods[] = { { "release", "()V", (void *)android_media_MediaCodec_release }, { "native_configure", - "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;I)V", + "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;" + "Landroid/media/Crypto;I)V", (void *)android_media_MediaCodec_native_configure }, { "start", "()V", (void *)android_media_MediaCodec_start }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 6b1257d..6bb4071 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -27,6 +27,7 @@ namespace android { struct ALooper; struct AMessage; +struct ICrypto; struct ISurfaceTexture; struct MediaCodec; @@ -40,6 +41,7 @@ struct JMediaCodec : public RefBase { status_t configure( const sp<AMessage> &format, const sp<ISurfaceTexture> &surfaceTexture, + const sp<ICrypto> &crypto, int flags); status_t start(); diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 3074bb1..2e74ffd 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -879,6 +879,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_MediaCodec(JNIEnv *env); extern int register_android_media_MediaExtractor(JNIEnv *env); extern int register_android_media_MediaCodecList(JNIEnv *env); @@ -968,6 +969,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_Crypto(env) < 0) { + ALOGE("ERROR: MediaCodec native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java index c501d3f..7be2707 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java @@ -22,7 +22,7 @@ import com.android.mediaframeworktest.MediaProfileReader; import com.android.mediaframeworktest.functional.CodecTest; import android.content.Context; -import android.test.ActivityInstrumentationTestCase; +import android.test.ActivityInstrumentationTestCase2; import android.util.Log; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; @@ -33,25 +33,28 @@ import java.io.File; /** * Junit / Instrumentation test case for the media player api */ -public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFrameworkTest> { - private boolean duratoinWithinTolerence = false; - private String TAG = "MediaPlayerApiTest"; - private boolean isWMAEnable = false; - private boolean isWMVEnable = false; +public class MediaPlayerApiTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private boolean duratoinWithinTolerence = false; + private String TAG = "MediaPlayerApiTest"; + private boolean isWMAEnable = false; + private boolean isWMVEnable = false; - Context mContext; + Context mContext; - public MediaPlayerApiTest() { - super("com.android.mediaframeworktest", MediaFrameworkTest.class); - isWMAEnable = MediaProfileReader.getWMAEnable(); - isWMVEnable = MediaProfileReader.getWMVEnable(); - } + public MediaPlayerApiTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + isWMAEnable = MediaProfileReader.getWMAEnable(); + isWMVEnable = MediaProfileReader.getWMVEnable(); + } protected void setUp() throws Exception { - super.setUp(); - - } - + //Insert a 2 second before launching the test activity. This is + //the workaround for the race condition of requesting the updated surface. + Thread.sleep(2000); + getActivity(); + super.setUp(); + } + public boolean verifyDuration(int duration, int expectedDuration){ if ((duration > expectedDuration * 1.1) || (duration < expectedDuration * 0.9)) return false; diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java index fc9832c..30876d0 100644 --- a/test-runner/src/android/test/AndroidTestRunner.java +++ b/test-runner/src/android/test/AndroidTestRunner.java @@ -28,6 +28,7 @@ import junit.framework.TestResult; import junit.framework.TestSuite; import junit.runner.BaseTestRunner; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -91,15 +92,35 @@ public class AndroidTestRunner extends BaseTestRunner { private TestCase buildSingleTestMethod(Class testClass, String testMethodName) { try { - TestCase testCase = (TestCase) testClass.newInstance(); + Constructor c = testClass.getConstructor(); + return newSingleTestMethod(testClass, testMethodName, c); + } catch (NoSuchMethodException e) { + } + + try { + Constructor c = testClass.getConstructor(String.class); + return newSingleTestMethod(testClass, testMethodName, c, testMethodName); + } catch (NoSuchMethodException e) { + } + + return null; + } + + private TestCase newSingleTestMethod(Class testClass, String testMethodName, + Constructor constructor, Object... args) { + try { + TestCase testCase = (TestCase) constructor.newInstance(args); testCase.setName(testMethodName); return testCase; } catch (IllegalAccessException e) { runFailed("Could not access test class. Class: " + testClass.getName()); } catch (InstantiationException e) { runFailed("Could not instantiate test class. Class: " + testClass.getName()); + } catch (IllegalArgumentException e) { + runFailed("Illegal argument passed to constructor. Class: " + testClass.getName()); + } catch (InvocationTargetException e) { + runFailed("Constructor thew an exception. Class: " + testClass.getName()); } - return null; } diff --git a/tests/BiDiTests/res/layout/textview_alignment_ltr.xml b/tests/BiDiTests/res/layout/textview_alignment_ltr.xml new file mode 100644 index 0000000..0e1adba --- /dev/null +++ b/tests/BiDiTests/res/layout/textview_alignment_ltr.xml @@ -0,0 +1,578 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/textview_alignment_ltr" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layoutDirection="ltr"> + + <TableLayout android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="gravity" + android:gravity="center_horizontal"> + + <TextView android:text="inherit gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="center"> + + <TextView android:text="inherit gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textStart"> + + <TextView android:text="inherit textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textEnd"> + + <TextView android:text="inherit textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewStart"> + + <TextView android:text="inherit viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewEnd"> + + <TextView android:text="inherit viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + </TableLayout> + +</FrameLayout> diff --git a/tests/BiDiTests/res/layout/textview_alignment_rtl.xml b/tests/BiDiTests/res/layout/textview_alignment_rtl.xml new file mode 100644 index 0000000..12a90d5 --- /dev/null +++ b/tests/BiDiTests/res/layout/textview_alignment_rtl.xml @@ -0,0 +1,578 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/textview_alignment_rtl" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layoutDirection="rtl"> + + <TableLayout android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="gravity" + android:gravity="center_horizontal"> + + <TextView android:text="inherit gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="center"> + + <TextView android:text="inherit gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textStart"> + + <TextView android:text="inherit textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textEnd"> + + <TextView android:text="inherit textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewStart"> + + <TextView android:text="inherit viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewEnd"> + + <TextView android:text="inherit viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + </TableLayout> + +</FrameLayout> diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index c5a1235..209597e 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -104,6 +104,16 @@ public class BiDiTestActivity extends Activity { addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas); addItem(result, "Canvas2", BiDiTestCanvas2.class, R.id.canvas2); + addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); + addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl); + addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale); + + addItem(result, "TextDirection LTR", BiDiTestTextViewDirectionLtr.class, R.id.textview_direction_ltr); + addItem(result, "TextDirection RTL", BiDiTestTextViewDirectionRtl.class, R.id.textview_direction_rtl); + + addItem(result, "TextAlignment LTR", BiDiTestTextViewAlignmentLtr.class, R.id.textview_alignment_ltr); + addItem(result, "TextAlignment RTL", BiDiTestTextViewAlignmentRtl.class, R.id.textview_alignment_rtl); + addItem(result, "Linear LTR", BiDiTestLinearLayoutLtr.class, R.id.linear_layout_ltr); addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl); addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale); @@ -134,15 +144,9 @@ public class BiDiTestActivity extends Activity { addItem(result, "Margin MIXED", BiDiTestViewGroupMarginMixed.class, R.id.view_group_margin_mixed); - addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); - addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl); - addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale); - - addItem(result, "TextDirection LTR", BiDiTestTextViewDirectionLtr.class, R.id.textview_direction_ltr); - addItem(result, "TextDirection RTL", BiDiTestTextViewDirectionRtl.class, R.id.textview_direction_rtl); - addItem(result, "TextView Drawables LTR", BiDiTestTextViewDrawablesLtr.class, R.id.textview_drawables_ltr); addItem(result, "TextView Drawables RTL", BiDiTestTextViewDrawablesRtl.class, R.id.textview_drawables_rtl); + addItem(result, "Gallery LTR", BiDiTestGalleryLtr.class, R.id.gallery_ltr); addItem(result, "Gallery RTL", BiDiTestGalleryRtl.class, R.id.gallery_rtl); diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java new file mode 100644 index 0000000..5ea5d81 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestTextViewAlignmentLtr extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.textview_alignment_ltr, container, false); + } +} diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java new file mode 100644 index 0000000..fcc7a5d --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestTextViewAlignmentRtl extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.textview_alignment_rtl, container, false); + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java index 87baf76..7c03313 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java @@ -23,7 +23,6 @@ import android.util.Log; import android.webkit.WebSettingsClassic; import android.webkit.WebView; import android.webkit.WebViewClassic; -import android.widget.Toast; import java.util.ArrayList; @@ -72,10 +71,7 @@ public class ProfiledWebView extends WebView implements WebViewClassic.PageSwapD mContext = c; } - /** Show a toast from the web page */ public void animationComplete() { - Toast.makeText(mContext, "Animation complete!", Toast.LENGTH_SHORT).show(); - //Log.d(LOGTAG, "anim complete"); mAnimationTime = System.currentTimeMillis(); } } diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index 2b9b056..9de685a 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -57,6 +57,13 @@ struct image_info bool is9Patch; Res_png_9patch info9Patch; + // Layout padding, if relevant + bool haveLayoutBounds; + int32_t layoutBoundsLeft; + int32_t layoutBoundsTop; + int32_t layoutBoundsRight; + int32_t layoutBoundsBottom; + png_uint_32 allocHeight; png_bytepp allocRows; }; @@ -129,33 +136,62 @@ static void read_png(const char* imageName, &interlace_type, &compression_type, NULL); } -static bool is_tick(png_bytep p, bool transparent, const char** outError) +#define COLOR_TRANSPARENT 0 +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_TICK 0xFF000000 +#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF + +enum { + TICK_TYPE_NONE, + TICK_TYPE_TICK, + TICK_TYPE_LAYOUT_BOUNDS, + TICK_TYPE_BOTH +}; + +static int tick_type(png_bytep p, bool transparent, const char** outError) { + png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + if (transparent) { if (p[3] == 0) { - return false; + return TICK_TYPE_NONE; + } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; + } + + // Error cases if (p[3] != 0xff) { *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)"; - return false; + return TICK_TYPE_NONE; } if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in transparent frame must be black"; + *outError = "Ticks in transparent frame must be black or red"; } - return true; + return TICK_TYPE_TICK; } if (p[3] != 0xFF) { *outError = "White frame must be a solid color (no alpha)"; } - if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) { - return false; + if (color == COLOR_WHITE) { + return TICK_TYPE_NONE; + } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; + } + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in white frame must be black"; - return false; + *outError = "Ticks in white frame must be black or red"; + return TICK_TYPE_NONE; } - return true; + return TICK_TYPE_TICK; } enum { @@ -175,7 +211,7 @@ static status_t get_horizontal_ticks( bool found = false; for (i=1; i<width-1; i++) { - if (is_tick(row+i*4, transparent, outError)) { + if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) { if (state == TICK_START || (state == TICK_OUTSIDE_1 && multipleAllowed)) { *outLeft = i-1; @@ -224,7 +260,7 @@ static status_t get_vertical_ticks( bool found = false; for (i=1; i<height-1; i++) { - if (is_tick(rows[i]+offset, transparent, outError)) { + if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) { if (state == TICK_START || (state == TICK_OUTSIDE_1 && multipleAllowed)) { *outTop = i-1; @@ -262,6 +298,83 @@ static status_t get_vertical_ticks( return NO_ERROR; } +static status_t get_horizontal_layout_bounds_ticks( + png_bytep row, int width, bool transparent, bool required, + int32_t* outLeft, int32_t* outRight, const char** outError) +{ + int i; + *outLeft = *outRight = 0; + + // Look for left tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < width - 1) { + (*outLeft)++; + i++; + int tick = tick_type(row + i * 4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for right tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) { + // Ending with a layout padding tick + i = width - 2; + while (i > 1) { + (*outRight)++; + i--; + int tick = tick_type(row+i*4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + +static status_t get_vertical_layout_bounds_ticks( + png_bytepp rows, int offset, int height, bool transparent, bool required, + int32_t* outTop, int32_t* outBottom, const char** outError) +{ + int i; + *outTop = *outBottom = 0; + + // Look for top tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < height - 1) { + (*outTop)++; + i++; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for bottom tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) { + // Ending with a layout padding tick + i = height - 2; + while (i > 1) { + (*outBottom)++; + i--; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + + static uint32_t get_color( png_bytepp rows, int left, int top, int right, int bottom) { @@ -353,6 +466,9 @@ static status_t do_9patch(const char* imageName, image_info* image) image->info9Patch.paddingLeft = image->info9Patch.paddingRight = image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; + image->layoutBoundsLeft = image->layoutBoundsRight = + image->layoutBoundsTop = image->layoutBoundsBottom = 0; + png_bytep p = image->rows[0]; bool transparent = p[3] == 0; bool hasColor = false; @@ -408,6 +524,25 @@ static status_t do_9patch(const char* imageName, image_info* image) goto getout; } + // Find left and right of layout padding... + get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false, + &image->layoutBoundsLeft, + &image->layoutBoundsRight, &errorMsg); + + get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false, + &image->layoutBoundsTop, + &image->layoutBoundsBottom, &errorMsg); + + image->haveLayoutBounds = image->layoutBoundsLeft != 0 + || image->layoutBoundsRight != 0 + || image->layoutBoundsTop != 0 + || image->layoutBoundsBottom != 0; + + if (image->haveLayoutBounds) { + NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, + image->layoutBoundsRight, image->layoutBoundsBottom)); + } + // Copy patch data into image image->info9Patch.numXDivs = numXDivs; image->info9Patch.numYDivs = numYDivs; @@ -845,8 +980,9 @@ static void write_png(const char* imageName, int bit_depth, interlace_type, compression_type; int i; - png_unknown_chunk unknowns[1]; + png_unknown_chunk unknowns[2]; unknowns[0].data = NULL; + unknowns[1].data = NULL; png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep)); if (outRows == (png_bytepp) 0) { @@ -916,23 +1052,42 @@ static void write_png(const char* imageName, } if (imageInfo.is9Patch) { + int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0); + int p_index = imageInfo.haveLayoutBounds ? 1 : 0; + int b_index = 0; + png_byte *chunk_names = imageInfo.haveLayoutBounds + ? (png_byte*)"npLb\0npTc\0" + : (png_byte*)"npTc"; NOISY(printf("Adding 9-patch info...\n")); - strcpy((char*)unknowns[0].name, "npTc"); - unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize(); - unknowns[0].size = imageInfo.info9Patch.serializedSize(); + strcpy((char*)unknowns[p_index].name, "npTc"); + unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize(); + unknowns[p_index].size = imageInfo.info9Patch.serializedSize(); // TODO: remove the check below when everything works - checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data); + checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data); + + if (imageInfo.haveLayoutBounds) { + int chunk_size = sizeof(png_uint_32) * 4; + strcpy((char*)unknowns[b_index].name, "npLb"); + unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1); + memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size); + unknowns[b_index].size = chunk_size; + } + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, - (png_byte*)"npTc", 1); - png_set_unknown_chunks(write_ptr, write_info, unknowns, 1); + chunk_names, chunk_count); + png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count); // XXX I can't get this to work without forcibly changing // the location to what I want... which apparently is supposed // to be a private API, but everything else I have tried results // in the location being set to what I -last- wrote so I never // get written. :p png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); + if (imageInfo.haveLayoutBounds) { + png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE); + } } + png_write_info(write_ptr, write_info); png_bytepp rows; @@ -954,6 +1109,7 @@ static void write_png(const char* imageName, } free(outRows); free(unknowns[0].data); + free(unknowns[1].data); png_get_IHDR(write_ptr, write_info, &width, &height, &bit_depth, &color_type, &interlace_type, diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index eadec02..b76b8cf 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -524,7 +524,8 @@ public final class Bitmap_Delegate { int nativeInt = sManager.addNewDelegate(delegate); // and create/return a new Bitmap with it - return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density); + return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, + density); } /** |