Merge "Add input filter mechanism for accessibility."
diff --git a/Android.mk b/Android.mk
index aae10dc..3902971 100644
--- a/Android.mk
+++ b/Android.mk
@@ -370,6 +370,7 @@
     -since ./frameworks/base/api/9.xml 9 \
     -since ./frameworks/base/api/10.xml 10 \
     -since ./frameworks/base/api/11.xml 11 \
+    -since ./frameworks/base/api/12.xml 12 \
 		-werror -hide 113 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
diff --git a/api/current.xml b/api/current.xml
index ded1d1b..58aa1fd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -15703,6 +15703,17 @@
  visibility="public"
 >
 </field>
+<field name="Theme_Holo_Light_NoActionBar"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16974064"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Theme_Holo_Light_Panel"
  type="int"
  transient="false"
@@ -259887,7 +259898,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="appWidgetId" type="int">
@@ -259897,6 +259908,21 @@
 <parameter name="intent" type="android.content.Intent">
 </parameter>
 </method>
+<method name="setRemoteAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
 <method name="setScrollPosition"
  return="void"
  abstract="false"
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0c6ab9e..c8cb1de 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -95,7 +95,7 @@
      * Defines the UID/GID for the NFC service process.
      * @hide
      */
-    public static final int NFC_UID = 1022;
+    public static final int NFC_UID = 1023;
 
     /**
      * Defines the GID for the group that allows write access to the internal media storage.
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index d426d124..6e2168b 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -58,6 +58,13 @@
             int flags, float[] advances, int advancesIndex, Paint paint);
 
     /**
+     * Just like {@link Paint#getTextRunAdvances}.
+     * @hide
+     */
+    float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex, Paint paint);
+
+    /**
      * Just like {@link Paint#getTextRunCursor}.
      * @hide
      */
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index ea5cdfe..ff6a4cd 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1170,6 +1170,35 @@
     }
 
     /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd, int flags,
+            float[] advances, int advancesPos, Paint p) {
+
+        float ret;
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+
+        if (end <= mGapStart) {
+            ret = p.getTextRunAdvancesICU(mText, start, len, contextStart, contextLen,
+                    flags, advances, advancesPos);
+        } else if (start >= mGapStart) {
+            ret = p.getTextRunAdvancesICU(mText, start + mGapLength, len,
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            ret = p.getTextRunAdvancesICU(buf, start - contextStart, len,
+                    0, contextLen, flags, advances, advancesPos);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    /**
      * Returns the next cursor position in the run.  This avoids placing the cursor between
      * surrogates, between characters that form conjuncts, between base characters and combining
      * marks, or within a reordering cluster.
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 3ce0730..e21a02e 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -857,6 +857,7 @@
         String cacheControl = headers.getCacheControl();
         if (cacheControl != null) {
             String[] controls = cacheControl.toLowerCase().split("[ ,;]");
+            boolean noCache = false;
             for (int i = 0; i < controls.length; i++) {
                 if (NO_STORE.equals(controls[i])) {
                     return null;
@@ -867,7 +868,12 @@
                 // can only be used in CACHE_MODE_CACHE_ONLY case
                 if (NO_CACHE.equals(controls[i])) {
                     ret.expires = 0;
-                } else if (controls[i].startsWith(MAX_AGE)) {
+                    noCache = true;
+                // if cache control = no-cache has been received, ignore max-age
+                // header, according to http spec:
+                // If a request includes the no-cache directive, it SHOULD NOT
+                // include min-fresh, max-stale, or max-age.
+                } else if (controls[i].startsWith(MAX_AGE) && !noCache) {
                     int separator = controls[i].indexOf('=');
                     if (separator < 0) {
                         separator = controls[i].indexOf(':');
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 9636513..3d15968 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -198,8 +198,6 @@
 
         if (mProgressView != null) {
             mProgressView.setVisibility(View.GONE);
-            mLayout.removeView(mProgressView);
-            mProgressView = null;
         }
 
         mVideoWidth = mp.getVideoWidth();
@@ -321,4 +319,13 @@
         return false;
     }
 
+    @Override
+    protected void switchProgressView(boolean playerBuffering) {
+        if (playerBuffering) {
+            mProgressView.setVisibility(View.VISIBLE);
+        } else {
+            mProgressView.setVisibility(View.GONE);
+        }
+        return;
+    }
 }
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index ad6e5d3..fd3f358 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -78,6 +78,7 @@
                         TIMEUPDATE_PERIOD);
             }
             mPlayer.start();
+            setPlayerBuffering(false);
         }
     }
 
@@ -296,4 +297,21 @@
         return 0;
     }
 
+    // This is true only when the player is buffering and paused
+    public boolean mPlayerBuffering = false;
+
+    public boolean getPlayerBuffering() {
+        return mPlayerBuffering;
+    }
+
+    public void setPlayerBuffering(boolean playerBuffering) {
+        mPlayerBuffering = playerBuffering;
+        switchProgressView(playerBuffering);
+    }
+
+
+    protected void switchProgressView(boolean playerBuffering) {
+        // Only used in HTML5VideoFullScreen
+    }
+
 }
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 0ee1566..14157c2 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -95,8 +95,10 @@
         // identify the exact layer on the UI thread to use the SurfaceTexture.
         private static int mBaseLayer = 0;
 
-        // This is true only when the player is buffering and paused
-        private static boolean mPlayerBuffering = false;
+        private static void setPlayerBuffering(boolean playerBuffering) {
+            mHTML5VideoView.setPlayerBuffering(playerBuffering);
+        }
+
         // Every time webView setBaseLayer, this will be called.
         // When we found the Video layer, then we set the Surface Texture to it.
         // Otherwise, we may want to delete the Surface Texture to save memory.
@@ -111,7 +113,7 @@
                 int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
                 if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
                     int playerState = mHTML5VideoView.getCurrentState();
-                    if (mPlayerBuffering)
+                    if (mHTML5VideoView.getPlayerBuffering())
                         playerState = HTML5VideoView.STATE_NOTPREPARED;
                     boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
                             layer, currentVideoLayerId, textureName,
@@ -166,7 +168,6 @@
                 WebChromeClient client, int videoLayerId) {
             int currentVideoLayerId = -1;
             boolean backFromFullScreenMode = false;
-            mPlayerBuffering = false;
             if (mHTML5VideoView != null) {
                 currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
                 if (mHTML5VideoView instanceof HTML5VideoFullScreen) {
@@ -231,7 +232,6 @@
         }
 
         public static void onPrepared() {
-            mPlayerBuffering = false;
             // The VideoView will decide whether to really kick off to play.
             mHTML5VideoView.start();
             if (mBaseLayer != 0) {
@@ -350,11 +350,11 @@
                 break;
             }
             case BUFFERING_START: {
-                VideoPlayer.mPlayerBuffering = true;
+                VideoPlayer.setPlayerBuffering(true);
                 break;
             }
             case BUFFERING_END: {
-                VideoPlayer.mPlayerBuffering = false;
+                VideoPlayer.setPlayerBuffering(false);
                 break;
             }
         }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c854fac..9cf2718 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -125,7 +125,7 @@
      *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
      */
     private abstract static class Action implements Parcelable {
-        public abstract void apply(View root) throws ActionException;
+        public abstract void apply(View root, ViewGroup rootParent) throws ActionException;
 
         public int describeContents() {
             return 0;
@@ -183,7 +183,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View view = root.findViewById(viewId);
             if (!(view instanceof AdapterView<?>)) return;
 
@@ -214,7 +214,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View target = root.findViewById(viewId);
             if (target == null) return;
 
@@ -295,7 +295,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View target = root.findViewById(viewId);
             if (target == null) return;
 
@@ -360,6 +360,60 @@
         public final static int TAG = 8;
     }
 
+    private class SetRemoteViewsAdapterIntent extends Action {
+        public SetRemoteViewsAdapterIntent(int id, Intent intent) {
+            this.viewId = id;
+            this.intent = intent;
+        }
+
+        public SetRemoteViewsAdapterIntent(Parcel parcel) {
+            viewId = parcel.readInt();
+            intent = Intent.CREATOR.createFromParcel(parcel);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            intent.writeToParcel(dest, flags);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent) {
+            final View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            // Ensure that we are applying to an AppWidget root
+            if (!(rootParent instanceof AppWidgetHostView)) {
+                Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " +
+                        "AppWidgets (root id: " + viewId + ")");
+                return;
+            }
+            // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
+            if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
+                Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " +
+                        "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
+                return;
+            }
+
+            // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
+            // RemoteViewsService
+            AppWidgetHostView host = (AppWidgetHostView) rootParent;
+            intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
+            if (target instanceof AbsListView) {
+                AbsListView v = (AbsListView) target;
+                v.setRemoteViewsAdapter(intent);
+            } else if (target instanceof AdapterViewAnimator) {
+                AdapterViewAnimator v = (AdapterViewAnimator) target;
+                v.setRemoteViewsAdapter(intent);
+            }
+        }
+
+        int viewId;
+        Intent intent;
+
+        public final static int TAG = 10;
+    }
+
     /**
      * Equivalent to calling
      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
@@ -383,7 +437,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View target = root.findViewById(viewId);
             if (target == null) return;
 
@@ -479,7 +533,7 @@
         }
         
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View target = root.findViewById(viewId);
             if (target == null) return;
             
@@ -539,7 +593,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View view = root.findViewById(viewId);
             if (view == null) return;
 
@@ -755,7 +809,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final View view = root.findViewById(viewId);
             if (view == null) return;
 
@@ -850,7 +904,7 @@
         }
 
         @Override
-        public void apply(View root) {
+        public void apply(View root, ViewGroup rootParent) {
             final Context context = root.getContext();
             final ViewGroup target = (ViewGroup) root.findViewById(viewId);
             if (target == null) return;
@@ -952,6 +1006,9 @@
                 case SetOnClickFillInIntent.TAG:
                     mActions.add(new SetOnClickFillInIntent(parcel));
                     break;
+                case SetRemoteViewsAdapterIntent.TAG:
+                    mActions.add(new SetRemoteViewsAdapterIntent(parcel));
+                    break;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
@@ -1287,16 +1344,29 @@
     /**
      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
      *
-     * @param appWidgetId The id of the app widget which contains the specified view
+     * @param appWidgetId The id of the app widget which contains the specified view. (This
+     *      parameter is ignored in this deprecated method)
+     * @param viewId The id of the view whose text should change
+     * @param intent The intent of the service which will be
+     *            providing data to the RemoteViewsAdapter
+     * @deprecated This method has been deprecated. See
+     *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
+     */
+    @Deprecated
+    public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
+        setRemoteAdapter(viewId, intent);
+    }
+
+    /**
+     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+     * Can only be used for App Widgets.
+     *
      * @param viewId The id of the view whose text should change
      * @param intent The intent of the service which will be
      *            providing data to the RemoteViewsAdapter
      */
-    public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
-        // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
-        // RemoteViewsService
-        intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, appWidgetId);
-        setIntent(viewId, "setRemoteViewsAdapter", intent);
+    public void setRemoteAdapter(int viewId, Intent intent) {
+        addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
     }
 
     /**
@@ -1499,7 +1569,7 @@
 
         result = inflater.inflate(mLayoutId, parent, false);
 
-        performApply(result);
+        performApply(result, parent);
 
         return result;
     }
@@ -1514,15 +1584,15 @@
      */
     public void reapply(Context context, View v) {
         prepareContext(context);
-        performApply(v);
+        performApply(v, (ViewGroup) v.getParent());
     }
 
