summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt47
-rw-r--r--core/java/android/app/Notification.java26
-rw-r--r--core/java/android/nfc/INdefPushCallback.aidl3
-rw-r--r--core/java/android/nfc/NfcActivityManager.java44
-rw-r--r--core/java/android/nfc/NfcAdapter.java13
-rw-r--r--core/java/android/nfc/tech/MifareClassic.java1
-rw-r--r--core/java/android/provider/Settings.java21
-rw-r--r--core/java/android/view/View.java803
-rw-r--r--core/java/android/view/ViewGroup.java21
-rw-r--r--core/java/android/view/ViewRootImpl.java308
-rw-r--r--core/java/android/view/ViewTreeObserver.java119
-rw-r--r--core/java/android/webkit/WebSettingsClassic.java2
-rw-r--r--core/java/android/widget/Chronometer.java2
-rw-r--r--core/java/android/widget/TextView.java57
-rw-r--r--core/jni/android/graphics/Bitmap.cpp8
-rw-r--r--core/jni/android/graphics/BitmapFactory.cpp21
-rw-r--r--core/jni/android/graphics/BitmapRegionDecoder.cpp2
-rw-r--r--core/jni/android/graphics/Graphics.cpp10
-rw-r--r--core/jni/android/graphics/GraphicsJNI.h3
-rw-r--r--core/jni/android/graphics/NinePatchPeeker.cpp10
-rw-r--r--core/jni/android/graphics/NinePatchPeeker.h10
-rw-r--r--core/res/res/layout/notification_template_base.xml16
-rw-r--r--core/res/res/layout/notification_template_part_chronometer.xml26
-rw-r--r--core/res/res/layout/notification_template_part_time.xml26
-rwxr-xr-xcore/res/res/values/attrs.xml24
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--docs/html/guide/developing/testing/testing_otheride.jd2
-rw-r--r--docs/html/resources/resources_toc.cs25
-rw-r--r--docs/html/shareables/training/BitmapFun.zipbin0 -> 315657 bytes
-rw-r--r--docs/html/training/displaying-bitmaps/cache-bitmap.jd337
-rw-r--r--docs/html/training/displaying-bitmaps/display-bitmap.jd400
-rw-r--r--docs/html/training/displaying-bitmaps/index.jd78
-rw-r--r--docs/html/training/displaying-bitmaps/load-bitmap.jd165
-rw-r--r--docs/html/training/displaying-bitmaps/process-bitmap.jd239
-rw-r--r--graphics/java/android/graphics/Bitmap.java36
-rw-r--r--graphics/java/android/graphics/BitmapFactory.java2
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java16
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java10
-rw-r--r--media/java/android/media/AudioManager.java11
-rw-r--r--media/java/android/media/AudioService.java27
-rw-r--r--media/java/android/media/Crypto.java49
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/MediaCodec.java14
-rw-r--r--media/java/android/media/MediaExtractor.java3
-rw-r--r--media/java/android/media/MediaScanner.java10
-rw-r--r--media/jni/Android.mk1
-rw-r--r--media/jni/android_media_Crypto.cpp291
-rw-r--r--media/jni/android_media_Crypto.h59
-rw-r--r--media/jni/android_media_MediaCodec.cpp15
-rw-r--r--media/jni/android_media_MediaCodec.h2
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java35
-rw-r--r--test-runner/src/android/test/AndroidTestRunner.java25
-rw-r--r--tests/BiDiTests/res/layout/textview_alignment_ltr.xml578
-rw-r--r--tests/BiDiTests/res/layout/textview_alignment_rtl.xml578
-rw-r--r--tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java18
-rw-r--r--tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java31
-rw-r--r--tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java31
-rw-r--r--tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java4
-rw-r--r--tools/aapt/Images.cpp194
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java3
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 &lt;main_path&gt; -p &lt;test_path&gt;</pre>
+<pre>android update test-project -m &lt;main_path&gt; -p &lt;test_path&gt;</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">&nbsp;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">&nbsp;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
new file mode 100644
index 0000000..e7e71f9
--- /dev/null
+++ b/docs/html/shareables/training/BitmapFun.zip
Binary files differ
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;
+
+&#64;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) {
+ &#64;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.
+ &#64;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";
+
+&#64;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.
+ &#64;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;
+
+&#64;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;
+ }
+
+ &#64;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};
+
+ &#64;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;
+ }
+
+ &#64;Override
+ public int getCount() {
+ return mSize;
+ }
+
+ &#64;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() {}
+
+ &#64;Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
+ }
+
+ &#64;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;
+ }
+
+ &#64;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 {
+ ...
+
+ &#64;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;
+
+ &#64;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() {}
+
+ &#64;Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAdapter = new ImageAdapter(getActivity());
+ }
+
+ &#64;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;
+ }
+
+ &#64;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;
+ }
+
+ &#64;Override
+ public int getCount() {
+ return imageResIds.length;
+ }
+
+ &#64;Override
+ public Object getItem(int position) {
+ return imageResIds[position];
+ }
+
+ &#64;Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ &#64;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 {
+ ...
+
+ &#64;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.
+ &#64;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.
+ &#64;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> {
+ ...
+
+ &#64;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);
}
/**