-    private void performApply(View v) {
+    private void performApply(View v, ViewGroup parent) {
         if (mActions != null) {
             final int count = mActions.size();
             for (int i = 0; i < count; i++) {
                 Action a = mActions.get(i);
-                a.apply(v);
+                a.apply(v, parent);
             }
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index baf20a1..9e482b4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2951,6 +2951,16 @@
                     advancesIndex);
         }
 
+        public float getTextRunAdvancesICU(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvancesICU(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex);
+        }
+
         public int getTextRunCursor(int contextStart, int contextEnd, int flags,
                 int offset, int cursorOpt, Paint p) {
             int contextCount = contextEnd - contextStart;
diff --git a/core/jni/android/graphics/HarfbuzzSkia.cpp b/core/jni/android/graphics/HarfbuzzSkia.cpp
index 58fb32b..92c743f 100644
--- a/core/jni/android/graphics/HarfbuzzSkia.cpp
+++ b/core/jni/android/graphics/HarfbuzzSkia.cpp
@@ -34,6 +34,8 @@
 #include "SkRect.h"
 #include "SkTypeface.h"
 
+#include <utils/Log.h>
+
 extern "C" {
 #include "harfbuzz-shaper.h"
 }
@@ -43,20 +45,13 @@
 
 namespace android {
 
-static HB_Fixed SkiaScalarToHarfbuzzFixed(SkScalar value)
-{
-    // HB_Fixed is a 26.6 fixed point format.
-    return value * 64;
-}
-
 static void setupPaintWithFontData(SkPaint* paint, FontData* data) {
-    paint->setAntiAlias(true);
-    paint->setSubpixelText(true);
-    paint->setHinting(SkPaint::kSlight_Hinting);
-    paint->setTextSize(SkFloatToScalar(data->textSize));
     paint->setTypeface(data->typeFace);
-    paint->setFakeBoldText(data->fakeBold);
-    paint->setTextSkewX(data->fakeItalic ? -SK_Scalar1/4 : 0);
+    paint->setTextSize(data->textSize);
+    paint->setTextSkewX(data->textSkewX);
+    paint->setTextScaleX(data->textScaleX);
+    paint->setFlags(data->flags);
+    paint->setHinting(data->hinting);
 }
 
 static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length,
@@ -67,16 +62,13 @@
     setupPaintWithFontData(&paint, data);
 
     paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
-    int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t),
-            reinterpret_cast<uint16_t*>(glyphs));
+    uint16_t* skiaGlyphs = reinterpret_cast<uint16_t*>(glyphs);
+    int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), skiaGlyphs);
 
     // HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our
     // |glyphs| array needs to be converted.
     for (int i = numGlyphs - 1; i >= 0; --i) {
-        uint16_t value;
-        // We use a memcpy to avoid breaking strict aliasing rules.
-        memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(value));
-        glyphs[i] = value;
+        glyphs[i] = skiaGlyphs[i];
     }
 
     *glyphsSize = numGlyphs;
@@ -97,16 +89,17 @@
         return;
     for (unsigned i = 0; i < numGlyphs; ++i)
         glyphs16[i] = glyphs[i];
-    paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), reinterpret_cast<SkScalar*>(advances));
+    SkScalar* scalarAdvances = reinterpret_cast<SkScalar*>(advances);
+    paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), scalarAdvances);
 
     // The |advances| values which Skia outputs are SkScalars, which are floats
     // in Chromium. However, Harfbuzz wants them in 26.6 fixed point format.
     // These two formats are both 32-bits long.
     for (unsigned i = 0; i < numGlyphs; ++i) {
-        float value;
-        // We use a memcpy to avoid breaking strict aliasing rules.
-        memcpy(&value, reinterpret_cast<char*>(advances) + sizeof(float) * i, sizeof(value));
-        advances[i] = SkiaScalarToHarfbuzzFixed(value);
+        advances[i] = SkScalarToHBFixed(scalarAdvances[i]);
+#if DEBUG_ADVANCES
+        LOGD("glyphsToAdvances -- advances[%d]=%d", i, advances[i]);
+#endif
     }
     delete glyphs16;
 }
@@ -156,8 +149,8 @@
         return HB_Err_Invalid_SubTable;
     // Skia does let us get a single point from the path.
     path.getPoints(points, point + 1);
-    *xPos = SkiaScalarToHarfbuzzFixed(points[point].fX);
-    *yPos = SkiaScalarToHarfbuzzFixed(points[point].fY);
+    *xPos = SkScalarToHBFixed(points[point].fX);
+    *yPos = SkScalarToHBFixed(points[point].fY);
     *resultingNumPoints = numPoints;
     delete points;
 
@@ -176,12 +169,12 @@
     SkRect bounds;
     paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds);
 
-    metrics->x = SkiaScalarToHarfbuzzFixed(bounds.fLeft);
-    metrics->y = SkiaScalarToHarfbuzzFixed(bounds.fTop);
-    metrics->width = SkiaScalarToHarfbuzzFixed(bounds.width());
-    metrics->height = SkiaScalarToHarfbuzzFixed(bounds.height());
+    metrics->x = SkScalarToHBFixed(bounds.fLeft);
+    metrics->y = SkScalarToHBFixed(bounds.fTop);
+    metrics->width = SkScalarToHBFixed(bounds.width());
+    metrics->height = SkScalarToHBFixed(bounds.height());
 
-    metrics->xOffset = SkiaScalarToHarfbuzzFixed(width);
+    metrics->xOffset = SkScalarToHBFixed(width);
     // We can't actually get the |y| correct because Skia doesn't export
     // the vertical advance. However, nor we do ever render vertical text at
     // the moment so it's unimportant.
@@ -199,7 +192,7 @@
 
     switch (metric) {
     case HB_FontAscent:
-        return SkiaScalarToHarfbuzzFixed(-skiaMetrics.fAscent);
+        return SkScalarToHBFixed(-skiaMetrics.fAscent);
     // We don't support getting the rest of the metrics and Harfbuzz doesn't seem to need them.
     default:
         return 0;
diff --git a/core/jni/android/graphics/HarfbuzzSkia.h b/core/jni/android/graphics/HarfbuzzSkia.h
index d057d76..99b389a 100644
--- a/core/jni/android/graphics/HarfbuzzSkia.h
+++ b/core/jni/android/graphics/HarfbuzzSkia.h
@@ -27,22 +27,38 @@
 #ifndef HarfbuzzSkia_h
 #define HarfbuzzSkia_h
 
+#include "SkScalar.h"
 #include "SkTypeface.h"
+#include "SkPaint.h"
 
 extern "C" {
 #include "harfbuzz-shaper.h"
 }
 
 namespace android {
-    typedef struct {
-        SkTypeface* typeFace;
-        float textSize;
-        bool fakeBold;
-        bool fakeItalic;
-    } FontData;
 
-    HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len);
-    extern const HB_FontClass harfbuzzSkiaClass;
+static inline float HBFixedToFloat(HB_Fixed v) {
+    // Harfbuzz uses 26.6 fixed point values for pixel offsets
+    return v * (1.0f / 64);
+}
+
+static inline HB_Fixed SkScalarToHBFixed(SkScalar value) {
+    // HB_Fixed is a 26.6 fixed point format.
+    return SkScalarToFloat(value) * 64.0f;
+}
+
+typedef struct {
+    SkTypeface* typeFace;
+    SkScalar textSize;
+    SkScalar textSkewX;
+    SkScalar textScaleX;
+    uint32_t flags;
+    SkPaint::Hinting hinting;
+} FontData;
+
+HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len);
+extern const HB_FontClass harfbuzzSkiaClass;
+
 }  // namespace android
 
 #endif
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 5c3497f..27be871 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -414,6 +414,7 @@
         for (int i = 0; i < glyphCount; i++) {
             glyphsArray[i] = (jchar) shaperItem.glyphs[i];
         }
+        env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT);
         return glyphCount;
     }
 
@@ -442,6 +443,21 @@
         return totalAdvance;
     }
 
+    static jfloat doTextRunAdvancesICU(JNIEnv *env, SkPaint *paint, const jchar *text,
+                                    jint start, jint count, jint contextCount, jint flags,
+                                    jfloatArray advances, jint advancesIndex) {
+        jfloat advancesArray[count];
+        jfloat totalAdvance;
+
+        TextLayout::getTextRunAdvancesICU(paint, text, start, count, contextCount, flags,
+                                       advancesArray, totalAdvance);
+
+        if (advances != NULL) {
+            env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
+        }
+        return totalAdvance;
+    }
+
     static float getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
             jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
             jint flags, jfloatArray advances, jint advancesIndex) {
@@ -463,6 +479,27 @@
         return result;
     }
 
+    static float getTextRunAdvancesICU___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
+            jint flags, jfloatArray advances, jint advancesIndex) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextIndex,
+            index - contextIndex, count, contextCount, flags, advances, advancesIndex);
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+        return result;
+    }
+
+    static float getTextRunAdvancesICU__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jfloatArray advances, jint advancesIndex) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextStart,
+            start - contextStart, end - start, contextEnd - contextStart, flags, advances,
+            advancesIndex);
+        env->ReleaseStringChars(text, textArray);
+        return result;
+    }
+
     static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
             jint count, jint flags, jint offset, jint opt) {
         SkScalar scalarArray[count];
@@ -748,10 +785,14 @@
     {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
     {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
     {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
-    {"native_getTextRunAdvances","(I[CIIIII[FI)F", (void*)
-        SkPaintGlue::getTextRunAdvances___CIIIII_FI},
+    {"native_getTextRunAdvances","(I[CIIIII[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FI},
     {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F",
         (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
+    {"native_getTextRunAdvancesICU","(I[CIIIII[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvancesICU___CIIIII_FI},
+    {"native_getTextRunAdvancesICU","(ILjava/lang/String;IIIII[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvancesICU__StringIIIII_FI},
     {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I",
         (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
     {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h
index 2c68fa3..f41f4a1 100644
--- a/core/jni/android/graphics/RtlProperties.h
+++ b/core/jni/android/graphics/RtlProperties.h
@@ -45,7 +45,12 @@
     return kRtlDebugDisabled;
 }
 
+// Define if we want to use Harfbuzz (1) or not (0)
 #define RTL_USE_HARFBUZZ 1
 
+// Define if we want (1) to have Advances debug values or not (0)
+#define DEBUG_ADVANCES 0
+
+
 } // namespace android
 #endif // ANDROID_RTL_PROPERTIES_H
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index f1bb696..434f63b 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -269,6 +269,21 @@
 #endif
 }
 
+void TextLayout::getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat* resultAdvances, jfloat& resultTotalAdvance) {
+    // Compute advances and return them
+    RunAdvanceDescription::computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
+            resultAdvances, &resultTotalAdvance);
+}
+
+void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat* resultAdvances, jfloat& resultTotalAdvance) {
+    // Compute advances and return them
+    RunAdvanceDescription::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
+            resultAdvances, &resultTotalAdvance);
+}
 
 // Draws a paragraph of text on a single line, running bidi and shaping
 void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index a950d13..138983c 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -73,6 +73,14 @@
                                    jint count, jint contextCount, jint dirFlags,
                                    jfloat* resultAdvances, jfloat& resultTotalAdvance);
 
+    static void getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat* resultAdvances, jfloat& resultTotalAdvance);
+
+    static void getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat* resultAdvances, jfloat& resultTotalAdvance);
+
     static void drawText(SkPaint* paint, const jchar* text, jsize len,
                          jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
 
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 925bb7c..31d802c 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -19,17 +19,20 @@
 
 #include "RtlProperties.h"
 
-#include "stddef.h"
+#include <stddef.h>
 #include <utils/threads.h>
 #include <utils/String16.h>
-#include "utils/GenerationCache.h"
-#include "utils/Compare.h"
+#include <utils/GenerationCache.h>
+#include <utils/Compare.h>
 
-#include "SkPaint.h"
-#include "SkTemplates.h"
+#include <SkPaint.h>
+#include <SkTemplates.h>
+#include <SkUtils.h>
+#include <SkScalerContext.h>
+#include <SkAutoKern.h>
 
-#include "unicode/ubidi.h"
-#include "unicode/ushape.h"
+#include <unicode/ubidi.h>
+#include <unicode/ushape.h>
 #include "HarfbuzzSkia.h"
 #include "harfbuzz-shaper.h"
 
@@ -54,14 +57,8 @@
 // Define the interval in number of cache hits between two statistics dump
 #define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
 
-// Define if we want to have Advances debug values
-#define DEBUG_ADVANCES 0
-
 namespace android {
 
-// Harfbuzz uses 26.6 fixed point values for pixel offsets
-#define HB_FIXED_TO_FLOAT(v) (((float) v) * (1.0 / 64))
-
 /**
  * TextLayoutCacheKey is the Cache key
  */
@@ -202,6 +199,8 @@
         shaperItem->font = font;
         shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
 
+        shaperItem->kerning_applied = false;
+
         // We cannot know, ahead of time, how many glyphs a given script run
         // will produce. We take a guess that script runs will not produce more
         // than twice as many glyphs as there are code points plus a bit of
@@ -222,10 +221,12 @@
         shaperItem->string = chars;
         shaperItem->stringLength = contextCount;
 
-        fontData->textSize = paint->getTextSize();
-        fontData->fakeBold = paint->isFakeBoldText();
-        fontData->fakeItalic = (paint->getTextSkewX() > 0);
         fontData->typeFace = paint->getTypeface();
+        fontData->textSize = paint->getTextSize();
+        fontData->textSkewX = paint->getTextSkewX();
+        fontData->textScaleX = paint->getTextScaleX();
+        fontData->flags = paint->getFlags();
+        fontData->hinting = paint->getHinting();
 
         shaperItem->font->userData = fontData;
     }
@@ -248,6 +249,21 @@
         }
     }
 
+#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+
+    static int adjust(int prev, int next) {
+        int delta = next - prev;
+        if (delta >= 32) {
+            return -1;
+        }
+        else if (delta < -32) {
+            return +1;
+        }
+        else {
+            return 0;
+        }
+    }
+
     static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start,
             size_t count, size_t contextCount, int dirFlags,
             jfloat* outAdvances, jfloat* outTotalAdvance) {
@@ -261,13 +277,15 @@
                 contextCount, dirFlags);
 
 #if DEBUG_ADVANCES
-        LOGD("HARFBUZZ -- num_glypth=%d", shaperItem.num_glyphs);
+        LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, shaperItem.kerning_applied);
+        LOGD("         -- string= '%s'", String8(chars, contextCount).string());
+        LOGD("         -- isDevKernText=%d", paint->isDevKernText());
 #endif
 
         jfloat totalAdvance = 0;
+
         for (size_t i = 0; i < count; i++) {
-            // Be careful: we need to use ceilf() for doing the same way as what Skia is doing
-            totalAdvance += outAdvances[i] = ceilf(HB_FIXED_TO_FLOAT(shaperItem.advances[i]));
+            totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]);
 
 #if DEBUG_ADVANCES
             LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0c9a2ef..348ded7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1031,7 +1031,7 @@
     <permission android:name="android.permission.STOP_APP_SWITCHES"
         android:label="@string/permlab_stopAppSwitches"
         android:description="@string/permdesc_stopAppSwitches"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signatureOrSystem" />
 
     <!-- Allows an application to retrieve the current state of keys and
          switches.  This is only for use by the system.-->
diff --git a/core/res/res/drawable-hdpi/ic_media_video_poster.png b/core/res/res/drawable-hdpi/ic_media_video_poster.png
index 6c1fd6b..77b6b0e 100644
--- a/core/res/res/drawable-hdpi/ic_media_video_poster.png
+++ b/core/res/res/drawable-hdpi/ic_media_video_poster.png
Binary files differ
diff --git a/core/res/res/drawable-ldpi/ic_media_video_poster.png b/core/res/res/drawable-ldpi/ic_media_video_poster.png
index 786d0e6..7b34913 100644
--- a/core/res/res/drawable-ldpi/ic_media_video_poster.png
+++ b/core/res/res/drawable-ldpi/ic_media_video_poster.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_video_poster.png b/core/res/res/drawable-mdpi/ic_media_video_poster.png
old mode 100644
new mode 100755
index 10bbd74..f457f23
--- a/core/res/res/drawable-mdpi/ic_media_video_poster.png
+++ b/core/res/res/drawable-mdpi/ic_media_video_poster.png
Binary files differ
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cca7d8b..968d99c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -34,6 +34,8 @@
     <dimen name="status_bar_height">25dip</dimen>
     <!-- Height of the status bar -->
     <dimen name="status_bar_icon_size">25dip</dimen>
+    <!-- Size of the giant number (unread count) in the notifications -->
+    <dimen name="status_bar_content_number_size">48sp</dimen>
     <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
     <dimen name="status_bar_edge_ignore">5dp</dimen>
     <!-- Margin for permanent screen decorations at the bottom. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0fefbf2..4109ae1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1657,4 +1657,6 @@
   <public type="attr" name="state_drag_can_accept" />
   <public type="attr" name="state_drag_hovered" />
 
+  <public type="style" name="Theme.Holo.Light.NoActionBar" />
+
 </resources>
diff --git a/docs/html/guide/market/billing/billing_integrate.jd b/docs/html/guide/market/billing/billing_integrate.jd
index 26bda66..56e471e 100755
--- a/docs/html/guide/market/billing/billing_integrate.jd
+++ b/docs/html/guide/market/billing/billing_integrate.jd
@@ -296,7 +296,7 @@
 <pre>
 try {
   boolean bindResult = mContext.bindService(
-    new Intent(IMarketBillingService.class.getName()), this, Context.BIND_AUTO_CREATE);
+    new Intent("com.android.vending.billing.MarketBillingService.BIND"), this, Context.BIND_AUTO_CREATE);
   if (bindResult) {
     Log.i(TAG, "Service bind successful.");
   } else {
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 96eb936..0949beb 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1534,6 +1534,48 @@
     }
 
     /**
+     * Convenience overload that takes a char array instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvancesICU(char[] chars, int index, int count,
+            int contextIndex, int contextCount, int flags, float[] advances,
+            int advancesIndex) {
+
+        if ((index | count | contextIndex | contextCount | advancesIndex
+                | (index - contextIndex)
+                | ((contextIndex + contextCount) - (index + count))
+                | (chars.length - (contextIndex + contextCount))
+                | (advances == null ? 0 :
+                    (advances.length - (advancesIndex + count)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+
+        if (!mHasCompatScaling) {
+            return native_getTextRunAdvancesICU(mNativePaint, chars, index, count,
+                    contextIndex, contextCount, flags, advances, advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        float res = native_getTextRunAdvancesICU(mNativePaint, chars, index, count,
+                contextIndex, contextCount, flags, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + count; i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return res * mInvCompatScaling; // assume errors are not significant
+    }
+
+    /**
      * Convenience overload that takes a CharSequence instead of a
      * String.
      *
@@ -1569,6 +1611,41 @@
     }
 
     /**
+     * Convenience overload that takes a CharSequence instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvancesICU(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, int flags, float[] advances,
+            int advancesIndex) {
+
+        if (text instanceof String) {
+            return getTextRunAdvancesICU((String) text, start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+        if (text instanceof SpannedString ||
+            text instanceof SpannableString) {
+            return getTextRunAdvancesICU(text.toString(), start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextRunAdvancesICU(start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex, this);
+        }
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+        char[] buf = TemporaryBuffer.obtain(contextLen);
+        TextUtils.getChars(text, start, end, buf, 0);
+        float result = getTextRunAdvancesICU(buf, start - contextStart, len,
+                0, contextLen, flags, advances, advancesIndex);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
      * Returns the total advance width for the characters in the run
      * between start and end, and if advances is not null, the advance
      * assigned to each of these characters (java chars).
@@ -1644,6 +1721,44 @@
     }
 
     /**
+     * Temporary - DO NOT USE
+     *
+     * @hide
+     */
+    public float getTextRunAdvancesICU(String text, int start, int end, int contextStart,
+            int contextEnd, int flags, float[] advances, int advancesIndex) {
+
+        if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
+                | (start - contextStart) | (contextEnd - end)
+                | (text.length() - contextEnd)
+                | (advances == null ? 0 :
+                    (advances.length - advancesIndex - (end - start)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+
+        if (!mHasCompatScaling) {
+            return native_getTextRunAdvancesICU(mNativePaint, text, start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
+                contextStart, contextEnd, flags, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return totalAdvance * mInvCompatScaling; // assume errors are insignificant
+    }
+
+    /**
      * Returns the next cursor position in the run.  This avoids placing the
      * cursor between surrogates, between characters that form conjuncts,
      * between base characters and combining marks, or within a reordering
@@ -1907,6 +2022,13 @@
             String text, int start, int end, int contextStart, int contextEnd,
             int flags, float[] advances, int advancesIndex);
 
+    private static native float native_getTextRunAdvancesICU(int native_object,
+            char[] text, int index, int count, int contextIndex, int contextCount,
+            int flags, float[] advances, int advancesIndex);
+    private static native float native_getTextRunAdvancesICU(int native_object,
+            String text, int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex);
+
     private native int native_getTextRunCursor(int native_object, char[] text,
             int contextStart, int contextLength, int flags, int offset, int cursorOpt);
     private native int native_getTextRunCursor(int native_object, String text,
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index d30e908..6b6fcdf 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -84,6 +84,8 @@
         return String8();
     }
 
+    virtual String8 getMIMEType() const;
+
 protected:
     virtual ~DataSource() {}
 
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index 66dfff6..6a21627 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -45,6 +45,7 @@
 extern const char *MEDIA_MIMETYPE_CONTAINER_OGG;
 extern const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA;
 extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS;
+extern const char *MEDIA_MIMETYPE_CONTAINER_AVI;
 
 extern const char *MEDIA_MIMETYPE_CONTAINER_WVM;
 
diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h
index 972e799..0a6e4fb 100644
--- a/include/ui/egl/android_natives.h
+++ b/include/ui/egl/android_natives.h
@@ -291,6 +291,9 @@
     void* reserved_proc[2];
 };
 
+// Backwards compatibility...  please switch to ANativeWindow.
+typedef struct ANativeWindow android_native_window_t;
+
 /*
  *  native_window_set_usage(..., usage)
  *  Sets the intended usage flags for the next buffers
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index c41e0ad..352579a 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -66,8 +66,9 @@
     public static final int FILE_TYPE_ASF     = 26;
     public static final int FILE_TYPE_MKV     = 27;
     public static final int FILE_TYPE_MP2TS   = 28;
+    public static final int FILE_TYPE_AVI     = 29;
     private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4;
-    private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_MP2TS;
+    private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_AVI;
     
     // Image file types
     public static final int FILE_TYPE_JPEG    = 31;
@@ -198,6 +199,7 @@
         addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska");
         addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska");
         addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts");
+        addFileType("AVI", FILE_TYPE_AVI, "video/avi");
 
         if (isWMVEnabled()) {
             addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV);
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 7fdf448..7c181ee 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -83,7 +83,7 @@
      *
      * @param filePath the path of image file
      * @param kind could be MINI_KIND or MICRO_KIND
-     * @return Bitmap
+     * @return Bitmap, or null on failures
      *
      * @hide This method is only used by media framework and media provider internally.
      */
@@ -123,6 +123,8 @@
                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
             } catch (IOException ex) {
                 Log.e(TAG, "", ex);
+            } catch (OutOfMemoryError oom) {
+                Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom);
             }
         }
 
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 346d0bb..9928f44 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -1081,8 +1081,8 @@
                 android_native_rect_t crop;
                 crop.left = rect.nLeft;
                 crop.top = rect.nTop;
-                crop.right = rect.nLeft + rect.nWidth - 1;
-                crop.bottom = rect.nTop + rect.nHeight - 1;
+                crop.right = rect.nLeft + rect.nWidth;
+                crop.bottom = rect.nTop + rect.nHeight;
 
                 CHECK_EQ(0, native_window_set_crop(
                             mNativeWindow.get(), &crop));
diff --git a/media/libstagefright/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp
new file mode 100644
index 0000000..6313ca3
--- /dev/null
+++ b/media/libstagefright/AVIExtractor.cpp
@@ -0,0 +1,922 @@
+/*
+ * Copyright (C) 2011 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 "AVIExtractor"
+#include <utils/Log.h>
+
+#include "include/AVIExtractor.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct AVIExtractor::AVISource : public MediaSource {
+    AVISource(const sp<AVIExtractor> &extractor, size_t trackIndex);
+
+    virtual status_t start(MetaData *params);
+    virtual status_t stop();
+
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options);
+
+protected:
+    virtual ~AVISource();
+
+private:
+    sp<AVIExtractor> mExtractor;
+    size_t mTrackIndex;
+    const AVIExtractor::Track &mTrack;
+    MediaBufferGroup *mBufferGroup;
+    size_t mSampleIndex;
+
+    DISALLOW_EVIL_CONSTRUCTORS(AVISource);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+AVIExtractor::AVISource::AVISource(
+        const sp<AVIExtractor> &extractor, size_t trackIndex)
+    : mExtractor(extractor),
+      mTrackIndex(trackIndex),
+      mTrack(mExtractor->mTracks.itemAt(trackIndex)),
+      mBufferGroup(NULL) {
+}
+
+AVIExtractor::AVISource::~AVISource() {
+    if (mBufferGroup) {
+        stop();
+    }
+}
+
+status_t AVIExtractor::AVISource::start(MetaData *params) {
+    CHECK(!mBufferGroup);
+
+    mBufferGroup = new MediaBufferGroup;
+
+    mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize));
+    mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize));
+    mSampleIndex = 0;
+
+    return OK;
+}
+
+status_t AVIExtractor::AVISource::stop() {
+    CHECK(mBufferGroup);
+
+    delete mBufferGroup;
+    mBufferGroup = NULL;
+
+    return OK;
+}
+
+sp<MetaData> AVIExtractor::AVISource::getFormat() {
+    return mTrack.mMeta;
+}
+
+status_t AVIExtractor::AVISource::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    CHECK(mBufferGroup);
+
+    *buffer = NULL;
+
+    int64_t seekTimeUs;
+    ReadOptions::SeekMode seekMode;
+    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
+        status_t err =
+            mExtractor->getSampleIndexAtTime(
+                    mTrackIndex, seekTimeUs, seekMode, &mSampleIndex);
+
+        if (err != OK) {
+            return ERROR_END_OF_STREAM;
+        }
+    }
+
+    int64_t timeUs =
+        (mSampleIndex * 1000000ll * mTrack.mRate) / mTrack.mScale;
+
+    off64_t offset;
+    size_t size;
+    bool isKey;
+    status_t err = mExtractor->getSampleInfo(
+            mTrackIndex, mSampleIndex, &offset, &size, &isKey);
+
+    ++mSampleIndex;
+
+    if (err != OK) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    MediaBuffer *out;
+    CHECK_EQ(mBufferGroup->acquire_buffer(&out), (status_t)OK);
+
+    ssize_t n = mExtractor->mDataSource->readAt(offset, out->data(), size);
+
+    if (n < (ssize_t)size) {
+        return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED;
+    }
+
+    out->set_range(0, size);
+
+    out->meta_data()->setInt64(kKeyTime, timeUs);
+
+    if (isKey) {
+        out->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+    }
+
+    *buffer = out;
+
+    return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+AVIExtractor::AVIExtractor(const sp<DataSource> &dataSource)
+    : mDataSource(dataSource) {
+    mInitCheck = parseHeaders();
+
+    if (mInitCheck != OK) {
+        mTracks.clear();
+    }
+}
+
+AVIExtractor::~AVIExtractor() {
+}
+
+size_t AVIExtractor::countTracks() {
+    return mTracks.size();
+}
+
+sp<MediaSource> AVIExtractor::getTrack(size_t index) {
+    return index < mTracks.size() ? new AVISource(this, index) : NULL;
+}
+
+sp<MetaData> AVIExtractor::getTrackMetaData(
+        size_t index, uint32_t flags) {
+    return index < mTracks.size() ? mTracks.editItemAt(index).mMeta : NULL;
+}
+
+sp<MetaData> AVIExtractor::getMetaData() {
+    sp<MetaData> meta = new MetaData;
+
+    if (mInitCheck == OK) {
+        meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_AVI);
+    }
+
+    return meta;
+}
+
+status_t AVIExtractor::parseHeaders() {
+    mTracks.clear();
+    mMovieOffset = 0;
+    mFoundIndex = false;
+    mOffsetsAreAbsolute = false;
+
+    ssize_t res = parseChunk(0ll, -1ll);
+
+    if (res < 0) {
+        return (status_t)res;
+    }
+
+    if (mMovieOffset == 0ll || !mFoundIndex) {
+        return ERROR_MALFORMED;
+    }
+
+    return OK;
+}
+
+ssize_t AVIExtractor::parseChunk(off64_t offset, off64_t size, int depth) {
+    if (size >= 0 && size < 8) {
+        return ERROR_MALFORMED;
+    }
+
+    uint8_t tmp[12];
+    ssize_t n = mDataSource->readAt(offset, tmp, 8);
+
+    if (n < 8) {
+        return (n < 0) ? n : (ssize_t)ERROR_MALFORMED;
+    }
+
+    uint32_t fourcc = U32_AT(tmp);
+    uint32_t chunkSize = U32LE_AT(&tmp[4]);
+
+    if (size >= 0 && chunkSize + 8 > size) {
+        return ERROR_MALFORMED;
+    }
+
+    static const char kPrefix[] = "                              ";
+    const char *prefix = &kPrefix[strlen(kPrefix) - 2 * depth];
+
+    if (fourcc == FOURCC('L', 'I', 'S', 'T')
+            || fourcc == FOURCC('R', 'I', 'F', 'F')) {
+        // It's a list of chunks
+
+        if (size >= 0 && size < 12) {
+            return ERROR_MALFORMED;
+        }
+
+        n = mDataSource->readAt(offset + 8, &tmp[8], 4);
+
+        if (n < 4) {
+            return (n < 0) ? n : (ssize_t)ERROR_MALFORMED;
+        }
+
+        uint32_t subFourcc = U32_AT(&tmp[8]);
+
+        LOGV("%s offset 0x%08llx LIST of '%c%c%c%c', size %d",
+             prefix,
+             offset,
+             (char)(subFourcc >> 24),
+             (char)((subFourcc >> 16) & 0xff),
+             (char)((subFourcc >> 8) & 0xff),
+             (char)(subFourcc & 0xff),
+             chunkSize - 4);
+
+        if (subFourcc == FOURCC('m', 'o', 'v', 'i')) {
+            // We're not going to parse this, but will take note of the
+            // offset.
+
+            mMovieOffset = offset;
+        } else {
+            off64_t subOffset = offset + 12;
+            off64_t subOffsetLimit = subOffset + chunkSize - 4;
+            while (subOffset < subOffsetLimit) {
+                ssize_t res =
+                    parseChunk(subOffset, subOffsetLimit - subOffset, depth + 1);
+
+                if (res < 0) {
+                    return res;
+                }
+
+                subOffset += res;
+            }
+        }
+    } else {
+        LOGV("%s offset 0x%08llx CHUNK '%c%c%c%c'",
+             prefix,
+             offset,
+             (char)(fourcc >> 24),
+             (char)((fourcc >> 16) & 0xff),
+             (char)((fourcc >> 8) & 0xff),
+             (char)(fourcc & 0xff));
+
+        status_t err = OK;
+
+        switch (fourcc) {
+            case FOURCC('s', 't', 'r', 'h'):
+            {
+                err = parseStreamHeader(offset + 8, chunkSize);
+                break;
+            }
+
+            case FOURCC('s', 't', 'r', 'f'):
+            {
+                err = parseStreamFormat(offset + 8, chunkSize);
+                break;
+            }
+
+            case FOURCC('i', 'd', 'x', '1'):
+            {
+                err = parseIndex(offset + 8, chunkSize);
+                break;
+            }
+
+            default:
+                break;
+        }
+
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    if (chunkSize & 1) {
+        ++chunkSize;
+    }
+
+    return chunkSize + 8;
+}
+
+static const char *GetMIMETypeForHandler(uint32_t handler) {
+    switch (handler) {
+        // Wow... shamelessly copied from
+        // http://wiki.multimedia.cx/index.php?title=ISO_MPEG-4
+
+        case FOURCC('3', 'I', 'V', '2'):
+        case FOURCC('3', 'i', 'v', '2'):
+        case FOURCC('B', 'L', 'Z', '0'):
+        case FOURCC('D', 'I', 'G', 'I'):
+        case FOURCC('D', 'I', 'V', '1'):
+        case FOURCC('d', 'i', 'v', '1'):
+        case FOURCC('D', 'I', 'V', 'X'):
+        case FOURCC('d', 'i', 'v', 'x'):
+        case FOURCC('D', 'X', '5', '0'):
+        case FOURCC('d', 'x', '5', '0'):
+        case FOURCC('D', 'X', 'G', 'M'):
+        case FOURCC('E', 'M', '4', 'A'):
+        case FOURCC('E', 'P', 'H', 'V'):
+        case FOURCC('F', 'M', 'P', '4'):
+        case FOURCC('f', 'm', 'p', '4'):
+        case FOURCC('F', 'V', 'F', 'W'):
+        case FOURCC('H', 'D', 'X', '4'):
+        case FOURCC('h', 'd', 'x', '4'):
+        case FOURCC('M', '4', 'C', 'C'):
+        case FOURCC('M', '4', 'S', '2'):
+        case FOURCC('m', '4', 's', '2'):
+        case FOURCC('M', 'P', '4', 'S'):
+        case FOURCC('m', 'p', '4', 's'):
+        case FOURCC('M', 'P', '4', 'V'):
+        case FOURCC('m', 'p', '4', 'v'):
+        case FOURCC('M', 'V', 'X', 'M'):
+        case FOURCC('R', 'M', 'P', '4'):
+        case FOURCC('S', 'E', 'D', 'G'):
+        case FOURCC('S', 'M', 'P', '4'):
+        case FOURCC('U', 'M', 'P', '4'):
+        case FOURCC('W', 'V', '1', 'F'):
+        case FOURCC('X', 'V', 'I', 'D'):
+        case FOURCC('X', 'v', 'i', 'D'):
+        case FOURCC('x', 'v', 'i', 'd'):
+        case FOURCC('X', 'V', 'I', 'X'):
+            return MEDIA_MIMETYPE_VIDEO_MPEG4;
+
+        default:
+            return NULL;
+    }
+}
+
+status_t AVIExtractor::parseStreamHeader(off64_t offset, size_t size) {
+    if (size != 56) {
+        return ERROR_MALFORMED;
+    }
+
+    if (mTracks.size() > 99) {
+        return -ERANGE;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(size);
+    ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+    if (n < (ssize_t)size) {
+        return n < 0 ? (status_t)n : ERROR_MALFORMED;
+    }
+
+    const uint8_t *data = buffer->data();
+
+    uint32_t type = U32_AT(data);
+    uint32_t handler = U32_AT(&data[4]);
+    uint32_t flags = U32LE_AT(&data[8]);
+
+    sp<MetaData> meta = new MetaData;
+
+    uint32_t rate = U32LE_AT(&data[20]);
+    uint32_t scale = U32LE_AT(&data[24]);
+
+    const char *mime = NULL;
+    Track::Kind kind = Track::OTHER;
+
+    if (type == FOURCC('v', 'i', 'd', 's')) {
+        mime = GetMIMETypeForHandler(handler);
+
+        if (mime && strncasecmp(mime, "video/", 6)) {
+            return ERROR_MALFORMED;
+        }
+
+        kind = Track::VIDEO;
+    } else if (type == FOURCC('a', 'u', 'd', 's')) {
+        if (mime && strncasecmp(mime, "audio/", 6)) {
+            return ERROR_MALFORMED;
+        }
+
+        kind = Track::AUDIO;
+    }
+
+    if (!mime) {
+        mime = "application/octet-stream";
+    }
+
+    meta->setCString(kKeyMIMEType, mime);
+
+    mTracks.push();
+    Track *track = &mTracks.editItemAt(mTracks.size() - 1);
+
+    track->mMeta = meta;
+    track->mRate = rate;
+    track->mScale = scale;
+    track->mKind = kind;
+    track->mNumSyncSamples = 0;
+    track->mThumbnailSampleSize = 0;
+    track->mThumbnailSampleIndex = -1;
+    track->mMaxSampleSize = 0;
+
+    return OK;
+}
+
+status_t AVIExtractor::parseStreamFormat(off64_t offset, size_t size) {
+    if (mTracks.isEmpty()) {
+        return ERROR_MALFORMED;
+    }
+
+    Track *track = &mTracks.editItemAt(mTracks.size() - 1);
+
+    if (track->mKind == Track::OTHER) {
+        // We don't support this content, but that's not a parsing error.
+        return OK;
+    }
+
+    bool isVideo = (track->mKind == Track::VIDEO);
+
+    if ((isVideo && size < 40) || (!isVideo && size < 18)) {
+        // Expected a BITMAPINFO or WAVEFORMATEX structure, respectively.
+        return ERROR_MALFORMED;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(size);
+    ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+    if (n < (ssize_t)size) {
+        return n < 0 ? (status_t)n : ERROR_MALFORMED;
+    }
+
+    const uint8_t *data = buffer->data();
+
+    if (isVideo) {
+        uint32_t width = U32LE_AT(&data[4]);
+        uint32_t height = U32LE_AT(&data[8]);
+
+        track->mMeta->setInt32(kKeyWidth, width);
+        track->mMeta->setInt32(kKeyHeight, height);
+    } else {
+        uint32_t format = U16LE_AT(data);
+        if (format == 0x55) {
+            track->mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+        }
+
+        uint32_t numChannels = U16LE_AT(&data[2]);
+        uint32_t sampleRate = U32LE_AT(&data[4]);
+
+        track->mMeta->setInt32(kKeyChannelCount, numChannels);
+        track->mMeta->setInt32(kKeySampleRate, sampleRate);
+    }
+
+    return OK;
+}
+
+// static
+bool AVIExtractor::IsCorrectChunkType(
+        ssize_t trackIndex, Track::Kind kind, uint32_t chunkType) {
+    uint32_t chunkBase = chunkType & 0xffff;
+
+    switch (kind) {
+        case Track::VIDEO:
+        {
+            if (chunkBase != FOURCC(0, 0, 'd', 'c')
+                    && chunkBase != FOURCC(0, 0, 'd', 'b')) {
+                return false;
+            }
+            break;
+        }
+
+        case Track::AUDIO:
+        {
+            if (chunkBase != FOURCC(0, 0, 'w', 'b')) {
+                return false;
+            }
+            break;
+        }
+
+        default:
+            break;
+    }
+
+    if (trackIndex < 0) {
+        return true;
+    }
+
+    uint8_t hi = chunkType >> 24;
+    uint8_t lo = (chunkType >> 16) & 0xff;
+
+    if (hi < '0' || hi > '9' || lo < '0' || lo > '9') {
+        return false;
+    }
+
+    if (trackIndex != (10 * (hi - '0') + (lo - '0'))) {
+        return false;
+    }
+
+    return true;
+}
+
+status_t AVIExtractor::parseIndex(off64_t offset, size_t size) {
+    if ((size % 16) != 0) {
+        return ERROR_MALFORMED;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(size);
+    ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+    if (n < (ssize_t)size) {
+        return n < 0 ? (status_t)n : ERROR_MALFORMED;
+    }
+
+    const uint8_t *data = buffer->data();
+
+    while (size > 0) {
+        uint32_t chunkType = U32_AT(data);
+
+        uint8_t hi = chunkType >> 24;
+        uint8_t lo = (chunkType >> 16) & 0xff;
+
+        if (hi < '0' || hi > '9' || lo < '0' || lo > '9') {
+            return ERROR_MALFORMED;
+        }
+
+        size_t trackIndex = 10 * (hi - '0') + (lo - '0');
+
+        if (trackIndex >= mTracks.size()) {
+            return ERROR_MALFORMED;
+        }
+
+        Track *track = &mTracks.editItemAt(trackIndex);
+
+        if (!IsCorrectChunkType(-1, track->mKind, chunkType)) {
+            return ERROR_MALFORMED;
+        }
+
+        if (track->mKind == Track::OTHER) {
+            data += 16;
+            size -= 16;
+            continue;
+        }
+
+        uint32_t flags = U32LE_AT(&data[4]);
+        uint32_t offset = U32LE_AT(&data[8]);
+        uint32_t chunkSize = U32LE_AT(&data[12]);
+
+        if (chunkSize > track->mMaxSampleSize) {
+            track->mMaxSampleSize = chunkSize;
+        }
+
+        track->mSamples.push();
+
+        SampleInfo *info =
+            &track->mSamples.editItemAt(track->mSamples.size() - 1);
+
+        info->mOffset = offset;
+        info->mIsKey = (flags & 0x10) != 0;
+
+        if (info->mIsKey) {
+            static const size_t kMaxNumSyncSamplesToScan = 20;
+
+            if (track->mNumSyncSamples < kMaxNumSyncSamplesToScan) {
+                if (chunkSize > track->mThumbnailSampleSize) {
+                    track->mThumbnailSampleSize = chunkSize;
+
+                    track->mThumbnailSampleIndex =
+                        track->mSamples.size() - 1;
+                }
+            }
+
+            ++track->mNumSyncSamples;
+        }
+
+        data += 16;
+        size -= 16;
+    }
+
+    if (!mTracks.isEmpty()) {
+        off64_t offset;
+        size_t size;
+        bool isKey;
+        status_t err = getSampleInfo(0, 0, &offset, &size, &isKey);
+
+        if (err != OK) {
+            mOffsetsAreAbsolute = !mOffsetsAreAbsolute;
+            err = getSampleInfo(0, 0, &offset, &size, &isKey);
+
+            if (err != OK) {
+                return err;
+            }
+        }
+
+        LOGV("Chunk offsets are %s",
+             mOffsetsAreAbsolute ? "absolute" : "movie-chunk relative");
+    }
+
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        Track *track = &mTracks.editItemAt(i);
+
+        int64_t durationUs =
+            (track->mSamples.size() * 1000000ll * track->mRate) / track->mScale;
+
+        LOGV("track %d duration = %.2f secs", i, durationUs / 1E6);
+
+        track->mMeta->setInt64(kKeyDuration, durationUs);
+        track->mMeta->setInt32(kKeyMaxInputSize, track->mMaxSampleSize);
+
+        const char *tmp;
+        CHECK(track->mMeta->findCString(kKeyMIMEType, &tmp));
+
+        AString mime = tmp;
+
+        if (!strncasecmp("video/", mime.c_str(), 6)
+                && track->mThumbnailSampleIndex >= 0) {
+            int64_t thumbnailTimeUs =
+                (track->mThumbnailSampleIndex * 1000000ll * track->mRate)
+                    / track->mScale;
+
+            track->mMeta->setInt64(kKeyThumbnailTime, thumbnailTimeUs);
+
+            if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG4)) {
+                status_t err = addMPEG4CodecSpecificData(i);
+
+                if (err != OK) {
+                    return err;
+                }
+            }
+        }
+    }
+
+    mFoundIndex = true;
+
+    return OK;
+}
+
+static size_t GetSizeWidth(size_t x) {
+    size_t n = 1;
+    while (x > 127) {
+        ++n;
+        x >>= 7;
+    }
+    return n;
+}
+
+static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
+    while (x > 127) {
+        *dst++ = (x & 0x7f) | 0x80;
+        x >>= 7;
+    }
+    *dst++ = x;
+    return dst;
+}
+
+sp<ABuffer> MakeMPEG4VideoCodecSpecificData(const sp<ABuffer> &config) {
+    size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
+    size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
+    size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
+
+    sp<ABuffer> csd = new ABuffer(len3);
+    uint8_t *dst = csd->data();
+    *dst++ = 0x03;
+    dst = EncodeSize(dst, len2 + 3);
+    *dst++ = 0x00;  // ES_ID
+    *dst++ = 0x00;
+    *dst++ = 0x00;  // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+    *dst++ = 0x04;
+    dst = EncodeSize(dst, len1 + 13);
+    *dst++ = 0x01;  // Video ISO/IEC 14496-2 Simple Profile
+    for (size_t i = 0; i < 12; ++i) {
+        *dst++ = 0x00;
+    }
+
+    *dst++ = 0x05;
+    dst = EncodeSize(dst, config->size());
+    memcpy(dst, config->data(), config->size());
+    dst += config->size();
+
+    // hexdump(csd->data(), csd->size());
+
+    return csd;
+}
+
+status_t AVIExtractor::addMPEG4CodecSpecificData(size_t trackIndex) {
+    Track *track = &mTracks.editItemAt(trackIndex);
+
+    off64_t offset;
+    size_t size;
+    bool isKey;
+    status_t err = getSampleInfo(trackIndex, 0, &offset, &size, &isKey);
+
+    if (err != OK) {
+        return err;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(size);
+    ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+    if (n < (ssize_t)size) {
+        return n < 0 ? (status_t)n : ERROR_MALFORMED;
+    }
+
+    // Extract everything up to the first VOP start code from the first
+    // frame's encoded data and use it to construct an ESDS with the
+    // codec specific data.
+
+    size_t i = 0;
+    bool found = false;
+    while (i + 3 < buffer->size()) {
+        if (!memcmp("\x00\x00\x01\xb6", &buffer->data()[i], 4)) {
+            found = true;
+            break;
+        }
+
+        ++i;
+    }
+
+    if (!found) {
+        return ERROR_MALFORMED;
+    }
+
+    buffer->setRange(0, i);
+
+    sp<ABuffer> csd = MakeMPEG4VideoCodecSpecificData(buffer);
+    track->mMeta->setData(kKeyESDS, kTypeESDS, csd->data(), csd->size());
+
+    return OK;
+}
+
+status_t AVIExtractor::getSampleInfo(
+        size_t trackIndex, size_t sampleIndex,
+        off64_t *offset, size_t *size, bool *isKey) {
+    if (trackIndex >= mTracks.size()) {
+        return -ERANGE;
+    }
+
+    const Track &track = mTracks.itemAt(trackIndex);
+
+    if (sampleIndex >= track.mSamples.size()) {
+        return -ERANGE;
+    }
+
+    const SampleInfo &info = track.mSamples.itemAt(sampleIndex);
+
+    if (!mOffsetsAreAbsolute) {
+        *offset = info.mOffset + mMovieOffset + 8;
+    } else {
+        *offset = info.mOffset;
+    }
+
+    *size = 0;
+
+    uint8_t tmp[8];
+    ssize_t n = mDataSource->readAt(*offset, tmp, 8);
+
+    if (n < 8) {
+        return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED;
+    }
+
+    uint32_t chunkType = U32_AT(tmp);
+
+    if (!IsCorrectChunkType(trackIndex, track.mKind, chunkType)) {
+        return ERROR_MALFORMED;
+    }
+
+    *offset += 8;
+    *size = U32LE_AT(&tmp[4]);
+
+    *isKey = info.mIsKey;
+
+    return OK;
+}
+
+status_t AVIExtractor::getSampleIndexAtTime(
+        size_t trackIndex,
+        int64_t timeUs, MediaSource::ReadOptions::SeekMode mode,
+        size_t *sampleIndex) const {
+    if (trackIndex >= mTracks.size()) {
+        return -ERANGE;
+    }
+
+    const Track &track = mTracks.itemAt(trackIndex);
+
+    ssize_t closestSampleIndex =
+        timeUs / track.mRate * track.mScale / 1000000ll;
+
+    ssize_t numSamples = track.mSamples.size();
+
+    if (closestSampleIndex < 0) {
+        closestSampleIndex = 0;
+    } else if (closestSampleIndex >= numSamples) {
+        closestSampleIndex = numSamples - 1;
+    }
+
+    if (mode == MediaSource::ReadOptions::SEEK_CLOSEST) {
+        *sampleIndex = closestSampleIndex;
+
+        return OK;
+    }
+
+    ssize_t prevSyncSampleIndex = closestSampleIndex;
+    while (prevSyncSampleIndex >= 0) {
+        const SampleInfo &info =
+            track.mSamples.itemAt(prevSyncSampleIndex);
+
+        if (info.mIsKey) {
+            break;
+        }
+
+        --prevSyncSampleIndex;
+    }
+
+    ssize_t nextSyncSampleIndex = closestSampleIndex;
+    while (nextSyncSampleIndex < numSamples) {
+        const SampleInfo &info =
+            track.mSamples.itemAt(nextSyncSampleIndex);
+
+        if (info.mIsKey) {
+            break;
+        }
+
+        ++nextSyncSampleIndex;
+    }
+
+    switch (mode) {
+        case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC:
+        {
+            *sampleIndex = prevSyncSampleIndex;
+
+            return prevSyncSampleIndex >= 0 ? OK : UNKNOWN_ERROR;
+        }
+
+        case MediaSource::ReadOptions::SEEK_NEXT_SYNC:
+        {
+            *sampleIndex = nextSyncSampleIndex;
+
+            return nextSyncSampleIndex < numSamples ? OK : UNKNOWN_ERROR;
+        }
+
+        case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC:
+        {
+            if (prevSyncSampleIndex < 0 && nextSyncSampleIndex >= numSamples) {
+                return UNKNOWN_ERROR;
+            }
+
+            if (prevSyncSampleIndex < 0) {
+                *sampleIndex = nextSyncSampleIndex;
+                return OK;
+            }
+
+            if (nextSyncSampleIndex >= numSamples) {
+                *sampleIndex = prevSyncSampleIndex;
+                return OK;
+            }
+
+            size_t dist1 = closestSampleIndex - prevSyncSampleIndex;
+            size_t dist2 = nextSyncSampleIndex - closestSampleIndex;
+
+            *sampleIndex =
+                (dist1 < dist2) ? prevSyncSampleIndex : nextSyncSampleIndex;
+
+            return OK;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+bool SniffAVI(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence,
+        sp<AMessage> *) {
+    char tmp[12];
+    if (source->readAt(0, tmp, 12) < 12) {
+        return false;
+    }
+
+    if (!memcmp(tmp, "RIFF", 4) && !memcmp(&tmp[8], "AVI ", 4)) {
+        mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_AVI);
+        *confidence = 0.2;
+
+        return true;
+    }
+
+    return false;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index d2e8b46..2f3e1410 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -8,6 +8,7 @@
         AACExtractor.cpp                  \
         AMRExtractor.cpp                  \
         AMRWriter.cpp                     \
+        AVIExtractor.cpp                  \
         AudioPlayer.cpp                   \
         AudioSource.cpp                   \
         AwesomePlayer.cpp                 \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 7940de0..759bd86 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -1693,29 +1693,37 @@
 
         dataSource = mCachedSource;
 
-        // We're going to prefill the cache before trying to instantiate
-        // the extractor below, as the latter is an operation that otherwise
-        // could block on the datasource for a significant amount of time.
-        // During that time we'd be unable to abort the preparation phase
-        // without this prefill.
+        String8 contentType = dataSource->getMIMEType();
 
-        mLock.unlock();
+        if (strncasecmp(contentType.string(), "audio/", 6)) {
+            // We're not doing this for streams that appear to be audio-only
+            // streams to ensure that even low bandwidth streams start
+            // playing back fairly instantly.
 
-        for (;;) {
-            status_t finalStatus;
-            size_t cachedDataRemaining =
-                mCachedSource->approxDataRemaining(&finalStatus);
+            // We're going to prefill the cache before trying to instantiate
+            // the extractor below, as the latter is an operation that otherwise
+            // could block on the datasource for a significant amount of time.
+            // During that time we'd be unable to abort the preparation phase
+            // without this prefill.
 
-            if (finalStatus != OK || cachedDataRemaining >= kHighWaterMarkBytes
-                    || (mFlags & PREPARE_CANCELLED)) {
-                break;
+            mLock.unlock();
+
+            for (;;) {
+                status_t finalStatus;
+                size_t cachedDataRemaining =
+                    mCachedSource->approxDataRemaining(&finalStatus);
+
+                if (finalStatus != OK || cachedDataRemaining >= kHighWaterMarkBytes
+                        || (mFlags & PREPARE_CANCELLED)) {
+                    break;
+                }
+
+                usleep(200000);
             }
 
-            usleep(200000);
+            mLock.lock();
         }
 
-        mLock.lock();
-
         if (mFlags & PREPARE_CANCELLED) {
             LOGI("Prepare cancelled while waiting for initial cache fill.");
             return UNKNOWN_ERROR;
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index b5c51f4..c16b3b5 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "include/AMRExtractor.h"
+#include "include/AVIExtractor.h"
 #include "include/MP3Extractor.h"
 #include "include/MPEG4Extractor.h"
 #include "include/WAVExtractor.h"
@@ -111,6 +112,7 @@
     RegisterSniffer(SniffMPEG2TS);
     RegisterSniffer(SniffMP3);
     RegisterSniffer(SniffAAC);
+    RegisterSniffer(SniffAVI);
 
     char value[PROPERTY_VALUE_MAX];
     if (property_get("drm.service.enabled", value, NULL)
@@ -144,4 +146,8 @@
     return source;
 }
 
+String8 DataSource::getMIMEType() const {
+    return String8("application/octet-stream");
+}
+
 }  // namespace android
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 0be7261..8ca6ee8 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -43,6 +43,7 @@
 const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
 const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
 const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS = "video/mp2ts";
+const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi";
 
 const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm";
 
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 23bad5b..af0131e 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -19,6 +19,7 @@
 #include <utils/Log.h>
 
 #include "include/AMRExtractor.h"
+#include "include/AVIExtractor.h"
 #include "include/MP3Extractor.h"
 #include "include/MPEG4Extractor.h"
 #include "include/WAVExtractor.h"
@@ -108,6 +109,8 @@
         ret = new MatroskaExtractor(source);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
         ret = new MPEG2TSExtractor(source);
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {
+        ret = new AVIExtractor(source);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
         ret = new WVMExtractor(source);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp
index 3c99d1c..c1aa46e 100644
--- a/media/libstagefright/NuCachedSource2.cpp
+++ b/media/libstagefright/NuCachedSource2.cpp
@@ -493,4 +493,8 @@
     return mSource->getUri();
 }
 
+String8 NuCachedSource2::getMIMEType() const {
+    return mSource->getMIMEType();
+}
+
 }  // namespace android
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
index 73daf12..821ba9b 100644
--- a/media/libstagefright/NuHTTPDataSource.cpp
+++ b/media/libstagefright/NuHTTPDataSource.cpp
@@ -136,6 +136,7 @@
     unsigned port;
 
     mUri = uri;
+    mContentType = String8("application/octet-stream");
 
     bool https;
     if (!ParseURL(uri, &host, &port, &path, &https)) {
@@ -265,6 +266,15 @@
             }
         }
 
+        {
+            AString value;
+            if (mHTTP.find_header_value("Content-Type", &value)) {
+                mContentType = String8(value.c_str());
+            } else {
+                mContentType = String8("application/octet-stream");
+            }
+        }
+
         applyTimeoutResponse();
 
         if (offset == 0) {
@@ -410,7 +420,14 @@
             internalRead((uint8_t *)data + numBytesRead, size - numBytesRead);
 
         if (n < 0) {
-            return n;
+            if (numBytesRead == 0 || mContentLengthValid) {
+                return n;
+            }
+
+            // If there was an error we want to at least return the data
+            // we've already successfully read. The next call to read will
+            // then return the error.
+            n = 0;
         }
 
         int64_t delayUs = ALooper::GetNowUs() - startTimeUs;
@@ -564,4 +581,8 @@
     return mUri;
 }
 
+String8 NuHTTPDataSource::getMIMEType() const {
+    return mContentType;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 904bd62..c278992 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -2240,8 +2240,8 @@
                         android_native_rect_t crop;
                         crop.left = left;
                         crop.top = top;
-                        crop.right = right;
-                        crop.bottom = bottom;
+                        crop.right = right + 1;
+                        crop.bottom = bottom + 1;
 
                         // We'll ignore any errors here, if the surface is
                         // already invalid, we'll know soon enough.
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index 8b86e53..f82ff32 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -38,6 +38,7 @@
         ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
         ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota",
         ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac", ".mxmf",
+        ".avi",
     };
     static const size_t kNumValidExtensions =
         sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index e9e5ef9..76f47f7 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -425,6 +425,11 @@
         return false;
     }
 
+    sp<MediaExtractor> extractor = new WAVExtractor(source);
+    if (extractor->countTracks() == 0) {
+        return false;
+    }
+
     *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV;
     *confidence = 0.3f;
 
diff --git a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp
index 949a5e4..1096717 100644
--- a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp
+++ b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp
@@ -79,6 +79,7 @@
     }
 
     mURI = uri;
+    mContentType = String8("application/octet-stream");
 
     if (headers != NULL) {
         mHeaders = *headers;
@@ -99,10 +100,12 @@
     return mState == CONNECTED ? OK : mIOResult;
 }
 
-void ChromiumHTTPDataSource::onConnectionEstablished(int64_t contentSize) {
+void ChromiumHTTPDataSource::onConnectionEstablished(
+        int64_t contentSize, const char *contentType) {
     Mutex::Autolock autoLock(mLock);
     mState = CONNECTED;
     mContentSize = (contentSize < 0) ? -1 : contentSize + mCurrentOffset;
+    mContentType = String8(contentType);
     mCondition.broadcast();
 }
 
@@ -314,6 +317,12 @@
     return String8(mURI.c_str());
 }
 
+String8 ChromiumHTTPDataSource::getMIMEType() const {
+    Mutex::Autolock autoLock(mLock);
+
+    return mContentType;
+}
+
 void ChromiumHTTPDataSource::clearDRMState_l() {
     if (mDecryptHandle != NULL) {
         // To release mDecryptHandle
diff --git a/media/libstagefright/chromium_http/support.cpp b/media/libstagefright/chromium_http/support.cpp
index 7ac56e8..af2f6ac 100644
--- a/media/libstagefright/chromium_http/support.cpp
+++ b/media/libstagefright/chromium_http/support.cpp
@@ -253,7 +253,11 @@
 
     MY_LOGV(StringPrintf("response headers: %s", headers.c_str()).c_str());
 
-    mOwner->onConnectionEstablished(request->GetExpectedContentSize());
+    std::string contentType;
+    request->GetResponseHeaderByName("Content-Type", &contentType);
+
+    mOwner->onConnectionEstablished(
+            request->GetExpectedContentSize(), contentType.c_str());
 }
 
 void SfDelegate::OnReadCompleted(URLRequest *request, int bytes_read) {
diff --git a/media/libstagefright/include/AVIExtractor.h b/media/libstagefright/include/AVIExtractor.h
new file mode 100644
index 0000000..375a94d
--- /dev/null
+++ b/media/libstagefright/include/AVIExtractor.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 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 AVI_EXTRACTOR_H_
+
+#define AVI_EXTRACTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AVIExtractor : public MediaExtractor {
+    AVIExtractor(const sp<DataSource> &dataSource);
+
+    virtual size_t countTracks();
+
+    virtual sp<MediaSource> getTrack(size_t index);
+
+    virtual sp<MetaData> getTrackMetaData(
+            size_t index, uint32_t flags);
+
+    virtual sp<MetaData> getMetaData();
+
+protected:
+    virtual ~AVIExtractor();
+
+private:
+    struct AVISource;
+
+    struct SampleInfo {
+        uint32_t mOffset;
+        bool mIsKey;
+    };
+
+    struct Track {
+        sp<MetaData> mMeta;
+        Vector<SampleInfo> mSamples;
+        uint32_t mRate;
+        uint32_t mScale;
+
+        enum Kind {
+            AUDIO,
+            VIDEO,
+            OTHER
+
+        } mKind;
+
+        size_t mNumSyncSamples;
+        size_t mThumbnailSampleSize;
+        ssize_t mThumbnailSampleIndex;
+        size_t mMaxSampleSize;
+    };
+
+    sp<DataSource> mDataSource;
+    status_t mInitCheck;
+    Vector<Track> mTracks;
+
+    off64_t mMovieOffset;
+    bool mFoundIndex;
+    bool mOffsetsAreAbsolute;
+
+    ssize_t parseChunk(off64_t offset, off64_t size, int depth = 0);
+    status_t parseStreamHeader(off64_t offset, size_t size);
+    status_t parseStreamFormat(off64_t offset, size_t size);
+    status_t parseIndex(off64_t offset, size_t size);
+
+    status_t parseHeaders();
+
+    status_t getSampleInfo(
+            size_t trackIndex, size_t sampleIndex,
+            off64_t *offset, size_t *size, bool *isKey);
+
+    status_t getSampleIndexAtTime(
+            size_t trackIndex,
+            int64_t timeUs, MediaSource::ReadOptions::SeekMode mode,
+            size_t *sampleIndex) const;
+
+    status_t addMPEG4CodecSpecificData(size_t trackIndex);
+
+    static bool IsCorrectChunkType(
+        ssize_t trackIndex, Track::Kind kind, uint32_t chunkType);
+
+    DISALLOW_EVIL_CONSTRUCTORS(AVIExtractor);
+};
+
+class String8;
+struct AMessage;
+
+bool SniffAVI(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence,
+        sp<AMessage> *);
+
+}  // namespace android
+
+#endif  // AVI_EXTRACTOR_H_
diff --git a/media/libstagefright/include/ChromiumHTTPDataSource.h b/media/libstagefright/include/ChromiumHTTPDataSource.h
index af49059..0e2927d 100644
--- a/media/libstagefright/include/ChromiumHTTPDataSource.h
+++ b/media/libstagefright/include/ChromiumHTTPDataSource.h
@@ -51,6 +51,8 @@
 
     virtual String8 getUri();
 
+    virtual String8 getMIMEType() const;
+
 protected:
     virtual ~ChromiumHTTPDataSource();
 
@@ -90,6 +92,8 @@
 
     int64_t mContentSize;
 
+    String8 mContentType;
+
     List<BandwidthEntry> mBandwidthHistory;
     size_t mNumBandwidthHistoryItems;
     int64_t mTotalTransferTimeUs;
@@ -110,7 +114,9 @@
 
     void initiateRead(void *data, size_t size);
 
-    void onConnectionEstablished(int64_t contentSize);
+    void onConnectionEstablished(
+            int64_t contentSize, const char *contentType);
+
     void onConnectionFailed(status_t err);
     void onReadCompleted(ssize_t size);
     void onDisconnectComplete();
diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h
index 02d5817..2128682 100644
--- a/media/libstagefright/include/NuCachedSource2.h
+++ b/media/libstagefright/include/NuCachedSource2.h
@@ -40,6 +40,9 @@
     virtual sp<DecryptHandle> DrmInitialization();
     virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client);
     virtual String8 getUri();
+
+    virtual String8 getMIMEType() const;
+
     ////////////////////////////////////////////////////////////////////////////
 
     size_t cachedSize();
diff --git a/media/libstagefright/include/NuHTTPDataSource.h b/media/libstagefright/include/NuHTTPDataSource.h
index 7dd5d59..2ab1f19 100644
--- a/media/libstagefright/include/NuHTTPDataSource.h
+++ b/media/libstagefright/include/NuHTTPDataSource.h
@@ -51,6 +51,8 @@
     virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client);
     virtual String8 getUri();
 
+    virtual String8 getMIMEType() const;
+
 protected:
     virtual ~NuHTTPDataSource();
 
@@ -85,6 +87,8 @@
     bool mContentLengthValid;
     bool mHasChunkedTransferEncoding;
 
+    String8 mContentType;
+
     // The number of data bytes in the current chunk before any subsequent
     // chunk header (or -1 if no more chunks).
     ssize_t mChunkDataBytesLeft;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 12ac052..0b0e1ef 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -543,18 +543,8 @@
      */
     public NetworkInfo getActiveNetworkInfo() {
         enforceAccessPermission();
-        for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
-            if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) {
-                continue;
-            }
-            NetworkStateTracker t = mNetTrackers[type];
-            NetworkInfo info = t.getNetworkInfo();
-            if (info.isConnected()) {
-                if (DBG && type != mActiveDefaultNetwork) {
-                    loge("connected default network is not mActiveDefaultNetwork!");
-                }
-                return info;
-            }
+        if (mActiveDefaultNetwork != -1) {
+            return mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
         }
         return null;
     }
@@ -1353,7 +1343,20 @@
                 handleApplyDefaultProxy(netType);
                 addDefaultRoute(mNetTrackers[netType]);
             } else {
-                addPrivateDnsRoutes(mNetTrackers[netType]);
+                // many radios add a default route even when we don't want one.
+                // remove the default interface unless we need it for our active network
+                if (mActiveDefaultNetwork != -1) {
+                    LinkProperties linkProperties =
+                            mNetTrackers[mActiveDefaultNetwork].getLinkProperties();
+                    LinkProperties newLinkProperties =
+                            mNetTrackers[netType].getLinkProperties();
+                    String defaultIface = linkProperties.getInterfaceName();
+                    if (defaultIface != null &&
+                            !defaultIface.equals(newLinkProperties.getInterfaceName())) {
+                        mNetTrackers[netType].removeDefaultRoute();
+                    }
+                }
+                mNetTrackers[netType].addPrivateDnsRoutes();
             }
         } else {
             if (mNetAttributes[netType].isDefault()) {
diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java
index 21f2bcf..1e95f3e 100644
--- a/services/java/com/android/server/LightsService.java
+++ b/services/java/com/android/server/LightsService.java
@@ -148,7 +148,6 @@
                 fis.close();
                 return (result != '0');
             } catch (Exception e) {
-                Slog.e(TAG, "getFlashlightEnabled failed", e);
                 return false;
             }
         }
@@ -168,7 +167,7 @@
                 fos.write(bytes);
                 fos.close();
             } catch (Exception e) {
-                Slog.e(TAG, "setFlashlightEnabled failed", e);
+                // fail silently
             }
         }
     };
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index 2fcdb5d..c39dc80 100755
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -247,6 +247,7 @@
     // Lock held on mVibrations
     private void startNextVibrationLocked() {
         if (mVibrations.size() <= 0) {
+            mCurrentVibration = null;
             return;
         }
         mCurrentVibration = mVibrations.getFirst();
@@ -273,17 +274,27 @@
             Vibration vib = iter.next();
             if (vib.mToken == token) {
                 iter.remove();
+                unlinkVibration(vib);
                 return vib;
             }
         }
         // We might be looking for a simple vibration which is only stored in
         // mCurrentVibration.
         if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+            unlinkVibration(mCurrentVibration);
             return mCurrentVibration;
         }
         return null;
     }
 
+    private void unlinkVibration(Vibration vib) {
+        if (vib.mPattern != null) {
+            // If Vibration object has a pattern,
+            // the Vibration object has also been linkedToDeath.
+            vib.mToken.unlinkToDeath(vib, 0);
+        }
+    }
+
     private class VibrateThread extends Thread {
         final Vibration mVibration;
         boolean mDone;
@@ -360,6 +371,7 @@
                     // If this vibration finished naturally, start the next
                     // vibration.
                     mVibrations.remove(mVibration);
+                    unlinkVibration(mVibration);
                     startNextVibrationLocked();
                 }
             }
diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java
index b7f6346..a151af0 100644
--- a/services/java/com/android/server/usb/UsbService.java
+++ b/services/java/com/android/server/usb/UsbService.java
@@ -561,11 +561,14 @@
                     case MSG_UPDATE_STATE:
                         if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
                             if (mConnected == 0) {
-                                // make sure accessory mode is off, and restore default functions
-                                if (mCurrentAccessory != null && UsbManager.setFunctionEnabled(
-                                        UsbManager.USB_FUNCTION_ACCESSORY, false)) {
+                                if (UsbManager.isFunctionEnabled(
+                                            UsbManager.USB_FUNCTION_ACCESSORY)) {
+                                    // make sure accessory mode is off, and restore default functions
                                     Log.d(TAG, "exited USB accessory mode");
-
+                                    if (!UsbManager.setFunctionEnabled
+                                            (UsbManager.USB_FUNCTION_ACCESSORY, false)) {
+                                        Log.e(TAG, "could not disable accessory function");
+                                    }
                                     int count = mDefaultFunctions.size();
                                     for (int i = 0; i < count; i++) {
                                         String function = mDefaultFunctions.get(i);
@@ -574,8 +577,10 @@
                                         }
                                     }
 
-                                    mDeviceManager.accessoryDetached(mCurrentAccessory);
-                                    mCurrentAccessory = null;
+                                    if (mCurrentAccessory != null) {
+                                        mDeviceManager.accessoryDetached(mCurrentAccessory);
+                                        mCurrentAccessory = null;
+                                    }
                                 }
                             }
 
diff --git a/tests/BiDiTests/res/layout/biditest_main.xml b/tests/BiDiTests/res/layout/biditest_main.xml
index 9f77ad2..087c9a3 100644
--- a/tests/BiDiTests/res/layout/biditest_main.xml
+++ b/tests/BiDiTests/res/layout/biditest_main.xml
@@ -48,6 +48,11 @@
 
     </LinearLayout>
 
+    <SeekBar android:id="@+id/seekbar"
+               android:layout_height="wrap_content"
+               android:layout_width="match_parent"
+               />
+
     <view class="com.android.bidi.BiDiTestView"
         android:id="@+id/main"
         android:layout_width="match_parent"
diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml
index ecff76e..632a02e 100644
--- a/tests/BiDiTests/res/values/strings.xml
+++ b/tests/BiDiTests/res/values/strings.xml
@@ -18,9 +18,11 @@
     <string name="edittext_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
     <string name="normal_text">Normal String</string>
     <string name="normal_long_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
+    <string name="normal_long_text_2">nnnnnnnnnnnnnnnnnnnnnnnn</string>
+    <string name="normal_long_text_3">Notify me when an open network is available</string>
     <string name="arabic_text">&#x0644;&#x0627;</string>
     <string name="chinese_text">利比亚局势或影响美俄关系发展</string>
     <string name="italic_text">Italic String</string>
-    <string name="bold_text">Bold String</string>
+    <string name="bold_text">Bold String - other text</string>
     <string name="bold_italic_text">Bold Italic String</string>
 </resources>
\ No newline at end of file
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index 3d7dd81..6c71574 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -20,16 +20,44 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
+import android.widget.SeekBar;
+
+import static com.android.bidi.BiDiTestConstants.FONT_MIN_SIZE;
+import static com.android.bidi.BiDiTestConstants.FONT_MAX_SIZE;
 
 public class BiDiTestActivity extends Activity {
 
     static final String TAG = "BiDiTestActivity";
 
+    static final int INIT_TEXT_SIZE = (FONT_MAX_SIZE - FONT_MIN_SIZE) / 2;
+
+    private BiDiTestView textView;
+    private SeekBar textSizeSeekBar;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.biditest_main);
+
+        textView = (BiDiTestView) findViewById(R.id.main);
+        textView.setCurrentTextSize(INIT_TEXT_SIZE);
+
+        textSizeSeekBar = (SeekBar) findViewById(R.id.seekbar);
+        textSizeSeekBar.setProgress(INIT_TEXT_SIZE);
+        textSizeSeekBar.setMax(FONT_MAX_SIZE - FONT_MIN_SIZE);
+
+        textSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                textView.setCurrentTextSize(FONT_MIN_SIZE + progress);
+            }
+
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
     }
 
     @Override
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java
new file mode 100644
index 0000000..5c28e3d
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011 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;
+
+public class BiDiTestConstants {
+    public static final int FONT_MIN_SIZE = 8;
+    public static final int FONT_MAX_SIZE = 72;
+}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
index e9b6fa6..cd415c2 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
@@ -32,9 +32,8 @@
 
     private static final int BORDER_PADDING = 4;
     private static final int TEXT_PADDING = 16;
-    private static final int TEXT_SIZE = 32;
-    private static final int ORIGIN = 48;
-    private static final int DELTA_Y = TEXT_SIZE;
+    private static final int TEXT_SIZE = 16;
+    private static final int ORIGIN = 80;
 
     private static final float DEFAULT_ITALIC_SKEW_X = -0.25f;
 
@@ -43,6 +42,8 @@
 
     private String NORMAL_TEXT;
     private String NORMAL_LONG_TEXT;
+    private String NORMAL_LONG_TEXT_2;
+    private String NORMAL_LONG_TEXT_3;
     private String ITALIC_TEXT;
     private String BOLD_TEXT;
     private String BOLD_ITALIC_TEXT;
@@ -51,6 +52,8 @@
 
     private Typeface typeface;
 
+    private int currentTextSize;
+
     public BiDiTestView(Context context) {
         super(context);
         init(context);
@@ -69,6 +72,8 @@
     private void init(Context context) {
         NORMAL_TEXT = context.getString(R.string.normal_text);
         NORMAL_LONG_TEXT = context.getString(R.string.normal_long_text);
+        NORMAL_LONG_TEXT_2 = context.getString(R.string.normal_long_text_2);
+        NORMAL_LONG_TEXT_3 = context.getString(R.string.normal_long_text_3);
         ITALIC_TEXT = context.getString(R.string.italic_text);
         BOLD_TEXT = context.getString(R.string.bold_text);
         BOLD_ITALIC_TEXT = context.getString(R.string.bold_italic_text);
@@ -79,34 +84,50 @@
         paint.setAntiAlias(true);
     }
 
+    public void setCurrentTextSize(int size) {
+        currentTextSize = size;
+        invalidate();
+    }
+
     @Override
     public void onDraw(Canvas canvas) {
         drawInsideRect(canvas, Color.BLACK);
 
-        int deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN, paint, typeface,
-                false, false,  Paint.DIRECTION_LTR);
-        deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
-                true, false,  Paint.DIRECTION_LTR);
-        deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
-                false, true,  Paint.DIRECTION_LTR);
-        deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
-                true, true,  Paint.DIRECTION_LTR);
+        int deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN,
+                paint, typeface, false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                paint, typeface, true, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN,
+                paint, typeface, false, true,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                paint, typeface, true, true,  Paint.DIRECTION_LTR, currentTextSize);
 
         // Test with a long string
-        deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * DELTA_Y, paint, typeface,
-                false, false,  Paint.DIRECTION_LTR);
+        deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * currentTextSize,
+                paint, typeface, false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_2, ORIGIN, ORIGIN + 4 * currentTextSize,
+                paint, typeface, false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_3, ORIGIN, ORIGIN + 6 * currentTextSize,
+                paint, typeface, false, false,  Paint.DIRECTION_LTR, currentTextSize);
 
         // Test Arabic ligature
-        deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 4 * DELTA_Y, paint, typeface,
-                false, false,  Paint.DIRECTION_RTL);
+        deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 8 * currentTextSize,
+                paint, typeface, false, false,  Paint.DIRECTION_RTL, currentTextSize);
 
         // Test Chinese
-        deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 6 * DELTA_Y, paint, typeface,
-                false, false,  Paint.DIRECTION_LTR);
+        deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 10 * currentTextSize,
+                paint, typeface, false, false,  Paint.DIRECTION_LTR, currentTextSize);
     }
 
     private int testString(Canvas canvas, String text, int x, int y, Paint paint, Typeface typeface,
-            boolean isItalic, boolean isBold, int dir) {
+            boolean isItalic, boolean isBold, int dir, int textSize) {
         paint.setTypeface(typeface);
 
         // Set paint properties
@@ -118,27 +139,28 @@
             paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X);
         }
 
-        drawTextWithCanvasDrawText(text, canvas, x, y, TEXT_SIZE, Color.WHITE);
+        drawTextWithCanvasDrawText(text, canvas, x, y, textSize, Color.WHITE);
 
         int length = text.length();
         float[] advances = new float[length];
-        float textWidth = paint.getTextRunAdvances(text, 0, length, 0, length, 0, advances, 0);
+        float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, 0, advances, 0);
+        float textWidthICU = paint.getTextRunAdvancesICU(text, 0, length, 0, length, 0, advances, 0);
 
-        logAdvances(text, textWidth, advances);
-        drawBoxAroundText(canvas, x, y, textWidth, TEXT_SIZE, Color.RED);
+        logAdvances(text, textWidthHB, textWidthICU, advances);
+        drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN);
 
         paint.setColor(Color.WHITE);
         char[] glyphs = new char[2*length];
         int count = getGlyphs(text, glyphs, dir);
 
-        logGlypths(glyphs, count);
-        drawTextWithDrawGlyph(canvas, glyphs, count, x, y + DELTA_Y);
+//        logGlypths(glyphs, count);
+        drawTextWithDrawGlyph(canvas, glyphs, count, x, y + currentTextSize);
 
         // Restore old paint properties
         paint.setFakeBoldText(oldFakeBold);
         paint.setTextSkewX(oldTextSkewX);
 
-        return (int) Math.ceil(textWidth) + TEXT_PADDING;
+        return (int) Math.ceil(textWidthHB) + TEXT_PADDING;
     }
 
     private void drawTextWithDrawGlyph(Canvas canvas, char[] glyphs, int count, int x, int y) {
@@ -172,19 +194,21 @@
         canvas.drawText(text, x, y, paint);
     }
 
-    private void drawBoxAroundText(Canvas canvas, int x, int y, float textWidth, int textSize,
-            int color) {
+    private void drawMetricsAroundText(Canvas canvas, int x, int y, float textWidthHB,
+            float textWidthICU, int textSize, int color, int colorICU) {
         paint.setColor(color);
         canvas.drawLine(x, y - textSize, x, y + 8, paint);
-        canvas.drawLine(x, y + 8, x + textWidth, y + 8, paint);
-        canvas.drawLine(x + textWidth, y - textSize, x + textWidth, y + 8, paint);
+        canvas.drawLine(x, y + 8, x + textWidthHB, y + 8, paint);
+        canvas.drawLine(x + textWidthHB, y - textSize, x + textWidthHB, y + 8, paint);
+        paint.setColor(colorICU);
+        canvas.drawLine(x + textWidthICU, y - textSize, x + textWidthICU, y + 8, paint);
     }
 
-    private void logAdvances(String text, float textWidth, float[] advances) {
-        Log.v(TAG, "Advances for text: " + text + " total=" + textWidth);
-        int length = advances.length;
-        for(int n=0; n<length; n++){
-            Log.v(TAG, "adv[" + n + "]=" + advances[n]);
-        }
+    private void logAdvances(String text, float textWidth, float textWidthICU, float[] advances) {
+        Log.v(TAG, "Advances for text: " + text + " total= " + textWidth + " - totalICU= " + textWidthICU);
+//        int length = advances.length;
+//        for(int n=0; n<length; n++){
+//            Log.v(TAG, "adv[" + n + "]=" + advances[n]);
+//        }
     }
 }
diff --git a/tests/CoreTests/android/core/HttpHeaderTest.java b/tests/CoreTests/android/core/HttpHeaderTest.java
index a5d48578..eedbc3f 100644
--- a/tests/CoreTests/android/core/HttpHeaderTest.java
+++ b/tests/CoreTests/android/core/HttpHeaderTest.java
@@ -19,12 +19,19 @@
 import org.apache.http.util.CharArrayBuffer;
 
 import android.net.http.Headers;
+import android.util.Log;
+import android.webkit.CacheManager;
+import android.webkit.CacheManager.CacheResult;
+
+import java.lang.reflect.Method;
 
 public class HttpHeaderTest extends AndroidTestCase {
 
     static final String LAST_MODIFIED = "Last-Modified: Fri, 18 Jun 2010 09:56:47 GMT";
     static final String CACHE_CONTROL_MAX_AGE = "Cache-Control:max-age=15";
     static final String CACHE_CONTROL_PRIVATE = "Cache-Control: private";
+    static final String CACHE_CONTROL_COMPOUND = "Cache-Control: no-cache, max-age=200000";
+    static final String CACHE_CONTROL_COMPOUND2 = "Cache-Control: max-age=200000, no-cache";
 
     /**
      * Tests that cache control header supports multiple instances of the header,
@@ -59,4 +66,39 @@
         h.parseHeader(buffer);
         assertEquals("max-age=15,private", h.getCacheControl());
     }
+
+    // Test that cache behaves correctly when receiving a compund
+    // cache-control statement containing no-cache and max-age argument.
+    //
+    // If a cache control header contains both a max-age arument and
+    // a no-cache argument the max-age argument should be ignored.
+    // The resource can be cached, but a validity check must be done on
+    // every request. Test case checks that the expiry time is 0 for
+    // this item, so item will be validated on subsequent requests.
+    public void testCacheControlMultipleArguments() throws Exception {
+        // get private method CacheManager.parseHeaders()
+        Method m = CacheManager.class.getDeclaredMethod("parseHeaders",
+                new Class[] {int.class, Headers.class, String.class});
+        m.setAccessible(true);
+
+        // create indata
+        Headers h = new Headers();
+        CharArrayBuffer buffer = new CharArrayBuffer(64);
+        buffer.append(CACHE_CONTROL_COMPOUND);
+        h.parseHeader(buffer);
+
+        CacheResult c = (CacheResult)m.invoke(null, 200, h, "text/html");
+
+        // Check that expires is set to 0, to ensure that no-cache has overridden
+        // the max-age argument
+        assertEquals(0, c.getExpires());
+
+        // check reverse order
+        buffer.clear();
+        buffer.append(CACHE_CONTROL_COMPOUND2);
+        h.parseHeader(buffer);
+
+        c = (CacheResult)m.invoke(null, 200, h, "text/html");
+        assertEquals(0, c.getExpires());
+    }
 }