Merge "Fix pixel test" into klp-dev
diff --git a/api/current.txt b/api/current.txt
index c7b3ed9..78abd53 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20796,6 +20796,7 @@
     field public static final java.lang.String COLUMN_SIZE = "_size";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
     field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20
+    field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 64; // 0x40
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
     field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10
     field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
@@ -29052,12 +29053,12 @@
   }
 
   public class CaptioningManager {
-    method public void addCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
+    method public void addCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
     method public final float getFontScale();
     method public final java.util.Locale getLocale();
     method public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
     method public final boolean isEnabled();
-    method public void removeCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
+    method public void removeCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
   }
 
   public static final class CaptioningManager.CaptionStyle {
@@ -29071,7 +29072,7 @@
     field public final int foregroundColor;
   }
 
-  public abstract class CaptioningManager.CaptioningChangeListener {
+  public static abstract class CaptioningManager.CaptioningChangeListener {
     ctor public CaptioningManager.CaptioningChangeListener();
     method public void onEnabledChanged(boolean);
     method public void onFontScaleChanged(float);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7c40bb1..2d28280 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2106,7 +2106,12 @@
         }
         // If the target is not exported, then nobody else can get to it.
         if (!exported) {
-            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
+            /*
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
+                    here);
+            */
             return PackageManager.PERMISSION_DENIED;
         }
         if (permission == null) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6d72114..653559d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -3377,7 +3377,7 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeString(packageName);
-        data.writeStrongBinder(observer.asBinder());
+        data.writeStrongBinder((observer != null) ? observer.asBinder() : null);
         data.writeInt(userId);
         mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0);
         reply.readException();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e776a98..7ff7562 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1827,6 +1827,11 @@
                       message);
     }
 
+    /**
+     * Logs a warning if the system process directly called a method such as
+     * {@link #startService(Intent)} instead of {@link #startServiceAsUser(Intent, UserHandle)}.
+     * The "AsUser" variants allow us to properly enforce the user's restrictions.
+     */
     private void warnIfCallingFromSystemProcess() {
         if (Process.myUid() == Process.SYSTEM_UID) {
             Slog.w(TAG, "Calling a method in the system process without a qualified user: "
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9f462aa..e914604 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1745,6 +1745,9 @@
      * @param extras any extras to pass to the SyncAdapter.
      */
     public static void requestSync(Account account, String authority, Bundle extras) {
+        if (extras == null) {
+            throw new IllegalArgumentException("Must specify extras.");
+        }
         SyncRequest request =
             new SyncRequest.Builder()
                 .setSyncAdapter(account, authority)
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 5d886a3..a99705b 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -894,14 +894,11 @@
      */
     protected boolean isValidFragment(String fragmentName) {
         if (getApplicationInfo().targetSdkVersion  >= android.os.Build.VERSION_CODES.KITKAT) {
-            Log.w(TAG, "Subclasses of PreferenceActivity must override isValidFragment(String)"
+            throw new RuntimeException(
+                    "Subclasses of PreferenceActivity must override isValidFragment(String)"
                     + " to verify that the Fragment class is valid! " + this.getClass().getName()
                     + " has not checked if fragment " + fragmentName + " is valid.");
-            // Return true for now, but will eventually return false when all bundled apps
-            // have been modified. TODO: change to return false
-            return true;
         } else {
-            Log.i(TAG, "PreferenceActivity built on pre-KLP launching fragment: " + fragmentName);
             return true;
         }
     }
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 96552af..0ffc40a 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -155,6 +156,8 @@
 
     private static final String LOG_TAG = "PrintService";
 
+    private static final boolean DEBUG = false;
+
     /**
      * The {@link Intent} action that must be declared as handled by a service
      * in its manifest for the system to recognize it as a print service.
@@ -433,6 +436,9 @@
 
                 case MSG_ON_PRINTJOB_QUEUED: {
                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "Queued: " + printJobInfo);
+                    }
                     onPrintJobQueued(new PrintJob(printJobInfo, mClient));
                 } break;
 
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 128628d..8e9636c 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -239,10 +239,10 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrintServiceInfo{");
-        builder.append("id:").append(mId).append(", ");
-        builder.append("resolveInfo:").append(mResolveInfo).append(", ");
-        builder.append("settingsActivityName:").append(mSettingsActivityName);
-        builder.append("addPrintersActivityName:").append(mAddPrintersActivityName);
+        builder.append("id=").append(mId);
+        builder.append(", resolveInfo=").append(mResolveInfo);
+        builder.append(", settingsActivityName=").append(mSettingsActivityName);
+        builder.append(", addPrintersActivityName=").append(mAddPrintersActivityName);
         builder.append("}");
         return builder.toString();
     }
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 5333a25..eaa4f78 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -251,6 +251,15 @@
          * @see #COLUMN_FLAGS
          */
         public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
+
+        /**
+         * Flag indicating that a directory prefers its contents be sorted by
+         * {@link #COLUMN_LAST_MODIFIED}. Only valid when
+         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 6;
     }
 
     /**
@@ -292,9 +301,6 @@
          * @see #FLAG_LOCAL_ONLY
          * @see #FLAG_SUPPORTS_CREATE
          * @see #FLAG_ADVANCED
-         * @see #FLAG_PROVIDES_AUDIO
-         * @see #FLAG_PROVIDES_IMAGES
-         * @see #FLAG_PROVIDES_VIDEO
          */
         public static final String COLUMN_FLAGS = "flags";
 
diff --git a/core/java/android/transition/TextChange.java b/core/java/android/transition/TextChange.java
index 1b26942..4f14d46 100644
--- a/core/java/android/transition/TextChange.java
+++ b/core/java/android/transition/TextChange.java
@@ -143,8 +143,8 @@
         final TextView view = (TextView) endValues.view;
         Map<String, Object> startVals = startValues.values;
         Map<String, Object> endVals = endValues.values;
-        final String startText = (String) startVals.get(PROPNAME_TEXT);
-        final String endText = (String) endVals.get(PROPNAME_TEXT);
+        final CharSequence startText = (CharSequence) startVals.get(PROPNAME_TEXT);
+        final CharSequence endText = (CharSequence) endVals.get(PROPNAME_TEXT);
         if (!startText.equals(endText)) {
             view.setText(startText);
             Animator anim;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8616aba..30531ed 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9546,9 +9546,11 @@
      */
     public void setPivotX(float pivotX) {
         ensureTransformationInfo();
-        mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
         final TransformationInfo info = mTransformationInfo;
-        if (info.mPivotX != pivotX) {
+        boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) ==
+                PFLAG_PIVOT_EXPLICITLY_SET;
+        if (info.mPivotX != pivotX || !pivotSet) {
+            mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
             invalidateViewProperty(true, false);
             info.mPivotX = pivotX;
             info.mMatrixDirty = true;
@@ -9596,9 +9598,11 @@
      */
     public void setPivotY(float pivotY) {
         ensureTransformationInfo();
-        mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
         final TransformationInfo info = mTransformationInfo;
-        if (info.mPivotY != pivotY) {
+        boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) ==
+                PFLAG_PIVOT_EXPLICITLY_SET;
+        if (info.mPivotY != pivotY || !pivotSet) {
+            mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
             invalidateViewProperty(true, false);
             info.mPivotY = pivotY;
             info.mMatrixDirty = true;
@@ -13476,6 +13480,9 @@
         // Fast path for layouts with no backgrounds
         if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
             dispatchDraw(canvas);
+            if (mOverlay != null && !mOverlay.isEmpty()) {
+                mOverlay.getOverlayView().draw(canvas);
+            }
         } else {
             draw(canvas);
         }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index ed128b0..92e5e96 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -698,6 +698,11 @@
                 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
             }
         }
+
+        if (view.mOverlay != null) {
+            ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
+            captureViewLayer(overlayContainer, clientStream, localVisible);
+        }
     }
 
     private static void outputDisplayList(View root, String parameter) throws IOException {
@@ -743,7 +748,7 @@
         }
     }
 
-    private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
+    private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
         if (captureView != null) {
             final CountDownLatch latch = new CountDownLatch(1);
             final Bitmap[] cache = new Bitmap[1];
@@ -752,7 +757,7 @@
                 public void run() {
                     try {
                         cache[0] = captureView.createSnapshot(
-                                Bitmap.Config.ARGB_8888, 0, skpiChildren);
+                                Bitmap.Config.ARGB_8888, 0, skipChildren);
                     } catch (OutOfMemoryError e) {
                         Log.w("View", "Out of memory for bitmap");
                     } finally {
@@ -815,6 +820,13 @@
             } else if (isRequestedView(view, className, hashCode)) {
                 return view;
             }
+            if (view.mOverlay != null) {
+                final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
+                        className, hashCode);
+                if (found != null) {
+                    return found;
+                }
+            }
             if (view instanceof HierarchyHandler) {
                 final View found = ((HierarchyHandler)view)
                         .findHierarchyView(className, hashCode);
@@ -823,12 +835,19 @@
                 }
             }
         }
-
         return null;
     }
 
     private static boolean isRequestedView(View view, String className, int hashCode) {
-        return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
+        if (view.hashCode() == hashCode) {
+            String viewClassName = view.getClass().getName();
+            if (className.equals("ViewOverlay")) {
+                return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
+            } else {
+                return className.equals(viewClassName);
+            }
+        }
+        return false;
     }
 
     private static void dumpViewHierarchy(Context context, ViewGroup group,
@@ -850,6 +869,12 @@
             } else {
                 dumpView(context, view, out, level + 1, includeProperties);
             }
+            if (view.mOverlay != null) {
+                ViewOverlay overlay = view.getOverlay();
+                ViewGroup overlayContainer = overlay.mOverlayViewGroup;
+                dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
+                        includeProperties);
+            }
         }
         if (group instanceof HierarchyHandler) {
             ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
@@ -863,7 +888,11 @@
             for (int i = 0; i < level; i++) {
                 out.write(' ');
             }
-            out.write(view.getClass().getName());
+            String className = view.getClass().getName();
+            if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
+                className = "ViewOverlay";
+            }
+            out.write(className);
             out.write('@');
             out.write(Integer.toHexString(view.hashCode()));
             out.write(' ');
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index ba63421..c61516b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2279,9 +2279,12 @@
         if (other.mExtras != null && !other.mExtras.isEmpty()) {
             getExtras().putAll(other.mExtras);
         }
-        mRangeInfo = other.mRangeInfo;
-        mCollectionInfo = other.mCollectionInfo;
-        mCollectionItemInfo = other.mCollectionItemInfo;
+        mRangeInfo = (other.mRangeInfo != null)
+                ? RangeInfo.obtain(other.mRangeInfo) : null;
+        mCollectionInfo = (other.mCollectionInfo != null)
+                ? CollectionInfo.obtain(other.mCollectionInfo) : null;
+        mCollectionItemInfo =  (other.mCollectionItemInfo != null)
+                ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
     }
 
     /**
@@ -2602,6 +2605,17 @@
         private float mCurrent;
 
         /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * @param other The instance to clone.
+         *
+         * @hide
+         */
+        public static RangeInfo obtain(RangeInfo other) {
+            return obtain(other.mType, other.mMin, other.mMax, other.mCurrent);
+        }
+
+        /**
          * Obtains a pooled instance.
          *
          * @param type The type of the range.
@@ -2708,6 +2722,18 @@
         private boolean mHierarchical;
 
         /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * @param other The instance to clone.
+         *
+         * @hide
+         */
+        public static CollectionInfo obtain(CollectionInfo other) {
+            return CollectionInfo.obtain(other.mRowCount, other.mColumnCount,
+                    other.mHierarchical);
+        }
+
+        /**
          * Obtains a pooled instance.
          *
          * @param rowCount The number of rows.
@@ -2796,6 +2822,18 @@
                 new SynchronizedPool<CollectionItemInfo>(MAX_POOL_SIZE);
 
         /**
+         * Obtains a pooled instance that is a clone of another one.
+         *
+         * @param other The instance to clone.
+         *
+         * @hide
+         */
+        public static CollectionItemInfo obtain(CollectionItemInfo other) {
+            return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan,
+                    other.mColumnIndex, other.mColumnSpan, other.mHeading);
+        }
+
+        /**
          * Obtains a pooled instance.
          *
          * @param rowIndex The row index at which the item is located.
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index d4c6abe..557239f 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -140,7 +140,7 @@
      *
      * @param listener the listener to add
      */
-    public void addCaptioningStateChangeListener(CaptioningChangeListener listener) {
+    public void addCaptioningChangeListener(CaptioningChangeListener listener) {
         synchronized (mListeners) {
             if (mListeners.isEmpty()) {
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
@@ -163,11 +163,11 @@
 
     /**
      * Removes a listener previously added using
-     * {@link #addCaptioningStateChangeListener}.
+     * {@link #addCaptioningChangeListener}.
      *
      * @param listener the listener to remove
      */
-    public void removeCaptioningStateChangeListener(CaptioningChangeListener listener) {
+    public void removeCaptioningChangeListener(CaptioningChangeListener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
 
@@ -366,7 +366,7 @@
      * Listener for changes in captioning properties, including enabled state
      * and user style preferences.
      */
-    public abstract class CaptioningChangeListener {
+    public static abstract class CaptioningChangeListener {
         /**
          * Called when the captioning enabled state changes.
          *
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 29b7cf2..7378d74 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1250,7 +1250,7 @@
             mFastScroller.setEnabled(true);
         }
 
-        recomputePadding();
+        resolvePadding();
 
         if (mFastScroller != null) {
             mFastScroller.updateLayout();
@@ -1312,7 +1312,11 @@
      * @see #setFastScrollAlwaysVisible(boolean)
      */
     public boolean isFastScrollAlwaysVisible() {
-        return mFastScrollEnabled && mFastScrollAlwaysVisible;
+        if (mFastScroller == null) {
+            return mFastScrollEnabled && mFastScrollAlwaysVisible;
+        } else {
+            return mFastScroller.isEnabled() && mFastScroller.isAlwaysShowEnabled();
+        }
     }
 
     @Override
@@ -1331,7 +1335,11 @@
      */
     @ViewDebug.ExportedProperty
     public boolean isFastScrollEnabled() {
-        return mFastScrollEnabled;
+        if (mFastScroller == null) {
+            return mFastScrollEnabled;
+        } else {
+            return mFastScroller.isEnabled();
+        }
     }
 
     @Override
@@ -1356,7 +1364,7 @@
      */
     @Override
     protected boolean isVerticalScrollBarHidden() {
-        return mFastScrollEnabled;
+        return isFastScrollEnabled();
     }
 
     /**
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index c48955f..006b96e 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -152,9 +152,6 @@
     /** The number of headers at the top of the view. */
     private int mHeaderCount;
 
-    /** The number of items in the list. */
-    private int mItemCount = -1;
-
     /** The index of the current section. */
     private int mCurrentSection = -1;
 
@@ -324,6 +321,7 @@
 
         getSectionsFromIndexer();
         refreshDrawablePressedState();
+        updateLongList(listView.getChildCount(), listView.getCount());
         setScrollbarPosition(mList.getVerticalScrollbarPosition());
         postAutoHide();
     }
@@ -343,14 +341,10 @@
      * @param enabled Whether the fast scroll thumb is enabled.
      */
     public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
+        if (mEnabled != enabled) {
+            mEnabled = enabled;
 
-        if (enabled) {
-            if (mAlwaysShow) {
-                setState(STATE_VISIBLE);
-            }
-        } else {
-            stop();
+            onStateDependencyChanged();
         }
     }
 
@@ -358,19 +352,17 @@
      * @return Whether the fast scroll thumb is enabled.
      */
     public boolean isEnabled() {
-        return mEnabled;
+        return mEnabled && (mLongList || mAlwaysShow);
     }
 
     /**
      * @param alwaysShow Whether the fast scroll thumb should always be shown
      */
     public void setAlwaysShow(boolean alwaysShow) {
-        mAlwaysShow = alwaysShow;
+        if (mAlwaysShow != alwaysShow) {
+            mAlwaysShow = alwaysShow;
 
-        if (alwaysShow) {
-            setState(STATE_VISIBLE);
-        } else if (mState == STATE_VISIBLE) {
-            postAutoHide();
+            onStateDependencyChanged();
         }
     }
 
@@ -382,6 +374,23 @@
         return mAlwaysShow;
     }
 
+    /**
+     * Called when one of the variables affecting enabled state changes.
+     */
+    private void onStateDependencyChanged() {
+        if (isEnabled()) {
+            if (isAlwaysShowEnabled()) {
+                setState(STATE_VISIBLE);
+            } else if (mState == STATE_VISIBLE) {
+                postAutoHide();
+            }
+        } else {
+            stop();
+        }
+
+        mList.resolvePadding();
+    }
+
     public void setScrollBarStyle(int style) {
         if (mScrollBarStyle != style) {
             mScrollBarStyle = style;
@@ -439,6 +448,18 @@
             final int firstVisibleItem = mList.getFirstVisiblePosition();
             setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
         }
+
+        updateLongList(visibleItemCount, totalItemCount);
+    }
+
+    private void updateLongList(int visibleItemCount, int totalItemCount) {
+        final boolean longList = visibleItemCount > 0
+                && totalItemCount / visibleItemCount >= MIN_PAGES;
+        if (mLongList != longList) {
+            mLongList = longList;
+
+            onStateDependencyChanged();
+        }
     }
 
     /**
@@ -795,19 +816,8 @@
         mList.postDelayed(mDeferHide, FADE_TIMEOUT);
     }
 
-    private boolean isLongList(int visibleItemCount, int totalItemCount) {
-        // Are there enough pages to require fast scroll? Recompute only if
-        // total count changes.
-        if (mItemCount != totalItemCount && visibleItemCount > 0) {
-            mItemCount = totalItemCount;
-            mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
-        }
-
-        return mLongList;
-    }
-
     public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-        if (!mEnabled || !mAlwaysShow && !isLongList(visibleItemCount, totalItemCount)) {
+        if (!isEnabled()) {
             setState(STATE_NONE);
             return;
         }
@@ -1221,7 +1231,7 @@
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (!mEnabled) {
+        if (!isEnabled()) {
             return false;
         }
 
@@ -1233,14 +1243,18 @@
                     // need to allow the parent time to decide whether it wants
                     // to intercept events. If it does, we will receive a CANCEL
                     // event.
-                    if (mList.isInScrollingContainer()) {
-                        mInitialTouchY = ev.getY();
-                        startPendingDrag();
-                        return false;
+                    if (!mList.isInScrollingContainer()) {
+                        beginDrag();
+                        return true;
                     }
 
-                    beginDrag();
-                    return true;
+                    mInitialTouchY = ev.getY();
+                    startPendingDrag();
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (!isPointInside(ev.getX(), ev.getY())) {
+                    cancelPendingDrag();
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -1253,7 +1267,7 @@
     }
 
     public boolean onInterceptHoverEvent(MotionEvent ev) {
-        if (!mEnabled) {
+        if (!isEnabled()) {
             return false;
         }
 
@@ -1269,18 +1283,11 @@
     }
 
     public boolean onTouchEvent(MotionEvent me) {
-        if (!mEnabled) {
+        if (!isEnabled()) {
             return false;
         }
 
         switch (me.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                if (isPointInside(me.getX(), me.getY())) {
-                    beginDrag();
-                    return true;
-                }
-            } break;
-
             case MotionEvent.ACTION_UP: {
                 if (mHasPendingDrag) {
                     // Allow a tap to scroll.
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index f449797..009b729 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -30,6 +30,7 @@
 import android.media.MediaPlayer.OnInfoListener;
 import android.media.Metadata;
 import android.media.SubtitleController;
+import android.media.SubtitleTrack.RenderingWidget;
 import android.media.WebVttRenderer;
 import android.net.Uri;
 import android.util.AttributeSet;
@@ -46,7 +47,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Map;
 import java.util.Vector;
 
@@ -100,14 +100,11 @@
     private boolean     mCanSeekBack;
     private boolean     mCanSeekForward;
 
-    /** List of views overlaid on top of the video. */
-    private ArrayList<View> mOverlays;
+    /** Subtitle rendering widget overlaid on top of the video. */
+    private RenderingWidget mSubtitleWidget;
 
-    /**
-     * Listener for overlay layout changes. Invalidates the video view to ensure
-     * that captions are redrawn whenever their layout changes.
-     */
-    private OnLayoutChangeListener mOverlayLayoutListener;
+    /** Listener for changes to subtitle data, used to redraw when needed. */
+    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
 
     public VideoView(Context context) {
         super(context);
@@ -302,11 +299,10 @@
             mMediaPlayer = new MediaPlayer();
             // TODO: create SubtitleController in MediaPlayer, but we need
             // a context for the subtitle renderers
-            SubtitleController controller = new SubtitleController(
-                    getContext(),
-                    mMediaPlayer.getMediaTimeProvider(),
-                    mMediaPlayer);
-            controller.registerRenderer(new WebVttRenderer(getContext(), null));
+            final Context context = getContext();
+            final SubtitleController controller = new SubtitleController(
+                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
+            controller.registerRenderer(new WebVttRenderer(context));
             mMediaPlayer.setSubtitleAnchor(controller, this);
 
             if (mAudioSession != 0) {
@@ -792,12 +788,29 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onAttachedToWindow();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onDetachedFromWindow();
+        }
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        // Layout overlay views, if necessary.
-        if (changed && mOverlays != null && !mOverlays.isEmpty()) {
-            measureAndLayoutOverlays();
+        if (mSubtitleWidget != null) {
+            measureAndLayoutSubtitleWidget();
         }
     }
 
@@ -805,104 +818,65 @@
     public void draw(Canvas canvas) {
         super.draw(canvas);
 
-        final int count = mOverlays.size();
-        for (int i = 0; i < count; i++) {
-            final View overlay = mOverlays.get(i);
-            overlay.draw(canvas);
+        if (mSubtitleWidget != null) {
+            final int saveCount = canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingTop());
+            mSubtitleWidget.draw(canvas);
+            canvas.restoreToCount(saveCount);
         }
     }
 
     /**
-     * Adds a view to be overlaid on top of this video view. During layout, the
-     * view will be forced to match the bounds, less padding, of the video view.
-     * <p>
-     * Overlays are drawn in the order they are added. The last added overlay
-     * will be drawn on top.
-     *
-     * @param overlay the view to overlay
-     * @see #removeOverlay(View)
-     */
-    private void addOverlay(View overlay) {
-        if (mOverlays == null) {
-            mOverlays = new ArrayList<View>(1);
-        }
-
-        if (mOverlayLayoutListener == null) {
-            mOverlayLayoutListener = new OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    invalidate();
-                }
-            };
-        }
-
-        if (mOverlays.isEmpty()) {
-            setWillNotDraw(false);
-        }
-
-        mOverlays.add(overlay);
-        overlay.addOnLayoutChangeListener(mOverlayLayoutListener);
-        measureAndLayoutOverlays();
-    }
-
-    /**
-     * Removes a view previously added using {@link #addOverlay}.
-     *
-     * @param overlay the view to remove
-     * @see #addOverlay(View)
-     */
-    private void removeOverlay(View overlay) {
-        if (mOverlays == null) {
-            return;
-        }
-
-        overlay.removeOnLayoutChangeListener(mOverlayLayoutListener);
-        mOverlays.remove(overlay);
-
-        if (mOverlays.isEmpty()) {
-            setWillNotDraw(true);
-        }
-
-        invalidate();
-    }
-
-    /**
      * Forces a measurement and layout pass for all overlaid views.
      *
-     * @see #addOverlay(View)
+     * @see #setSubtitleWidget(RenderingWidget)
      */
-    private void measureAndLayoutOverlays() {
-        final int left = getPaddingLeft();
-        final int top = getPaddingTop();
-        final int right = getWidth() - left - getPaddingRight();
-        final int bottom = getHeight() - top - getPaddingBottom();
-        final int widthSpec = MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY);
-        final int heightSpec = MeasureSpec.makeMeasureSpec(bottom - top, MeasureSpec.EXACTLY);
+    private void measureAndLayoutSubtitleWidget() {
+        final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+        final int height = getHeight() - getPaddingTop() - getPaddingBottom();
 
-        final int count = mOverlays.size();
-        for (int i = 0; i < count; i++) {
-            final View overlay = mOverlays.get(i);
-            overlay.measure(widthSpec, heightSpec);
-            overlay.layout(left, top, right, bottom);
-        }
+        mSubtitleWidget.setSize(width, height);
     }
 
     /** @hide */
     @Override
-    public void setSubtitleView(View view) {
-        if (mSubtitleView == view) {
+    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+        if (mSubtitleWidget == subtitleWidget) {
             return;
         }
 
-        if (mSubtitleView != null) {
-            removeOverlay(mSubtitleView);
-        }
-        mSubtitleView = view;
-        if (mSubtitleView != null) {
-            addOverlay(mSubtitleView);
-        }
-    }
+        final boolean attachedToWindow = isAttachedToWindow();
+        if (mSubtitleWidget != null) {
+            if (attachedToWindow) {
+                mSubtitleWidget.onDetachedFromWindow();
+            }
 
-    private View mSubtitleView;
+            mSubtitleWidget.setOnChangedListener(null);
+        }
+
+        mSubtitleWidget = subtitleWidget;
+
+        if (subtitleWidget != null) {
+            if (mSubtitlesChangedListener == null) {
+                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+                    @Override
+                    public void onChanged(RenderingWidget renderingWidget) {
+                        invalidate();
+                    }
+                };
+            }
+
+            setWillNotDraw(false);
+            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+            if (attachedToWindow) {
+                subtitleWidget.onAttachedToWindow();
+                requestLayout();
+            }
+        } else {
+            setWillNotDraw(true);
+        }
+
+        invalidate();
+    }
 }
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 16b119a..1f55a4c 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -165,7 +165,7 @@
     static final String CSV_SEP = "\t";
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 9;
+    private static final int PARCEL_VERSION = 11;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535453;
 
@@ -204,6 +204,12 @@
     int[] mAddLongTable;
     int mAddLongTableSize;
 
+    // For writing parcels.
+    ArrayMap<String, Integer> mCommonStringToIndex;
+
+    // For reading parcels.
+    ArrayList<String> mIndexToCommonString;
+
     public ProcessStats(boolean running) {
         mRunning = running;
         reset();
@@ -247,7 +253,7 @@
                     if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
                             + " service " + otherSvc.mName);
                     ServiceState thisSvc = getServiceStateLocked(pkgName, uid,
-                            null, otherSvc.mName);
+                            otherSvc.mProcessName, otherSvc.mName);
                     thisSvc.add(otherSvc);
                 }
             }
@@ -959,7 +965,15 @@
         for (int ip=procMap.size()-1; ip>=0; ip--) {
             SparseArray<ProcessState> uids = procMap.valueAt(ip);
             for (int iu=uids.size()-1; iu>=0; iu--) {
-                uids.valueAt(iu).resetSafely(now);
+                ProcessState ps = uids.valueAt(iu);
+                if (ps.isInUse()) {
+                    uids.valueAt(iu).resetSafely(now);
+                } else {
+                    uids.removeAt(iu);
+                }
+            }
+            if (uids.size() <= 0) {
+                procMap.removeAt(ip);
             }
         }
         ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
@@ -968,16 +982,27 @@
             for (int iu=uids.size()-1; iu>=0; iu--) {
                 PackageState pkgState = uids.valueAt(iu);
                 for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
-                    pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+                    ProcessState ps = pkgState.mProcesses.valueAt(iproc);
+                    if (ps.isInUse()) {
+                        pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+                    } else {
+                        pkgState.mProcesses.removeAt(iproc);
+                    }
                 }
                 for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
                     ServiceState ss = pkgState.mServices.valueAt(isvc);
-                    if (ss.isActive()) {
+                    if (ss.isInUse()) {
                         pkgState.mServices.valueAt(isvc).resetSafely(now);
                     } else {
                         pkgState.mServices.removeAt(isvc);
                     }
                 }
+                if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+                    uids.removeAt(iu);
+                }
+            }
+            if (uids.size() <= 0) {
+                pkgMap.removeAt(ip);
             }
         }
         mStartTime = SystemClock.uptimeMillis();
@@ -1048,6 +1073,75 @@
         return table;
     }
 
+    private void writeCompactedLongArray(Parcel out, long[] array) {
+        final int N = array.length;
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            long val = array[i];
+            if (val < 0) {
+                Slog.w(TAG, "Time val negative: " + val);
+                val = 0;
+            }
+            if (val <= Integer.MAX_VALUE) {
+                out.writeInt((int)val);
+            } else {
+                int top = ~((int)((val>>32)&0x7fffffff));
+                int bottom = (int)(val&0xfffffff);
+                out.writeInt(top);
+                out.writeInt(bottom);
+            }
+        }
+    }
+
+    private void readCompactedLongArray(Parcel in, int version, long[] array) {
+        if (version <= 10) {
+            in.readLongArray(array);
+            return;
+        }
+        final int N = in.readInt();
+        if (N != array.length) {
+            throw new RuntimeException("bad array lengths");
+        }
+        for (int i=0; i<N; i++) {
+            int val = in.readInt();
+            if (val >= 0) {
+                array[i] = val;
+            } else {
+                int bottom = in.readInt();
+                array[i] = (((long)~val)<<32) | bottom;
+            }
+        }
+    }
+
+    private void writeCommonString(Parcel out, String name) {
+        Integer index = mCommonStringToIndex.get(name);
+        if (index != null) {
+            out.writeInt(index);
+            return;
+        }
+        index = mCommonStringToIndex.size();
+        mCommonStringToIndex.put(name, index);
+        out.writeInt(~index);
+        out.writeString(name);
+    }
+
+    private String readCommonString(Parcel in, int version) {
+        if (version <= 9) {
+            return in.readString();
+        }
+        int index = in.readInt();
+        if (index >= 0) {
+            return mIndexToCommonString.get(index);
+        }
+        index = ~index;
+        String name = in.readString();
+        while (mIndexToCommonString.size() <= index) {
+            mIndexToCommonString.add(null);
+        }
+        mIndexToCommonString.set(index, name);
+        return name;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -1063,6 +1157,8 @@
         out.writeInt(PSS_COUNT);
         out.writeInt(LONGS_SIZE);
 
+        mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
+
         // First commit all running times.
         ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
         final int NPROC = procMap.size();
@@ -1104,7 +1200,7 @@
         out.writeInt(mLongs.size());
         out.writeInt(mNextLong);
         for (int i=0; i<(mLongs.size()-1); i++) {
-            out.writeLongArray(mLongs.get(i));
+            writeCompactedLongArray(out, mLongs.get(i));
         }
         long[] lastLongs = mLongs.get(mLongs.size() - 1);
         for (int i=0; i<mNextLong; i++) {
@@ -1116,24 +1212,24 @@
             mMemFactorDurations[mMemFactor] += now - mStartTime;
             mStartTime = now;
         }
-        out.writeLongArray(mMemFactorDurations);
+        writeCompactedLongArray(out, mMemFactorDurations);
 
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
-            out.writeString(procMap.keyAt(ip));
+            writeCommonString(out, procMap.keyAt(ip));
             SparseArray<ProcessState> uids = procMap.valueAt(ip);
             final int NUID = uids.size();
             out.writeInt(NUID);
             for (int iu=0; iu<NUID; iu++) {
                 out.writeInt(uids.keyAt(iu));
                 ProcessState proc = uids.valueAt(iu);
-                out.writeString(proc.mPackage);
+                writeCommonString(out, proc.mPackage);
                 proc.writeToParcel(out, now);
             }
         }
         out.writeInt(NPKG);
         for (int ip=0; ip<NPKG; ip++) {
-            out.writeString(pkgMap.keyAt(ip));
+            writeCommonString(out, pkgMap.keyAt(ip));
             SparseArray<PackageState> uids = pkgMap.valueAt(ip);
             final int NUID = uids.size();
             out.writeInt(NUID);
@@ -1143,7 +1239,7 @@
                 final int NPROCS = pkgState.mProcesses.size();
                 out.writeInt(NPROCS);
                 for (int iproc=0; iproc<NPROCS; iproc++) {
-                    out.writeString(pkgState.mProcesses.keyAt(iproc));
+                    writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
                     ProcessState proc = pkgState.mProcesses.valueAt(iproc);
                     if (proc.mCommonProcess == proc) {
                         // This is the same as the common process we wrote above.
@@ -1159,10 +1255,13 @@
                 for (int isvc=0; isvc<NSRVS; isvc++) {
                     out.writeString(pkgState.mServices.keyAt(isvc));
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    writeCommonString(out, svc.mProcessName);
                     svc.writeToParcel(out, now);
                 }
             }
         }
+
+        mCommonStringToIndex = null;
     }
 
     private boolean readCheckedInt(Parcel in, int val, String what) {
@@ -1222,7 +1321,7 @@
             return;
         }
         int version = in.readInt();
-        if (version != PARCEL_VERSION && version != 6) {
+        if (version != PARCEL_VERSION) {
             mReadError = "bad version: " + version;
             return;
         }
@@ -1239,14 +1338,14 @@
             return;
         }
 
+        mIndexToCommonString = new ArrayList<String>();
+
         mTimePeriodStartClock = in.readLong();
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = in.readLong();
         mTimePeriodEndRealtime = in.readLong();
-        if (version ==  PARCEL_VERSION) {
-            mRuntime = in.readString();
-            mWebView = in.readString();
-        }
+        mRuntime = in.readString();
+        mWebView = in.readString();
         mFlags = in.readInt();
 
         final int NLONGS = in.readInt();
@@ -1256,7 +1355,7 @@
             while (i >= mLongs.size()) {
                 mLongs.add(new long[LONGS_SIZE]);
             }
-            in.readLongArray(mLongs.get(i));
+            readCompactedLongArray(in, version, mLongs.get(i));
         }
         long[] longs = new long[LONGS_SIZE];
         mNextLong = NEXTLONG;
@@ -1266,7 +1365,7 @@
         }
         mLongs.add(longs);
 
-        in.readLongArray(mMemFactorDurations);
+        readCompactedLongArray(in, version, mMemFactorDurations);
 
         int NPROC = in.readInt();
         if (NPROC < 0) {
@@ -1275,7 +1374,7 @@
         }
         while (NPROC > 0) {
             NPROC--;
-            String procName = in.readString();
+            String procName = readCommonString(in, version);
             if (procName == null) {
                 mReadError = "bad process name";
                 return;
@@ -1292,7 +1391,7 @@
                     mReadError = "bad uid: " + uid;
                     return;
                 }
-                String pkgName = in.readString();
+                String pkgName = readCommonString(in, version);
                 if (pkgName == null) {
                     mReadError = "bad process package name";
                     return;
@@ -1322,7 +1421,7 @@
         }
         while (NPKG > 0) {
             NPKG--;
-            String pkgName = in.readString();
+            String pkgName = readCommonString(in, version);
             if (pkgName == null) {
                 mReadError = "bad package name";
                 return;
@@ -1348,7 +1447,7 @@
                 }
                 while (NPROCS > 0) {
                     NPROCS--;
-                    String procName = in.readString();
+                    String procName = readCommonString(in, version);
                     if (procName == null) {
                         mReadError = "bad package process name";
                         return;
@@ -1400,9 +1499,10 @@
                         mReadError = "bad package service name";
                         return;
                     }
+                    String processName = version > 9 ? readCommonString(in, version) : null;
                     ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
                     if (serv == null) {
-                        serv = new ServiceState(this, pkgName, serviceName, null);
+                        serv = new ServiceState(this, pkgName, serviceName, processName, null);
                     }
                     if (!serv.readFromParcel(in)) {
                         return;
@@ -1414,6 +1514,8 @@
             }
         }
 
+        mIndexToCommonString = null;
+
         if (DEBUG) Slog.d(TAG, "Successfully read procstats!");
     }
 
@@ -1555,12 +1657,11 @@
         final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid);
         ProcessStats.ServiceState ss = as.mServices.get(className);
         if (ss != null) {
-            ss.makeActive();
             return ss;
         }
         final ProcessStats.ProcessState ps = processName != null
                 ? getProcessStateLocked(packageName, uid, processName) : null;
-        ss = new ProcessStats.ServiceState(this, packageName, className, ps);
+        ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps);
         as.mServices.put(className, ss);
         return ss;
     }
@@ -1602,10 +1703,10 @@
                                 ALL_PROC_STATES, now);
                         dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                                 ALL_PROC_STATES);
-                        if (dumpAll) {
-                            pw.print("        mNumStartedServices=");
-                                    pw.println(proc.mNumStartedServices);
-                        }
+                        pw.print("        mActive="); pw.println(proc.mActive);
+                        pw.print("        mNumActiveServices="); pw.print(proc.mNumActiveServices);
+                                pw.print(" mNumStartedServices=");
+                                pw.println(proc.mNumStartedServices);
                     }
                 } else {
                     ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
@@ -1624,6 +1725,9 @@
                     pw.print(pkgState.mServices.keyAt(isvc));
                     pw.println(":");
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    dumpServiceStats(pw, "        ", "          ", "    ", "Running", svc,
+                            svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
+                            svc.mRunStartTime, now, totalTime, dumpAll);
                     dumpServiceStats(pw, "        ", "          ", "    ", "Started", svc,
                             svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
                             svc.mStartedStartTime, now, totalTime, dumpAll);
@@ -1633,6 +1737,9 @@
                     dumpServiceStats(pw, "        ", "          ", "  ", "Executing", svc,
                             svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
                             svc.mExecStartTime, now, totalTime, dumpAll);
+                    if (dumpAll) {
+                        pw.print("        mActive="); pw.println(svc.mActive);
+                    }
                 }
             }
         }
@@ -1663,6 +1770,12 @@
                             ALL_PROC_STATES, now);
                     dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                             ALL_PROC_STATES);
+                    if (dumpAll) {
+                        pw.print("        mActive="); pw.println(proc.mActive);
+                        pw.print("        mNumActiveServices="); pw.print(proc.mNumActiveServices);
+                                pw.print(" mNumStartedServices=");
+                                pw.println(proc.mNumStartedServices);
+                    }
                 }
             }
 
@@ -1929,6 +2042,9 @@
                     String serviceName = collapseString(pkgName,
                             pkgState.mServices.keyAt(isvc));
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName,
+                            svc, ServiceState.SERVICE_RUN, svc.mRunCount,
+                            svc.mRunState, svc.mRunStartTime, now);
                     dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
                             svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
                             svc.mStartedState, svc.mStartedStartTime, now);
@@ -2003,6 +2119,8 @@
         int[] mPssTable;
         int mPssTableSize;
 
+        boolean mActive;
+        int mNumActiveServices;
         int mNumStartedServices;
 
         int mNumExcessiveWake;
@@ -2072,6 +2190,7 @@
             }
             pnew.mNumExcessiveWake = mNumExcessiveWake;
             pnew.mNumExcessiveCpu = mNumExcessiveCpu;
+            pnew.mActive = mActive;
             pnew.mNumStartedServices = mNumStartedServices;
             return pnew;
         }
@@ -2151,6 +2270,18 @@
             return true;
         }
 
+        public void makeActive() {
+            mActive = true;
+        }
+
+        public void makeInactive() {
+            mActive = false;
+        }
+
+        public boolean isInUse() {
+            return mActive || mNumActiveServices > 0 || mNumStartedServices > 0;
+        }
+
         /**
          * Update the current state of the given list of processes.
          *
@@ -2219,6 +2350,24 @@
             longs[(off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK] += dur;
         }
 
+        void incActiveServices() {
+            if (mCommonProcess != this) {
+                mCommonProcess.incActiveServices();
+            }
+            mNumActiveServices++;
+        }
+
+        void decActiveServices() {
+            if (mCommonProcess != this) {
+                mCommonProcess.decActiveServices();
+            }
+            mNumActiveServices--;
+            if (mNumActiveServices < 0) {
+                throw new IllegalStateException("Proc active services underrun: pkg="
+                        + mPackage + " uid=" + mUid + " name=" + mName);
+            }
+        }
+
         void incStartedServices(int memFactor, long now) {
             if (mCommonProcess != this) {
                 mCommonProcess.incStartedServices(memFactor, now);
@@ -2406,18 +2555,24 @@
         final ProcessStats mStats;
         public final String mPackage;
         public final String mName;
+        public final String mProcessName;
         ProcessState mProc;
 
-        int mActive = 1;
+        int mActive = 0;
 
-        public static final int SERVICE_STARTED = 0;
-        public static final int SERVICE_BOUND = 1;
-        public static final int SERVICE_EXEC = 2;
-        static final int SERVICE_COUNT = 3;
+        public static final int SERVICE_RUN = 0;
+        public static final int SERVICE_STARTED = 1;
+        public static final int SERVICE_BOUND = 2;
+        public static final int SERVICE_EXEC = 3;
+        static final int SERVICE_COUNT = 4;
 
         int[] mDurationsTable;
         int mDurationsTableSize;
 
+        int mRunCount;
+        public int mRunState = STATE_NOTHING;
+        long mRunStartTime;
+
         int mStartedCount;
         public int mStartedState = STATE_NOTHING;
         long mStartedStartTime;
@@ -2430,14 +2585,19 @@
         public int mExecState = STATE_NOTHING;
         long mExecStartTime;
 
-        public ServiceState(ProcessStats processStats, String pkg, String name, ProcessState proc) {
+        public ServiceState(ProcessStats processStats, String pkg, String name,
+                String processName, ProcessState proc) {
             mStats = processStats;
             mPackage = pkg;
             mName = name;
+            mProcessName = processName;
             mProc = proc;
         }
 
         public void makeActive() {
+            if (mActive == 0) {
+                mProc.incActiveServices();
+            }
             mActive++;
         }
 
@@ -2448,9 +2608,12 @@
             Slog.i(TAG, "Making " + this + " inactive", here);
             */
             mActive--;
+            if (mActive == 0) {
+                mProc.decActiveServices();
+            }
         }
 
-        public boolean isActive() {
+        public boolean isInUse() {
             return mActive > 0;
         }
 
@@ -2460,6 +2623,7 @@
                 int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
                 addStateTime(state, other.mStats.getLong(ent, 0));
             }
+            mRunCount += other.mRunCount;
             mStartedCount += other.mStartedCount;
             mBoundCount += other.mBoundCount;
             mExecCount += other.mExecCount;
@@ -2468,6 +2632,7 @@
         void resetSafely(long now) {
             mDurationsTable = null;
             mDurationsTableSize = 0;
+            mRunCount = mRunState != STATE_NOTHING ? 1 : 0;
             mStartedCount = mStartedState != STATE_NOTHING ? 1 : 0;
             mBoundCount = mBoundState != STATE_NOTHING ? 1 : 0;
             mExecCount = mExecState != STATE_NOTHING ? 1 : 0;
@@ -2481,6 +2646,7 @@
                         + printLongOffset(mDurationsTable[i]));
                 out.writeInt(mDurationsTable[i]);
             }
+            out.writeInt(mRunCount);
             out.writeInt(mStartedCount);
             out.writeInt(mBoundCount);
             out.writeInt(mExecCount);
@@ -2493,6 +2659,7 @@
                 return false;
             }
             mDurationsTableSize = mDurationsTable != null ? mDurationsTable.length : 0;
+            mRunCount = in.readInt();
             mStartedCount = in.readInt();
             mBoundCount = in.readInt();
             mExecCount = in.readInt();
@@ -2518,6 +2685,10 @@
         }
 
         void commitStateTime(long now) {
+            if (mRunState != STATE_NOTHING) {
+                addStateTime(SERVICE_RUN + (mRunState*SERVICE_COUNT), now - mRunStartTime);
+                mRunStartTime = now;
+            }
             if (mStartedState != STATE_NOTHING) {
                 addStateTime(SERVICE_STARTED + (mStartedState*SERVICE_COUNT),
                         now - mStartedStartTime);
@@ -2533,6 +2704,21 @@
             }
         }
 
+        private void updateRunning(int memFactor, long now) {
+            final int state = (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
+                    || mExecState != STATE_NOTHING) ? memFactor : STATE_NOTHING;
+            if (mRunState != state) {
+                if (mRunState != STATE_NOTHING) {
+                    addStateTime(SERVICE_RUN + (mRunState*SERVICE_COUNT),
+                            now - mRunStartTime);
+                } else if (state != STATE_NOTHING) {
+                    mRunCount++;
+                }
+                mRunState = state;
+                mRunStartTime = now;
+            }
+        }
+
         public void setStarted(boolean started, int memFactor, long now) {
             if (mActive <= 0) {
                 throw new IllegalStateException("Service " + this + " has mActive=" + mActive);
@@ -2556,6 +2742,7 @@
                         mProc.decStartedServices(memFactor, now);
                     }
                 }
+                updateRunning(memFactor, now);
             }
         }
 
@@ -2573,6 +2760,7 @@
                 }
                 mBoundState = state;
                 mBoundStartTime = now;
+                updateRunning(memFactor, now);
             }
         }
 
@@ -2589,6 +2777,7 @@
                 }
                 mExecState = state;
                 mExecStartTime = now;
+                updateRunning(memFactor, now);
             }
         }
 
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a85d5fe..5538dca 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -17,6 +17,7 @@
 package com.android.internal.os;
 
 import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
 import android.os.Build;
 import android.os.Debug;
@@ -69,7 +70,14 @@
                 if (mApplicationObject == null) {
                     Slog.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
                 } else {
-                    Slog.e(TAG, "FATAL EXCEPTION: " + t.getName(), e);
+                    StringBuilder message = new StringBuilder();
+                    message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
+                    final String processName = ActivityThread.currentProcessName();
+                    if (processName != null) {
+                        message.append("Process: ").append(processName).append(", ");
+                    }
+                    message.append("PID: ").append(Process.myPid());
+                    Slog.e(TAG, message.toString(), e);
                 }
 
                 // Bring up crash dialog, wait for it to be dismissed
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index f060efd..c1ae9c2 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -156,7 +156,7 @@
     }
 
     @Override
-    public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+    public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
         View actionView = item.getActionView();
         if (actionView == null || item.hasCollapsibleActionView()) {
             if (!(convertView instanceof ActionMenuItemView)) {
@@ -166,6 +166,27 @@
         }
         actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
 
+        if (item.hasSubMenu()) {
+            actionView.setOnTouchListener(new ForwardingListener(actionView) {
+                @Override
+                public ListPopupWindow getPopup() {
+                    return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
+                }
+
+                @Override
+                protected boolean onForwardingStarted() {
+                    return onSubMenuSelected((SubMenuBuilder) item.getSubMenu());
+                }
+
+                @Override
+                protected boolean onForwardingStopped() {
+                    return dismissPopupMenus();
+                }
+            });
+        } else {
+            actionView.setOnTouchListener(null);
+        }
+
         final ActionMenuView menuParent = (ActionMenuView) parent;
         final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
         if (!menuParent.checkLayoutParams(lp)) {
diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java
index afa4103..7a294aa 100644
--- a/core/java/com/android/internal/widget/AutoScrollHelper.java
+++ b/core/java/com/android/internal/widget/AutoScrollHelper.java
@@ -66,7 +66,7 @@
  * The following scrolling properties may be configured:
  * <ul>
  * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default
- * value is 2500 milliseconds.
+ * value is 500 milliseconds.
  * <li>Acceleration ramp-down duration, see {@link #setRampDownDuration}.
  * Default value is 500 milliseconds.
  * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}.
@@ -191,7 +191,7 @@
     private static final float DEFAULT_RELATIVE_EDGE = 0.2f;
     private static final float DEFAULT_RELATIVE_VELOCITY = 1f;
     private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout();
-    private static final int DEFAULT_RAMP_UP_DURATION = 2500;
+    private static final int DEFAULT_RAMP_UP_DURATION = 500;
     private static final int DEFAULT_RAMP_DOWN_DURATION = 500;
 
     /**
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 13101512..356401c 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -74,6 +74,10 @@
     private float mSpacingAdd = 0;
     private int mInnerPaddingX = 0;
 
+    public SubtitleView(Context context) {
+        this(context, null);
+    }
+
     public SubtitleView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a17b301..490d85c 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -53,6 +53,7 @@
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
 extern int register_android_graphics_Camera(JNIEnv* env);
+extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
 extern int register_android_graphics_Graphics(JNIEnv* env);
 extern int register_android_graphics_Interpolator(JNIEnv* env);
 extern int register_android_graphics_LayerRasterizer(JNIEnv*);
@@ -1137,6 +1138,7 @@
     REG_JNI(register_android_graphics_BitmapFactory),
     REG_JNI(register_android_graphics_BitmapRegionDecoder),
     REG_JNI(register_android_graphics_Camera),
+    REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
     REG_JNI(register_android_graphics_Canvas),
     REG_JNI(register_android_graphics_ColorFilter),
     REG_JNI(register_android_graphics_DrawFilter),
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index d264392..2d06b68 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -7,12 +7,6 @@
 #include "Utils.h"
 #include <androidfw/Asset.h>
 
-#define RETURN_NULL_IF_NULL(value) \
-    do { if (!(value)) { SkASSERT(0); return NULL; } } while (false)
-
-#define RETURN_ZERO_IF_NULL(value) \
-    do { if (!(value)) { SkASSERT(0); return 0; } } while (false)
-
 static jmethodID    gInputStream_resetMethodID;
 static jmethodID    gInputStream_markMethodID;
 static jmethodID    gInputStream_markSupportedMethodID;
@@ -161,32 +155,6 @@
 
 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                        jbyteArray storage) {
-    static bool gInited;
-
-    if (!gInited) {
-        jclass inputStream_Clazz = env->FindClass("java/io/InputStream");
-        RETURN_NULL_IF_NULL(inputStream_Clazz);
-
-        gInputStream_resetMethodID      = env->GetMethodID(inputStream_Clazz,
-                                                           "reset", "()V");
-        gInputStream_markMethodID       = env->GetMethodID(inputStream_Clazz,
-                                                           "mark", "(I)V");
-        gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz,
-                                                              "markSupported", "()Z");
-        gInputStream_readMethodID       = env->GetMethodID(inputStream_Clazz,
-                                                           "read", "([BII)I");
-        gInputStream_skipMethodID       = env->GetMethodID(inputStream_Clazz,
-                                                           "skip", "(J)J");
-
-        RETURN_NULL_IF_NULL(gInputStream_resetMethodID);
-        RETURN_NULL_IF_NULL(gInputStream_markMethodID);
-        RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID);
-        RETURN_NULL_IF_NULL(gInputStream_readMethodID);
-        RETURN_NULL_IF_NULL(gInputStream_skipMethodID);
-
-        gInited = true;
-    }
-
     return new JavaInputStreamAdaptor(env, stream, storage);
 }
 
@@ -263,27 +231,19 @@
     const size_t            fLength;
 };
 
+static jclass   gByteArrayInputStream_Clazz;
+static jfieldID gCountField;
+static jfieldID gPosField;
+
 /**
  *  If jstream is a ByteArrayInputStream, return its remaining length. Otherwise
  *  return 0.
  */
 static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) {
-    static jclass byteArrayInputStream_Clazz;
-    static jfieldID countField;
-    static jfieldID posField;
-
-    byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream");
-    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
-
-    countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I");
-    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
-    posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I");
-    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
-
-    if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) {
+    if (env->IsInstanceOf(jstream, gByteArrayInputStream_Clazz)) {
         // Return the remaining length, to keep the same behavior of using the rest of the
         // stream.
-        return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField);
+        return env->GetIntField(jstream, gCountField) - env->GetIntField(jstream, gPosField);
     }
     return 0;
 }
@@ -321,21 +281,15 @@
     return adaptor_to_mem_stream(adaptor.get());
 }
 
+static jclass       gAssetInputStream_Clazz;
+static jmethodID    gGetAssetIntMethodID;
+
 android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) {
-    static jclass assetInputStream_Clazz;
-    static jmethodID getAssetIntMethodID;
-
-    assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream");
-    RETURN_NULL_IF_NULL(assetInputStream_Clazz);
-
-    getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I");
-    RETURN_NULL_IF_NULL(getAssetIntMethodID);
-
-    if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) {
+    if (!env->IsInstanceOf(jstream, gAssetInputStream_Clazz)) {
         return NULL;
     }
 
-    jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID);
+    jint jasset = env->CallIntMethod(jstream, gGetAssetIntMethodID);
     android::Asset* a = reinterpret_cast<android::Asset*>(jasset);
     if (NULL == a) {
         jniThrowNullPointerException(env, "NULL native asset");
@@ -406,18 +360,57 @@
     static bool gInited;
 
     if (!gInited) {
-        jclass outputStream_Clazz = env->FindClass("java/io/OutputStream");
-        RETURN_NULL_IF_NULL(outputStream_Clazz);
-
-        gOutputStream_writeMethodID = env->GetMethodID(outputStream_Clazz,
-                                                       "write", "([BII)V");
-        RETURN_NULL_IF_NULL(gOutputStream_writeMethodID);
-        gOutputStream_flushMethodID = env->GetMethodID(outputStream_Clazz,
-                                                       "flush", "()V");
-        RETURN_NULL_IF_NULL(gOutputStream_flushMethodID);
 
         gInited = true;
     }
 
     return new SkJavaOutputStream(env, stream, storage);
 }
+
+static jclass findClassCheck(JNIEnv* env, const char classname[]) {
+    jclass clazz = env->FindClass(classname);
+    SkASSERT(!env->ExceptionCheck());
+    return clazz;
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+                                const char fieldname[], const char type[]) {
+    jfieldID id = env->GetFieldID(clazz, fieldname, type);
+    SkASSERT(!env->ExceptionCheck());
+    return id;
+}
+
+static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
+                                  const char methodname[], const char type[]) {
+    jmethodID id = env->GetMethodID(clazz, methodname, type);
+    SkASSERT(!env->ExceptionCheck());
+    return id;
+}
+
+int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) {
+    jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
+    gInputStream_resetMethodID = getMethodIDCheck(env, inputStream_Clazz, "reset", "()V");
+    gInputStream_markMethodID = getMethodIDCheck(env, inputStream_Clazz, "mark", "(I)V");
+    gInputStream_markSupportedMethodID = getMethodIDCheck(env, inputStream_Clazz, "markSupported", "()Z");
+    gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
+    gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
+
+    gByteArrayInputStream_Clazz = findClassCheck(env, "java/io/ByteArrayInputStream");
+    // Ref gByteArrayInputStream_Clazz so we can continue to refer to it when
+    // calling IsInstance.
+    gByteArrayInputStream_Clazz = (jclass) env->NewGlobalRef(gByteArrayInputStream_Clazz);
+    gCountField = getFieldIDCheck(env, gByteArrayInputStream_Clazz, "count", "I");
+    gPosField = getFieldIDCheck(env, gByteArrayInputStream_Clazz, "pos", "I");
+
+    gAssetInputStream_Clazz = findClassCheck(env, "android/content/res/AssetManager$AssetInputStream");
+    // Ref gAssetInputStream_Clazz so we can continue to refer to it when
+    // calling IsInstance.
+    gAssetInputStream_Clazz = (jclass) env->NewGlobalRef(gAssetInputStream_Clazz);
+    gGetAssetIntMethodID = getMethodIDCheck(env, gAssetInputStream_Clazz, "getAssetInt", "()I");
+
+    jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream");
+    gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V");
+    gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V");
+
+    return 0;
+}
diff --git a/core/res/res/drawable-nodpi/view_accessibility_focused.9.png b/core/res/res/drawable-nodpi/view_accessibility_focused.9.png
deleted file mode 100644
index f03f575..0000000
--- a/core/res/res/drawable-nodpi/view_accessibility_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/view_accessibility_focused.xml b/core/res/res/drawable/view_accessibility_focused.xml
new file mode 100644
index 0000000..0da9a81
--- /dev/null
+++ b/core/res/res/drawable/view_accessibility_focused.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <stroke
+        android:width="2dp"
+        android:color="@color/accessibility_focus_highlight" />
+
+    <corners android:radius="2dp"/>
+
+</shape>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 604bf4b..284f613 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -195,5 +195,7 @@
     <color name="keyguard_avatar_frame_shadow_color">#80000000</color>
     <color name="keyguard_avatar_nick_color">#ffffffff</color>
     <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color>
+
+    <color name="accessibility_focus_highlight">#80ffff00</color>
 </resources>
 
diff --git a/docs/html/training/id-auth/authenticate.jd b/docs/html/training/id-auth/authenticate.jd
index 3084bea..65dbc39 100644
--- a/docs/html/training/id-auth/authenticate.jd
+++ b/docs/html/training/id-auth/authenticate.jd
@@ -79,7 +79,7 @@
 
 <p>To get an auth token you first need to request the
 {@link android.Manifest.permission#ACCOUNT_MANAGER}
-to yourmanifest file. To actually do anything useful with the
+to your manifest file. To actually do anything useful with the
 token, you'll also need to add the {@link android.Manifest.permission#INTERNET}
 permission.</p>
 
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 4193dbb..e3adc59 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -56,22 +56,6 @@
          * mutable even when decoding a resource which would normally result in
          * an immutable bitmap.</p>
          *
-         * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any
-         * mutable bitmap can be reused to decode any other bitmaps as long as
-         * the resulting {@link Bitmap#getByteCount() byte count} of the decoded
-         * bitmap is less than or equal to the {@link
-         * Bitmap#getAllocationByteCount() allocated byte count} of the reused
-         * bitmap. This can be because the intrinsic size is smaller, or its
-         * size post scaling (for density / sample size) is smaller.</p>
-         *
-         * <p>Prior to {@link android.os.Build.VERSION_CODES#KITKAT}
-         * additional constraints apply: The image being decoded (whether as a
-         * resource or as a stream) must be in jpeg or png format. Only equal
-         * sized bitmaps are supported, with {@link #inSampleSize} set to 1.
-         * Additionally, the {@link android.graphics.Bitmap.Config
-         * configuration} of the reused bitmap will override the setting of
-         * {@link #inPreferredConfig}, if set.</p>
-         *
          * <p>You should still always use the returned Bitmap of the decode
          * method and not assume that reusing the bitmap worked, due to the
          * constraints outlined above and failure situations that can occur.
@@ -81,6 +65,36 @@
          * function to ensure that you are using the bitmap that was used as the
          * decode destination.</p>
          *
+         * <h3>Usage with BitmapFactory</h3>
+         *
+         * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any
+         * mutable bitmap can be reused by {@link BitmapFactory} to decode any
+         * other bitmaps as long as the resulting {@link Bitmap#getByteCount()
+         * byte count} of the decoded bitmap is less than or equal to the {@link
+         * Bitmap#getAllocationByteCount() allocated byte count} of the reused
+         * bitmap. This can be because the intrinsic size is smaller, or its
+         * size post scaling (for density / sample size) is smaller.</p>
+         *
+         * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}
+         * additional constraints apply: The image being decoded (whether as a
+         * resource or as a stream) must be in jpeg or png format. Only equal
+         * sized bitmaps are supported, with {@link #inSampleSize} set to 1.
+         * Additionally, the {@link android.graphics.Bitmap.Config
+         * configuration} of the reused bitmap will override the setting of
+         * {@link #inPreferredConfig}, if set.</p>
+         *
+         * <h3>Usage with BitmapRegionDecoder</h3>
+         *
+         * <p>BitmapRegionDecoder will draw its requested content into the Bitmap
+         * provided, clipping if the output content size (post scaling) is larger
+         * than the provided Bitmap. The provided Bitmap's width, height, and
+         * {@link Bitmap.Config} will not be changed.
+         *
+         * <p class="note">BitmapRegionDecoder support for {@link #inBitmap} was
+         * introduced in {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. All
+         * formats supported by BitmapRegionDecoder support Bitmap reuse via
+         * {@link #inBitmap}.</p>
+         *
          * @see Bitmap#reconfigure(int,int, android.graphics.Bitmap.Config)
          */
         public Bitmap inBitmap;
@@ -229,6 +243,9 @@
          * rather than relying on the graphics system scaling it each time it
          * is drawn to a Canvas.
          *
+         * <p>BitmapRegionDecoder ignores this flag, and will not scale output
+         * based on density. (though {@link #inSampleSize} is supported)</p>
+         *
          * <p>This flag is turned on by default and should be turned off if you need
          * a non-scaled version of the bitmap.  Nine-patch bitmaps ignore this
          * flag and are always scaled.
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index 2cf1b2d..e83c5ba 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -20,8 +20,7 @@
 import java.util.Vector;
 
 import android.content.Context;
-import android.media.MediaPlayer.OnSubtitleDataListener;
-import android.view.View;
+import android.media.SubtitleTrack.RenderingWidget;
 import android.view.accessibility.CaptioningManager;
 
 /**
@@ -32,7 +31,6 @@
  * @hide
  */
 public class SubtitleController {
-    private Context mContext;
     private MediaTimeProvider mTimeProvider;
     private Vector<Renderer> mRenderers;
     private Vector<SubtitleTrack> mTracks;
@@ -50,7 +48,6 @@
             Context context,
             MediaTimeProvider timeProvider,
             Listener listener) {
-        mContext = context;
         mTimeProvider = timeProvider;
         mListener = listener;
 
@@ -79,11 +76,11 @@
         return mSelectedTrack;
     }
 
-    private View getSubtitleView() {
+    private RenderingWidget getRenderingWidget() {
         if (mSelectedTrack == null) {
             return null;
         }
-        return mSelectedTrack.getView();
+        return mSelectedTrack.getRenderingWidget();
     }
 
     /**
@@ -110,7 +107,7 @@
         }
 
         mSelectedTrack = track;
-        mAnchor.setSubtitleView(getSubtitleView());
+        mAnchor.setSubtitleWidget(getRenderingWidget());
 
         if (mSelectedTrack != null) {
             mSelectedTrack.setTimeProvider(mTimeProvider);
@@ -268,17 +265,16 @@
     }
 
     /**
-     * Subtitle anchor, an object that is able to display a subtitle view,
+     * Subtitle anchor, an object that is able to display a subtitle renderer,
      * e.g. a VideoView.
      */
     public interface Anchor {
         /**
-         * Anchor should set the subtitle view to the supplied view,
-         * or none, if the supplied view is null.
-         *
-         * @param view subtitle view, or null
+         * Anchor should use the supplied subtitle rendering widget, or
+         * none if it is null.
+         * @hide
          */
-        public void setSubtitleView(View view);
+        public void setSubtitleWidget(RenderingWidget subtitleWidget);
     }
 
     private Anchor mAnchor;
@@ -290,11 +286,11 @@
         }
 
         if (mAnchor != null) {
-            mAnchor.setSubtitleView(null);
+            mAnchor.setSubtitleWidget(null);
         }
         mAnchor = anchor;
         if (mAnchor != null) {
-            mAnchor.setSubtitleView(getSubtitleView());
+            mAnchor.setSubtitleWidget(getRenderingWidget());
         }
     }
 
diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java
index 09fb3f2..cb689af 100644
--- a/media/java/android/media/SubtitleTrack.java
+++ b/media/java/android/media/SubtitleTrack.java
@@ -16,11 +16,11 @@
 
 package android.media;
 
+import android.graphics.Canvas;
 import android.os.Handler;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
-import android.view.View;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -98,16 +98,16 @@
     public abstract void onData(String data, boolean eos, long runID);
 
     /**
-     * Called when adding the subtitle rendering view to the view hierarchy, as
-     * well as when showing or hiding the subtitle track, or when the video
+     * Called when adding the subtitle rendering widget to the view hierarchy,
+     * as well as when showing or hiding the subtitle track, or when the video
      * surface position has changed.
      *
-     * @return the view object that displays this subtitle track.  For most
-     * renderers there should be a single shared view instance that is used
-     * for all tracks supported by that renderer, as at most one subtitle
-     * track is visible at one time.
+     * @return the widget that renders this subtitle track. For most renderers
+     *         there should be a single shared instance that is used for all
+     *         tracks supported by that renderer, as at most one subtitle track
+     *         is visible at one time.
      */
-    public abstract View getView();
+    public abstract RenderingWidget getRenderingWidget();
 
     /**
      * Called when the active cues have changed, and the contents of the subtitle
@@ -268,7 +268,7 @@
         }
 
         mVisible = true;
-        getView().setVisibility(View.VISIBLE);
+        getRenderingWidget().setVisible(true);
         if (mTimeProvider != null) {
             mTimeProvider.scheduleUpdate(this);
         }
@@ -283,7 +283,7 @@
         if (mTimeProvider != null) {
             mTimeProvider.cancelNotifications(this);
         }
-        getView().setVisibility(View.INVISIBLE);
+        getRenderingWidget().setVisible(false);
         mVisible = false;
     }
 
@@ -645,4 +645,61 @@
             }
         }
     }
+
+    /**
+     * Interface for rendering subtitles onto a Canvas.
+     */
+    public interface RenderingWidget {
+        /**
+         * Sets the widget's callback, which is used to send updates when the
+         * rendered data has changed.
+         *
+         * @param callback update callback
+         */
+        public void setOnChangedListener(OnChangedListener callback);
+
+        /**
+         * Sets the widget's size.
+         *
+         * @param width width in pixels
+         * @param height height in pixels
+         */
+        public void setSize(int width, int height);
+
+        /**
+         * Sets whether the widget should draw subtitles.
+         *
+         * @param visible true if subtitles should be drawn, false otherwise
+         */
+        public void setVisible(boolean visible);
+
+        /**
+         * Renders subtitles onto a {@link Canvas}.
+         *
+         * @param c canvas on which to render subtitles
+         */
+        public void draw(Canvas c);
+
+        /**
+         * Called when the widget is attached to a window.
+         */
+        public void onAttachedToWindow();
+
+        /**
+         * Called when the widget is detached from a window.
+         */
+        public void onDetachedFromWindow();
+
+        /**
+         * Callback used to send updates about changes to rendering data.
+         */
+        public interface OnChangedListener {
+            /**
+             * Called when the rendering data has changed.
+             *
+             * @param renderingWidget the widget whose data has changed
+             */
+            public void onChanged(RenderingWidget renderingWidget);
+        }
+    }
 }
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 527c57f..74773a8 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -1,12 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.media;
 
 import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.TextView;
+import android.view.ViewGroup;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
 
+import com.android.internal.widget.SubtitleView;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -14,10 +39,12 @@
 
 /** @hide */
 public class WebVttRenderer extends SubtitleController.Renderer {
-    private TextView mMyTextView;
+    private final Context mContext;
 
-    public WebVttRenderer(Context context, AttributeSet attrs) {
-        mMyTextView = new WebVttView(context, attrs);
+    private WebVttRenderingWidget mRenderingWidget;
+
+    public WebVttRenderer(Context context) {
+        mContext = context;
     }
 
     @Override
@@ -30,19 +57,11 @@
 
     @Override
     public SubtitleTrack createTrack(MediaFormat format) {
-        return new WebVttTrack(format, mMyTextView);
-    }
-}
+        if (mRenderingWidget == null) {
+            mRenderingWidget = new WebVttRenderingWidget(mContext);
+        }
 
-/** @hide */
-class WebVttView extends TextView {
-    public WebVttView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setTextColor(0xffffff00);
-        setTextSize(46);
-        setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
-        setLayoutParams(new LayoutParams(
-                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        return new WebVttTrack(mRenderingWidget, format);
     }
 }
 
@@ -954,26 +973,26 @@
 class WebVttTrack extends SubtitleTrack implements WebVttCueListener {
     private static final String TAG = "WebVttTrack";
 
-    private final TextView mTextView;
-
     private final WebVttParser mParser = new WebVttParser(this);
     private final UnstyledTextExtractor mExtractor =
         new UnstyledTextExtractor();
     private final Tokenizer mTokenizer = new Tokenizer(mExtractor);
     private final Vector<Long> mTimestamps = new Vector<Long>();
+    private final WebVttRenderingWidget mRenderingWidget;
 
     private final Map<String, TextTrackRegion> mRegions =
         new HashMap<String, TextTrackRegion>();
     private Long mCurrentRunID;
 
-    WebVttTrack(MediaFormat format, TextView textView) {
+    WebVttTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) {
         super(format);
-        mTextView = textView;
+
+        mRenderingWidget = renderingWidget;
     }
 
     @Override
-    public View getView() {
-        return mTextView;
+    public WebVttRenderingWidget getRenderingWidget() {
+        return mRenderingWidget;
     }
 
     @Override
@@ -1051,6 +1070,7 @@
         }
     }
 
+    @Override
     public void updateView(Vector<SubtitleTrack.Cue> activeCues) {
         if (!mVisible) {
             // don't keep the state if we are not visible
@@ -1066,29 +1086,737 @@
                 Log.d(TAG, "at (illegal state) the active cues are:");
             }
         }
-        StringBuilder text = new StringBuilder();
-        StringBuilder lineBuilder = new StringBuilder();
-        for (Cue o: activeCues) {
-            TextTrackCue cue = (TextTrackCue)o;
-            if (DEBUG) Log.d(TAG, cue.toString());
-            for (TextTrackCueSpan[] line: cue.mLines) {
-                for (TextTrackCueSpan span: line) {
-                    if (!span.mEnabled) {
-                        continue;
-                    }
-                    lineBuilder.append(span.mText);
+
+        mRenderingWidget.setActiveCues(activeCues);
+    }
+}
+
+/**
+ * Widget capable of rendering WebVTT captions.
+ *
+ * @hide
+ */
+class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
+    private static final boolean DEBUG = false;
+    private static final int DEBUG_REGION_BACKGROUND = 0x800000FF;
+    private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000;
+
+    /** WebVtt specifies line height as 5.3% of the viewport height. */
+    private static final float LINE_HEIGHT_RATIO = 0.0533f;
+
+    /** Map of active regions, used to determine enter/exit. */
+    private final ArrayMap<TextTrackRegion, RegionLayout> mRegionBoxes =
+            new ArrayMap<TextTrackRegion, RegionLayout>();
+
+    /** Map of active cues, used to determine enter/exit. */
+    private final ArrayMap<TextTrackCue, CueLayout> mCueBoxes =
+            new ArrayMap<TextTrackCue, CueLayout>();
+
+    /** Captioning manager, used to obtain and track caption properties. */
+    private final CaptioningManager mManager;
+
+    /** Callback for rendering changes. */
+    private OnChangedListener mListener;
+
+    /** Current caption style. */
+    private CaptionStyle mCaptionStyle;
+
+    /** Current font size, computed from font scaling factor and height. */
+    private float mFontSize;
+
+    /** Whether a caption style change listener is registered. */
+    private boolean mHasChangeListener;
+
+    public WebVttRenderingWidget(Context context) {
+        this(context, null);
+    }
+
+    public WebVttRenderingWidget(Context context, AttributeSet attrs) {
+        this(context, null, 0);
+    }
+
+    public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        // Cannot render text over video when layer type is hardware.
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+        mCaptionStyle = mManager.getUserStyle();
+        mFontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+        measure(widthSpec, heightSpec);
+        layout(0, 0, width, height);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void setOnChangedListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.GONE);
+        }
+
+        manageChangeListener();
+    }
+
+    /**
+     * Manages whether this renderer is listening for caption style changes.
+     */
+    private void manageChangeListener() {
+        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
+        if (mHasChangeListener != needsListener) {
+            mHasChangeListener = needsListener;
+
+            if (needsListener) {
+                mManager.addCaptioningChangeListener(mCaptioningListener);
+
+                final CaptionStyle captionStyle = mManager.getUserStyle();
+                final float fontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
+                setCaptionStyle(captionStyle, fontSize);
+            } else {
+                mManager.removeCaptioningChangeListener(mCaptioningListener);
+            }
+        }
+    }
+
+    public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) {
+        final Context context = getContext();
+        final CaptionStyle captionStyle = mCaptionStyle;
+        final float fontSize = mFontSize;
+
+        prepForPrune();
+
+        // Ensure we have all necessary cue and region boxes.
+        final int count = activeCues.size();
+        for (int i = 0; i < count; i++) {
+            final TextTrackCue cue = (TextTrackCue) activeCues.get(i);
+            final TextTrackRegion region = cue.mRegion;
+            if (region != null) {
+                RegionLayout regionBox = mRegionBoxes.get(region);
+                if (regionBox == null) {
+                    regionBox = new RegionLayout(context, region, captionStyle, fontSize);
+                    mRegionBoxes.put(region, regionBox);
+                    addView(regionBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
                 }
-                if (lineBuilder.length() > 0) {
-                    text.append(lineBuilder.toString()).append("\n");
-                    lineBuilder.delete(0, lineBuilder.length());
+                regionBox.put(cue);
+            } else {
+                CueLayout cueBox = mCueBoxes.get(cue);
+                if (cueBox == null) {
+                    cueBox = new CueLayout(context, cue, captionStyle, fontSize);
+                    mCueBoxes.put(cue, cueBox);
+                    addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+                }
+                cueBox.update();
+                cueBox.setOrder(i);
+            }
+        }
+
+        prune();
+
+        // Force measurement and layout.
+        final int width = getWidth();
+        final int height = getHeight();
+        setSize(width, height);
+
+        if (mListener != null) {
+            mListener.onChanged(this);
+        }
+    }
+
+    private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+        mCaptionStyle = captionStyle;
+        mFontSize = fontSize;
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.setCaptionStyle(captionStyle, fontSize);
+        }
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.setCaptionStyle(captionStyle, fontSize);
+        }
+    }
+
+    /**
+     * Remove inactive cues and regions.
+     */
+    private void prune() {
+        int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            if (regionBox.prune()) {
+                removeView(regionBox);
+                mRegionBoxes.removeAt(i);
+                regionCount--;
+                i--;
+            }
+        }
+
+        int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            if (!cueBox.isActive()) {
+                removeView(cueBox);
+                mCueBoxes.removeAt(i);
+                cueCount--;
+                i--;
+            }
+        }
+    }
+
+    /**
+     * Reset active cues and regions.
+     */
+    private void prepForPrune() {
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.prepForPrune();
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.prepForPrune();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int viewportWidth = r - l;
+        final int viewportHeight = b - t;
+
+        setCaptionStyle(mCaptionStyle,
+                mManager.getFontScale() * LINE_HEIGHT_RATIO * viewportHeight);
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            layoutRegion(viewportWidth, viewportHeight, regionBox);
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            layoutCue(viewportWidth, viewportHeight, cueBox);
+        }
+    }
+
+    /**
+     * Lays out a region within the viewport. The region handles layout for
+     * contained cues.
+     */
+    private void layoutRegion(
+            int viewportWidth, int viewportHeight,
+            RegionLayout regionBox) {
+        final TextTrackRegion region = regionBox.getRegion();
+        final int regionHeight = regionBox.getMeasuredHeight();
+        final int regionWidth = regionBox.getMeasuredWidth();
+
+        // TODO: Account for region anchor point.
+        final float x = region.mViewportAnchorPointX;
+        final float y = region.mViewportAnchorPointY;
+        final int left = (int) (x * (viewportWidth - regionWidth) / 100);
+        final int top = (int) (y * (viewportHeight - regionHeight) / 100);
+
+        regionBox.layout(left, top, left + regionWidth, top + regionHeight);
+    }
+
+    /**
+     * Lays out a cue within the viewport.
+     */
+    private void layoutCue(
+            int viewportWidth, int viewportHeight, CueLayout cueBox) {
+        final TextTrackCue cue = cueBox.getCue();
+        final int direction = getLayoutDirection();
+        final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
+        final boolean cueSnapToLines = cue.mSnapToLines;
+
+        int size = 100 * cueBox.getMeasuredWidth() / viewportWidth;
+
+        // Determine raw x-position.
+        int xPosition;
+        switch (absAlignment) {
+            case TextTrackCue.ALIGNMENT_LEFT:
+                xPosition = cue.mTextPosition;
+                break;
+            case TextTrackCue.ALIGNMENT_RIGHT:
+                xPosition = cue.mTextPosition - size;
+                break;
+            case TextTrackCue.ALIGNMENT_MIDDLE:
+            default:
+                xPosition = cue.mTextPosition - size / 2;
+                break;
+        }
+
+        // Adjust x-position for layout.
+        if (direction == LAYOUT_DIRECTION_RTL) {
+            xPosition = 100 - xPosition;
+        }
+
+        // If the text track cue snap-to-lines flag is set, adjust
+        // x-position and size for padding. This is equivalent to placing the
+        // cue within the title-safe area.
+        if (cueSnapToLines) {
+            final int paddingLeft = 100 * getPaddingLeft() / viewportWidth;
+            final int paddingRight = 100 * getPaddingRight() / viewportWidth;
+            if (xPosition < paddingLeft && xPosition + size > paddingLeft) {
+                xPosition += paddingLeft;
+                size -= paddingLeft;
+            }
+            final float rightEdge = 100 - paddingRight;
+            if (xPosition < rightEdge && xPosition + size > rightEdge) {
+                size -= paddingRight;
+            }
+        }
+
+        // Compute absolute left position and width.
+        final int left = xPosition * viewportWidth / 100;
+        final int width = size * viewportWidth / 100;
+
+        // Determine initial y-position.
+        final int yPosition = calculateLinePosition(cueBox);
+
+        // Compute absolute final top position and height.
+        final int height = cueBox.getMeasuredHeight();
+        final int top;
+        if (yPosition < 0) {
+            // TODO: This needs to use the actual height of prior boxes.
+            top = viewportHeight + yPosition * height;
+        } else {
+            top = yPosition * (viewportHeight - height) / 100;
+        }
+
+        // Layout cue in final position.
+        cueBox.layout(left, top, left + width, top + height);
+    }
+
+    /**
+     * Calculates the line position for a cue.
+     * <p>
+     * If the resulting position is negative, it represents a bottom-aligned
+     * position relative to the number of active cues. Otherwise, it represents
+     * a percentage [0-100] of the viewport height.
+     */
+    private int calculateLinePosition(CueLayout cueBox) {
+        final TextTrackCue cue = cueBox.getCue();
+        final Integer linePosition = cue.mLinePosition;
+        final boolean snapToLines = cue.mSnapToLines;
+        final boolean autoPosition = (linePosition == null);
+
+        if (!snapToLines && !autoPosition && (linePosition < 0 || linePosition > 100)) {
+            // Invalid line position defaults to 100.
+            return 100;
+        } else if (!autoPosition) {
+            // Use the valid, supplied line position.
+            return linePosition;
+        } else if (!snapToLines) {
+            // Automatic, non-snapped line position defaults to 100.
+            return 100;
+        } else {
+            // Automatic snapped line position uses active cue order.
+            return -(cueBox.mOrder + 1);
+        }
+    }
+
+    /**
+     * Resolves cue alignment according to the specified layout direction.
+     */
+    private static int resolveCueAlignment(int layoutDirection, int alignment) {
+        switch (alignment) {
+            case TextTrackCue.ALIGNMENT_START:
+                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
+                        TextTrackCue.ALIGNMENT_LEFT : TextTrackCue.ALIGNMENT_RIGHT;
+            case TextTrackCue.ALIGNMENT_END:
+                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
+                        TextTrackCue.ALIGNMENT_RIGHT : TextTrackCue.ALIGNMENT_LEFT;
+        }
+        return alignment;
+    }
+
+    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
+        @Override
+        public void onFontScaleChanged(float fontScale) {
+            final float fontSize = fontScale * getHeight() * LINE_HEIGHT_RATIO;
+            setCaptionStyle(mCaptionStyle, fontSize);
+        }
+
+        @Override
+        public void onUserStyleChanged(CaptionStyle userStyle) {
+            setCaptionStyle(userStyle, mFontSize);
+        }
+    };
+
+    /**
+     * A text track region represents a portion of the video viewport and
+     * provides a rendering area for text track cues.
+     */
+    private static class RegionLayout extends LinearLayout {
+        private final ArrayList<CueLayout> mRegionCueBoxes = new ArrayList<CueLayout>();
+        private final TextTrackRegion mRegion;
+
+        private CaptionStyle mCaptionStyle;
+        private float mFontSize;
+
+        public RegionLayout(Context context, TextTrackRegion region, CaptionStyle captionStyle,
+                float fontSize) {
+            super(context);
+
+            mRegion = region;
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            // TODO: Add support for vertical text
+            setOrientation(VERTICAL);
+
+            if (DEBUG) {
+                setBackgroundColor(DEBUG_REGION_BACKGROUND);
+            }
+        }
+
+        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                cueBox.setCaptionStyle(captionStyle, fontSize);
+            }
+        }
+
+        /**
+         * Performs the parent's measurement responsibilities, then
+         * automatically performs its own measurement.
+         */
+        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
+            final TextTrackRegion region = mRegion;
+            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
+            final int width = (int) region.mWidth;
+
+            // Determine the absolute maximum region size as the requested size.
+            final int size = width * specWidth / 100;
+
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
+            measure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Prepares this region for pruning by setting all tracks as inactive.
+         * <p>
+         * Tracks that are added or updated using {@link #put(TextTrackCue)}
+         * after this calling this method will be marked as active.
+         */
+        public void prepForPrune() {
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                cueBox.prepForPrune();
+            }
+        }
+
+        /**
+         * Adds a {@link TextTrackCue} to this region. If the track had already
+         * been added, updates its active state.
+         *
+         * @param cue
+         */
+        public void put(TextTrackCue cue) {
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                if (cueBox.getCue() == cue) {
+                    cueBox.update();
+                    return;
+                }
+            }
+
+            final CueLayout cueBox = new CueLayout(getContext(), cue, mCaptionStyle, mFontSize);
+            addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+
+            if (getChildCount() > mRegion.mLines) {
+                removeViewAt(0);
+            }
+        }
+
+        /**
+         * Remove all inactive tracks from this region.
+         *
+         * @return true if this region is empty and should be pruned
+         */
+        public boolean prune() {
+            int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                if (!cueBox.isActive()) {
+                    mRegionCueBoxes.remove(i);
+                    removeView(cueBox);
+                    cueCount--;
+                    i--;
+                }
+            }
+
+            return mRegionCueBoxes.isEmpty();
+        }
+
+        /**
+         * @return the region data backing this layout
+         */
+        public TextTrackRegion getRegion() {
+            return mRegion;
+        }
+    }
+
+    /**
+     * A text track cue is the unit of time-sensitive data in a text track,
+     * corresponding for instance for subtitles and captions to the text that
+     * appears at a particular time and disappears at another time.
+     * <p>
+     * A single cue may contain multiple {@link SpanLayout}s, each representing a
+     * single line of text.
+     */
+    private static class CueLayout extends LinearLayout {
+        public final TextTrackCue mCue;
+
+        private CaptionStyle mCaptionStyle;
+        private float mFontSize;
+
+        private boolean mActive;
+        private int mOrder;
+
+        public CueLayout(
+                Context context, TextTrackCue cue, CaptionStyle captionStyle, float fontSize) {
+            super(context);
+
+            mCue = cue;
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            // TODO: Add support for vertical text.
+            final boolean horizontal = cue.mWritingDirection
+                    == TextTrackCue.WRITING_DIRECTION_HORIZONTAL;
+            setOrientation(horizontal ? VERTICAL : HORIZONTAL);
+
+            switch (cue.mAlignment) {
+                case TextTrackCue.ALIGNMENT_END:
+                    setGravity(Gravity.END);
+                    break;
+                case TextTrackCue.ALIGNMENT_LEFT:
+                    setGravity(Gravity.LEFT);
+                    break;
+                case TextTrackCue.ALIGNMENT_MIDDLE:
+                    setGravity(horizontal
+                            ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
+                    break;
+                case TextTrackCue.ALIGNMENT_RIGHT:
+                    setGravity(Gravity.RIGHT);
+                    break;
+                case TextTrackCue.ALIGNMENT_START:
+                    setGravity(Gravity.START);
+                    break;
+            }
+
+            if (DEBUG) {
+                setBackgroundColor(DEBUG_CUE_BACKGROUND);
+            }
+
+            update();
+        }
+
+        public void setCaptionStyle(CaptionStyle style, float fontSize) {
+            mCaptionStyle = style;
+            mFontSize = fontSize;
+
+            final int n = getChildCount();
+            for (int i = 0; i < n; i++) {
+                final View child = getChildAt(i);
+                if (child instanceof SpanLayout) {
+                    ((SpanLayout) child).setCaptionStyle(style, fontSize);
                 }
             }
         }
 
-        if (mTextView != null) {
-            if (DEBUG) Log.d(TAG, "updating to " + text.toString());
-            mTextView.setText(text.toString());
-            mTextView.postInvalidate();
+        public void prepForPrune() {
+            mActive = false;
+        }
+
+        public void update() {
+            mActive = true;
+
+            removeAllViews();
+
+            final CaptionStyle captionStyle = mCaptionStyle;
+            final float fontSize = mFontSize;
+            final TextTrackCueSpan[][] lines = mCue.mLines;
+            final int lineCount = lines.length;
+            for (int i = 0; i < lineCount; i++) {
+                final SpanLayout lineBox = new SpanLayout(getContext(), lines[i]);
+                lineBox.setCaptionStyle(captionStyle, fontSize);
+
+                addView(lineBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Performs the parent's measurement responsibilities, then
+         * automatically performs its own measurement.
+         */
+        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
+            final TextTrackCue cue = mCue;
+            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
+            final int direction = getLayoutDirection();
+            final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
+
+            // Determine the maximum size of cue based on its starting position
+            // and the direction in which it grows.
+            final int maximumSize;
+            switch (absAlignment) {
+                case TextTrackCue.ALIGNMENT_LEFT:
+                    maximumSize = 100 - cue.mTextPosition;
+                    break;
+                case TextTrackCue.ALIGNMENT_RIGHT:
+                    maximumSize = cue.mTextPosition;
+                    break;
+                case TextTrackCue.ALIGNMENT_MIDDLE:
+                    if (cue.mTextPosition <= 50) {
+                        maximumSize = cue.mTextPosition * 2;
+                    } else {
+                        maximumSize = (100 - cue.mTextPosition) * 2;
+                    }
+                    break;
+                default:
+                    maximumSize = 0;
+            }
+
+            // Determine absolute maximum cue size as the smaller of the
+            // requested size and the maximum theoretical size.
+            final int size = Math.min(cue.mSize, maximumSize) * specWidth / 100;
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
+            measure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Sets the order of this cue in the list of active cues.
+         *
+         * @param order the order of this cue in the list of active cues
+         */
+        public void setOrder(int order) {
+            mOrder = order;
+        }
+
+        /**
+         * @return whether this cue is marked as active
+         */
+        public boolean isActive() {
+            return mActive;
+        }
+
+        /**
+         * @return the cue data backing this layout
+         */
+        public TextTrackCue getCue() {
+            return mCue;
+        }
+    }
+
+    /**
+     * A text track line represents a single line of text within a cue.
+     * <p>
+     * A single line may contain multiple spans, each representing a section of
+     * text that may be enabled or disabled at a particular time.
+     */
+    private static class SpanLayout extends SubtitleView {
+        private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
+        private final TextTrackCueSpan[] mSpans;
+
+        public SpanLayout(Context context, TextTrackCueSpan[] spans) {
+            super(context);
+
+            mSpans = spans;
+
+            update();
+        }
+
+        public void update() {
+            final SpannableStringBuilder builder = mBuilder;
+            final TextTrackCueSpan[] spans = mSpans;
+
+            builder.clear();
+            builder.clearSpans();
+
+            final int spanCount = spans.length;
+            for (int i = 0; i < spanCount; i++) {
+                final TextTrackCueSpan span = spans[i];
+                if (span.mEnabled) {
+                    builder.append(spans[i].mText);
+                }
+            }
+
+            setText(builder);
+        }
+
+        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+            setBackgroundColor(captionStyle.backgroundColor);
+            setForegroundColor(captionStyle.foregroundColor);
+            setEdgeColor(captionStyle.edgeColor);
+            setEdgeType(captionStyle.edgeType);
+            setTypeface(captionStyle.getTypeface());
+            setTextSize(fontSize);
         }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index e0b8d19..d8e60aa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -70,7 +70,7 @@
 
                 try {
                     final Uri childUri = DocumentsContract.createDocument(
-                            resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
+                            resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);
 
                     // Navigate into newly created child
                     final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index e594437..a13beba 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -20,6 +20,8 @@
 import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
 import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
 import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
 import static com.android.documentsui.model.DocumentInfo.getCursorInt;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -91,43 +93,42 @@
 
     private int mType = TYPE_NORMAL;
 
+    private int mLastMode = MODE_UNKNOWN;
+    private int mLastSortOrder = SORT_ORDER_UNKNOWN;
+
     private Point mThumbSize;
 
     private DocumentsAdapter mAdapter;
     private LoaderCallbacks<DirectoryResult> mCallbacks;
 
     private static final String EXTRA_TYPE = "type";
-    private static final String EXTRA_AUTHORITY = "authority";
-    private static final String EXTRA_ROOT_ID = "rootId";
-    private static final String EXTRA_DOC_ID = "docId";
+    private static final String EXTRA_ROOT = "root";
+    private static final String EXTRA_DOC = "doc";
     private static final String EXTRA_QUERY = "query";
 
     private static AtomicInteger sLoaderId = new AtomicInteger(4000);
 
-    private int mLastSortOrder = -1;
-
     private final int mLoaderId = sLoaderId.incrementAndGet();
 
-    public static void showNormal(FragmentManager fm, Uri uri) {
-        show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
+    public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) {
+        show(fm, TYPE_NORMAL, root, doc, null);
     }
 
-    public static void showSearch(FragmentManager fm, Uri uri, String query) {
-        show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
-                query);
+    public static void showSearch(
+            FragmentManager fm, RootInfo root, DocumentInfo doc, String query) {
+        show(fm, TYPE_SEARCH, root, doc, query);
     }
 
     public static void showRecentsOpen(FragmentManager fm) {
-        show(fm, TYPE_RECENT_OPEN, null, null, null, null);
+        show(fm, TYPE_RECENT_OPEN, null, null, null);
     }
 
-    private static void show(FragmentManager fm, int type, String authority, String rootId,
-            String docId, String query) {
+    private static void show(
+            FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) {
         final Bundle args = new Bundle();
         args.putInt(EXTRA_TYPE, type);
-        args.putString(EXTRA_AUTHORITY, authority);
-        args.putString(EXTRA_ROOT_ID, rootId);
-        args.putString(EXTRA_DOC_ID, docId);
+        args.putParcelable(EXTRA_ROOT, root);
+        args.putParcelable(EXTRA_DOC, doc);
         args.putString(EXTRA_QUERY, query);
 
         final DirectoryFragment fragment = new DirectoryFragment();
@@ -167,6 +168,7 @@
         super.onActivityCreated(savedInstanceState);
 
         final Context context = getActivity();
+        final State state = getDisplayState(DirectoryFragment.this);
 
         mAdapter = new DocumentsAdapter();
         mType = getArguments().getInt(EXTRA_TYPE);
@@ -174,35 +176,48 @@
         mCallbacks = new LoaderCallbacks<DirectoryResult>() {
             @Override
             public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
-                final State state = getDisplayState(DirectoryFragment.this);
-
-                final String authority = getArguments().getString(EXTRA_AUTHORITY);
-                final String rootId = getArguments().getString(EXTRA_ROOT_ID);
-                final String docId = getArguments().getString(EXTRA_DOC_ID);
+                final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
+                final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
                 final String query = getArguments().getString(EXTRA_QUERY);
 
                 Uri contentsUri;
                 switch (mType) {
                     case TYPE_NORMAL:
-                        contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
-                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+                        contentsUri = DocumentsContract.buildChildDocumentsUri(
+                                doc.authority, doc.documentId);
+                        return new DirectoryLoader(context, root, doc, contentsUri);
                     case TYPE_SEARCH:
                         contentsUri = DocumentsContract.buildSearchDocumentsUri(
-                                authority, docId, query);
-                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+                                doc.authority, doc.documentId, query);
+                        return new DirectoryLoader(context, root, doc, contentsUri);
                     case TYPE_RECENT_OPEN:
                         final RootsCache roots = DocumentsApplication.getRootsCache(context);
                         final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
-                        return new RecentLoader(context, matchingRoots);
+                        return new RecentLoader(context, matchingRoots, state.acceptMimes);
                     default:
                         throw new IllegalStateException("Unknown type " + mType);
-
                 }
             }
 
             @Override
             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
+                if (!isAdded()) return;
+
                 mAdapter.swapCursor(result.cursor);
+
+                // Push latest state up to UI
+                // TODO: if mode change was racing with us, don't overwrite it
+                state.mode = result.mode;
+                state.sortOrder = result.sortOrder;
+                ((DocumentsActivity) context).onStateChanged();
+
+                updateDisplayState();
+
+                if (mLastSortOrder != result.sortOrder) {
+                    mLastSortOrder = result.sortOrder;
+                    mListView.smoothScrollToPosition(0);
+                    mGridView.smoothScrollToPosition(0);
+                }
             }
 
             @Override
@@ -211,6 +226,9 @@
             }
         };
 
+        // Kick off loader at least once
+        getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
+
         updateDisplayState();
     }
 
@@ -220,22 +238,27 @@
         updateDisplayState();
     }
 
-    public void updateDisplayState() {
+    public void onUserSortOrderChanged() {
+        // User change always triggers reload
+        getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
+    }
+
+    public void onUserModeChanged() {
+        // Mode change is just display; no need to reload
+        updateDisplayState();
+    }
+
+    private void updateDisplayState() {
         final State state = getDisplayState(this);
 
-        if (mLastSortOrder != state.sortOrder) {
-            getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
-            mLastSortOrder = state.sortOrder;
-        }
+        mFilter = new MimePredicate(state.acceptMimes);
 
-        mListView.smoothScrollToPosition(0);
-        mGridView.smoothScrollToPosition(0);
+        if (mLastMode == state.mode) return;
+        mLastMode = state.mode;
 
         mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
         mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
 
-        mFilter = new MimePredicate(state.acceptMimes);
-
         final int choiceMode;
         if (state.allowMultiple) {
             choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
@@ -254,14 +277,14 @@
             mGridView.setChoiceMode(choiceMode);
             mCurrentView = mGridView;
         } else if (state.mode == MODE_LIST) {
-            thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+            thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
             mGridView.setAdapter(null);
             mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
             mListView.setAdapter(mAdapter);
             mListView.setChoiceMode(choiceMode);
             mCurrentView = mListView;
         } else {
-            throw new IllegalStateException();
+            throw new IllegalStateException("Unknown state " + state.mode);
         }
 
         mThumbSize = new Point(thumbSize, thumbSize);
@@ -366,7 +389,7 @@
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.addCategory(Intent.CATEGORY_DEFAULT);
             intent.setType(doc.mimeType);
-            intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
+            intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
 
         } else if (docs.size() > 1) {
             intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
@@ -377,7 +400,7 @@
             final ArrayList<Uri> uris = Lists.newArrayList();
             for (DocumentInfo doc : docs) {
                 mimeTypes.add(doc.mimeType);
-                uris.add(doc.uri);
+                uris.add(doc.derivedUri);
             }
 
             intent.setType(findCommonMimeType(mimeTypes));
@@ -403,7 +426,7 @@
                 continue;
             }
 
-            if (!DocumentsContract.deleteDocument(resolver, doc.uri)) {
+            if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
                 Log.w(TAG, "Failed to delete " + doc);
                 hadTrouble = true;
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 6ea57d7..72dfa30 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -16,18 +16,29 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.DocumentsActivity.TAG;
+import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
 
 import android.content.AsyncTaskLoader;
 import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.CancellationSignal;
 import android.os.OperationCanceledException;
 import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
 
 import libcore.io.IoUtils;
 
@@ -36,6 +47,9 @@
     Cursor cursor;
     Exception exception;
 
+    int mode = MODE_UNKNOWN;
+    int sortOrder = SORT_ORDER_UNKNOWN;
+
     @Override
     public void close() {
         IoUtils.closeQuietly(cursor);
@@ -48,18 +62,18 @@
 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
     private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
 
-    private final String mRootId;
+    private final RootInfo mRoot;
+    private final DocumentInfo mDoc;
     private final Uri mUri;
-    private final int mSortOrder;
 
     private CancellationSignal mSignal;
     private DirectoryResult mResult;
 
-    public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) {
+    public DirectoryLoader(Context context, RootInfo root, DocumentInfo doc, Uri uri) {
         super(context);
-        mRootId = rootId;
+        mRoot = root;
+        mDoc = doc;
         mUri = uri;
-        mSortOrder = sortOrder;
     }
 
     @Override
@@ -70,20 +84,65 @@
             }
             mSignal = new CancellationSignal();
         }
-        final DirectoryResult result = new DirectoryResult();
+
+        final ContentResolver resolver = getContext().getContentResolver();
         final String authority = mUri.getAuthority();
+
+        final DirectoryResult result = new DirectoryResult();
+
+        int userMode = State.MODE_UNKNOWN;
+        int userSortOrder = State.SORT_ORDER_UNKNOWN;
+
+        // Pick up any custom modes requested by user
+        Cursor cursor = null;
         try {
-            result.client = getContext()
-                    .getContentResolver().acquireUnstableContentProviderClient(authority);
-            final Cursor cursor = result.client.query(
-                    mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal);
+            final Uri stateUri = RecentsProvider.buildState(
+                    mRoot.authority, mRoot.rootId, mDoc.documentId);
+            cursor = resolver.query(stateUri, null, null, null, null);
+            if (cursor.moveToFirst()) {
+                userMode = getCursorInt(cursor, StateColumns.MODE);
+                userSortOrder = getCursorInt(cursor, StateColumns.SORT_ORDER);
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        if (userMode != State.MODE_UNKNOWN) {
+            result.mode = userMode;
+        } else {
+            if ((mDoc.flags & Document.FLAG_DIR_PREFERS_GRID) != 0) {
+                result.mode = State.MODE_GRID;
+            } else {
+                result.mode = State.MODE_LIST;
+            }
+        }
+
+        if (userSortOrder != State.SORT_ORDER_UNKNOWN) {
+            result.sortOrder = userSortOrder;
+        } else {
+            if ((mDoc.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0) {
+                result.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
+            } else {
+                result.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
+            }
+        }
+
+        Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + userSortOrder + " --> mode="
+                + result.mode + ", sortOrder=" + result.sortOrder);
+
+        try {
+            result.client = resolver.acquireUnstableContentProviderClient(authority);
+            cursor = result.client.query(
+                    mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
             cursor.registerContentObserver(mObserver);
 
-            final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1);
-            final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder);
+            final Cursor withRoot = new RootCursorWrapper(
+                    mUri.getAuthority(), mRoot.rootId, cursor, -1);
+            final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder);
 
             result.cursor = sorted;
         } catch (Exception e) {
+            Log.d(TAG, "Failed to query", e);
             result.exception = e;
             ContentProviderClient.closeQuietly(result.client);
         } finally {
@@ -91,6 +150,7 @@
                 mSignal = null;
             }
         }
+
         return result;
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 38b2ee8..fe39800 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -60,6 +60,9 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.documentsui.RecentsProvider.RecentColumns;
+import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.RecentsProvider.StateColumns;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
@@ -191,7 +194,7 @@
             try {
                 if (cursor.moveToFirst()) {
                     final byte[] rawStack = cursor.getBlob(
-                            cursor.getColumnIndex(RecentsProvider.COL_PATH));
+                            cursor.getColumnIndex(ResumeColumns.STACK));
                     DurableUtils.readFromArray(rawStack, mState.stack);
                 }
             } catch (IOException e) {
@@ -204,7 +207,7 @@
             final RootInfo root = getCurrentRoot();
             final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState);
             if (!matchingRoots.contains(root)) {
-                mState.stack.clear();
+                mState.stack.reset();
             }
 
             // Only open drawer when showing recents
@@ -343,11 +346,16 @@
         final MenuItem list = menu.findItem(R.id.menu_list);
         final MenuItem settings = menu.findItem(R.id.menu_settings);
 
-        grid.setVisible(mState.mode != MODE_GRID);
-        list.setVisible(mState.mode != MODE_LIST);
+        if (cwd != null) {
+            sort.setVisible(true);
+            grid.setVisible(mState.mode != MODE_GRID);
+            list.setVisible(mState.mode != MODE_LIST);
+        } else {
+            sort.setVisible(false);
+            grid.setVisible(false);
+            list.setVisible(false);
+        }
 
-        // No sorting in recents
-        sort.setVisible(cwd != null);
         // Only sort by size when visible
         sortSize.setVisible(mState.showSize);
 
@@ -392,28 +400,19 @@
         } else if (id == R.id.menu_search) {
             return false;
         } else if (id == R.id.menu_sort_name) {
-            mState.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
-            updateDisplayState();
+            setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
             return true;
         } else if (id == R.id.menu_sort_date) {
-            mState.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
-            updateDisplayState();
+            setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
             return true;
         } else if (id == R.id.menu_sort_size) {
-            mState.sortOrder = State.SORT_ORDER_SIZE;
-            updateDisplayState();
+            setUserSortOrder(State.SORT_ORDER_SIZE);
             return true;
         } else if (id == R.id.menu_grid) {
-            // TODO: persist explicit user mode for cwd
-            mState.mode = MODE_GRID;
-            updateDisplayState();
-            invalidateOptionsMenu();
+            setUserMode(State.MODE_GRID);
             return true;
         } else if (id == R.id.menu_list) {
-            // TODO: persist explicit user mode for cwd
-            mState.mode = MODE_LIST;
-            updateDisplayState();
-            invalidateOptionsMenu();
+            setUserMode(State.MODE_LIST);
             return true;
         } else if (id == R.id.menu_settings) {
             startActivity(new Intent(this, SettingsActivity.class));
@@ -423,6 +422,51 @@
         }
     }
 
+    /**
+     * Update UI to reflect internal state changes not from user.
+     */
+    public void onStateChanged() {
+        invalidateOptionsMenu();
+    }
+
+    /**
+     * Set state sort order based on explicit user action.
+     */
+    private void setUserSortOrder(int sortOrder) {
+        final RootInfo root = getCurrentRoot();
+        final DocumentInfo cwd = getCurrentDirectory();
+
+        // TODO: persist async, then trigger rebind
+        final Uri stateUri = RecentsProvider.buildState(
+                root.authority, root.rootId, cwd.documentId);
+        final ContentValues values = new ContentValues();
+        values.put(StateColumns.SORT_ORDER, sortOrder);
+        getContentResolver().insert(stateUri, values);
+
+        DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
+        onStateChanged();
+    }
+
+    /**
+     * Set state mode based on explicit user action.
+     */
+    private void setUserMode(int mode) {
+        final RootInfo root = getCurrentRoot();
+        final DocumentInfo cwd = getCurrentDirectory();
+
+        // TODO: persist async, then trigger rebind
+        final Uri stateUri = RecentsProvider.buildState(
+                root.authority, root.rootId, cwd.documentId);
+        final ContentValues values = new ContentValues();
+        values.put(StateColumns.MODE, mode);
+        getContentResolver().insert(stateUri, values);
+
+        mState.mode = mode;
+
+        DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
+        onStateChanged();
+    }
+
     @Override
     public void onBackPressed() {
         final int size = mState.stack.size();
@@ -528,8 +572,8 @@
     };
 
     public RootInfo getCurrentRoot() {
-        if (mState.stack.size() > 0) {
-            return mState.stack.getRoot(mRoots);
+        if (mState.stack.root != null) {
+            return mState.stack.root;
         } else {
             return mRoots.getRecentsRoot();
         }
@@ -545,6 +589,7 @@
 
     private void onCurrentDirectoryChanged() {
         final FragmentManager fm = getFragmentManager();
+        final RootInfo root = getCurrentRoot();
         final DocumentInfo cwd = getCurrentDirectory();
 
         if (cwd == null) {
@@ -557,10 +602,10 @@
         } else {
             if (mState.currentSearch != null) {
                 // Ongoing search
-                DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch);
+                DirectoryFragment.showSearch(fm, root, cwd, mState.currentSearch);
             } else {
                 // Normal boring directory
-                DirectoryFragment.showNormal(fm, cwd.uri);
+                DirectoryFragment.showNormal(fm, root, cwd);
             }
         }
 
@@ -582,11 +627,6 @@
         dumpStack();
     }
 
-    private void updateDisplayState() {
-        // TODO: handle multiple directory stacks on tablets
-        DirectoryFragment.get(getFragmentManager()).updateDisplayState();
-    }
-
     public void onStackPicked(DocumentStack stack) {
         mState.stack = stack;
         onCurrentDirectoryChanged();
@@ -594,6 +634,7 @@
 
     public void onRootPicked(RootInfo root, boolean closeDrawer) {
         // Clear entire backstack and start in new root
+        mState.stack.root = root;
         mState.stack.clear();
 
         if (!mRoots.isRecentsRoot(root)) {
@@ -633,7 +674,7 @@
             onCurrentDirectoryChanged();
         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
-            onFinished(doc.uri);
+            onFinished(doc.derivedUri);
         } else if (mState.action == ACTION_CREATE) {
             // Replace selected file
             SaveFragment.get(fm).setReplaceTarget(doc);
@@ -641,7 +682,7 @@
             // First try managing the document; we expect manager to filter
             // based on authority, so we don't grant.
             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
-            manage.setData(doc.uri);
+            manage.setData(doc.derivedUri);
 
             try {
                 startActivity(manage);
@@ -649,7 +690,7 @@
                 // Fall back to viewing
                 final Intent view = new Intent(Intent.ACTION_VIEW);
                 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                view.setData(doc.uri);
+                view.setData(doc.derivedUri);
 
                 try {
                     startActivity(view);
@@ -665,22 +706,21 @@
             final int size = docs.size();
             final Uri[] uris = new Uri[size];
             for (int i = 0; i < size; i++) {
-                uris[i] = docs.get(i).uri;
+                uris[i] = docs.get(i).derivedUri;
             }
             onFinished(uris);
         }
     }
 
     public void onSaveRequested(DocumentInfo replaceTarget) {
-        onFinished(replaceTarget.uri);
+        onFinished(replaceTarget.derivedUri);
     }
 
     public void onSaveRequested(String mimeType, String displayName) {
         final DocumentInfo cwd = getCurrentDirectory();
-        final String authority = cwd.uri.getAuthority();
 
         final Uri childUri = DocumentsContract.createDocument(
-                getContentResolver(), cwd.uri, mimeType, displayName);
+                getContentResolver(), cwd.derivedUri, mimeType, displayName);
         if (childUri != null) {
             onFinished(childUri);
         } else {
@@ -698,22 +738,14 @@
         if (mState.action == ACTION_CREATE) {
             // Remember stack for last create
             values.clear();
-            values.put(RecentsProvider.COL_PATH, rawStack);
-            resolver.insert(RecentsProvider.buildRecentCreate(), values);
-
-        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
-            // Remember opened items
-            for (Uri uri : uris) {
-                values.clear();
-                values.put(RecentsProvider.COL_URI, uri.toString());
-                resolver.insert(RecentsProvider.buildRecentOpen(), values);
-            }
+            values.put(RecentColumns.STACK, rawStack);
+            resolver.insert(RecentsProvider.buildRecent(), values);
         }
 
         // Remember location for next app launch
         final String packageName = getCallingPackage();
         values.clear();
-        values.put(RecentsProvider.COL_PATH, rawStack);
+        values.put(ResumeColumns.STACK, rawStack);
         resolver.insert(RecentsProvider.buildResume(packageName), values);
 
         final Intent intent = new Intent();
@@ -760,12 +792,14 @@
         public static final int ACTION_GET_CONTENT = 3;
         public static final int ACTION_MANAGE = 4;
 
-        public static final int MODE_LIST = 0;
-        public static final int MODE_GRID = 1;
+        public static final int MODE_UNKNOWN = 0;
+        public static final int MODE_LIST = 1;
+        public static final int MODE_GRID = 2;
 
-        public static final int SORT_ORDER_DISPLAY_NAME = 0;
-        public static final int SORT_ORDER_LAST_MODIFIED = 1;
-        public static final int SORT_ORDER_SIZE = 2;
+        public static final int SORT_ORDER_UNKNOWN = 0;
+        public static final int SORT_ORDER_DISPLAY_NAME = 1;
+        public static final int SORT_ORDER_LAST_MODIFIED = 2;
+        public static final int SORT_ORDER_SIZE = 3;
 
         @Override
         public int describeContents() {
@@ -811,9 +845,10 @@
     }
 
     private void dumpStack() {
-        Log.d(TAG, "Current stack:");
+        Log.d(TAG, "Current stack: ");
+        Log.d(TAG, " * " + mState.stack.root);
         for (DocumentInfo doc : mState.stack) {
-            Log.d(TAG, "--> " + doc);
+            Log.d(TAG, " +-- " + doc);
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
new file mode 100644
index 0000000..60f0103
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You 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.documentsui;
+
+import static com.android.documentsui.DocumentsActivity.TAG;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+/**
+ * Cursor wrapper that filters MIME types not matching given list.
+ */
+public class FilteringCursorWrapper extends AbstractCursor {
+    private final Cursor mCursor;
+
+    private final int[] mPosition;
+    private int mCount;
+
+    public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
+        mCursor = cursor;
+
+        final int count = cursor.getCount();
+        mPosition = new int[count];
+
+        cursor.moveToPosition(-1);
+        while (cursor.moveToNext()) {
+            final String mimeType = cursor.getString(
+                    cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+            if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
+                mPosition[mCount++] = cursor.getPosition();
+            }
+        }
+
+        Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return mCursor.getExtras();
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mCursor.close();
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        return mCursor.moveToPosition(mPosition[newPosition]);
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mCursor.getColumnNames();
+    }
+
+    @Override
+    public int getCount() {
+        return mCount;
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public int getInt(int column) {
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column) {
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public short getShort(int column) {
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public String getString(int column) {
+        return mCursor.getString(column);
+    }
+
+    @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return mCursor.isNull(column);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
index 85d0988..b55ce82 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -49,6 +49,18 @@
         return false;
     }
 
+    public static boolean mimeMatches(String filter, String[] tests) {
+        if (tests == null) {
+            return true;
+        }
+        for (String test : tests) {
+            if (mimeMatches(filter, test)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static boolean mimeMatches(String[] filters, String test) {
         if (filters == null) {
             return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 756a297..57442a0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -17,6 +17,9 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.DocumentsActivity.TAG;
+import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
+import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
 
 import android.content.AsyncTaskLoader;
 import android.content.ContentProviderClient;
@@ -79,6 +82,7 @@
     }
 
     private final List<RootInfo> mRoots;
+    private final String[] mAcceptMimes;
 
     private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
 
@@ -135,9 +139,10 @@
         }
     }
 
-    public RecentLoader(Context context, List<RootInfo> roots) {
+    public RecentLoader(Context context, List<RootInfo> roots, String[] acceptMimes) {
         super(context);
         mRoots = roots;
+        mAcceptMimes = acceptMimes;
     }
 
     @Override
@@ -171,7 +176,15 @@
         for (RecentTask task : mTasks.values()) {
             if (task.isDone()) {
                 try {
-                    cursors.add(task.get());
+                    final Cursor cursor = task.get();
+                    final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
+                            cursor, mAcceptMimes) {
+                        @Override
+                        public void close() {
+                            // Ignored, since we manage cursor lifecycle internally
+                        }
+                    };
+                    cursors.add(filtered);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (ExecutionException e) {
@@ -181,15 +194,14 @@
         }
 
         final DirectoryResult result = new DirectoryResult();
+
+        final boolean acceptImages = MimePredicate.mimeMatches("image/*", mAcceptMimes);
+        result.mode = acceptImages ? MODE_GRID : MODE_LIST;
+        result.sortOrder = SORT_ORDER_LAST_MODIFIED;
+
         if (cursors.size() > 0) {
             final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
-            final SortingCursorWrapper sorted = new SortingCursorWrapper(
-                    merged, State.SORT_ORDER_LAST_MODIFIED) {
-                @Override
-                public void close() {
-                    // Ignored, since we manage cursor lifecycle internally
-                }
-            };
+            final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder);
             result.cursor = sorted;
         }
         return result;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 461c415..9391ca9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -41,8 +41,8 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import com.android.documentsui.RecentsProvider.RecentColumns;
 import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.model.RootInfo;
 import com.google.android.collect.Lists;
 
 import libcore.io.IoUtils;
@@ -128,7 +128,7 @@
 
     public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> {
         public RecentsCreateLoader(Context context) {
-            super(context, RecentsProvider.buildRecentCreate());
+            super(context, RecentsProvider.buildRecent());
         }
 
         @Override
@@ -137,14 +137,14 @@
 
             final ContentResolver resolver = getContext().getContentResolver();
             final Cursor cursor = resolver.query(
-                    uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal);
+                    uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal);
             try {
                 while (cursor != null && cursor.moveToNext()) {
-                    final byte[] raw = cursor.getBlob(
-                            cursor.getColumnIndex(RecentsProvider.COL_PATH));
+                    final byte[] rawStack = cursor.getBlob(
+                            cursor.getColumnIndex(RecentColumns.STACK));
                     try {
                         final DocumentStack stack = new DocumentStack();
-                        stack.read(new DataInputStream(new ByteArrayInputStream(raw)));
+                        stack.read(new DataInputStream(new ByteArrayInputStream(rawStack)));
                         result.add(stack);
                     } catch (IOException e) {
                         Log.w(TAG, "Failed to resolve stack: " + e);
@@ -183,8 +183,7 @@
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
 
             final DocumentStack stack = getItem(position);
-            final RootInfo root = stack.getRoot(roots);
-            icon.setImageDrawable(root.loadIcon(context));
+            icon.setImageDrawable(stack.root.loadIcon(context));
 
             final StringBuilder builder = new StringBuilder();
             for (int i = stack.size() - 1; i >= 0; i--) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 0c87783..df7ed4a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -25,51 +25,64 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.net.Uri;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.text.format.DateUtils;
 import android.util.Log;
 
 public class RecentsProvider extends ContentProvider {
     private static final String TAG = "RecentsProvider";
 
-    // TODO: offer view of recents that handles backend root resolution before
-    // returning cursor, include extra columns
+    public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45;
 
-    public static final String AUTHORITY = "com.android.documentsui.recents";
+    private static final String AUTHORITY = "com.android.documentsui.recents";
 
     private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
-    private static final int URI_RECENT_OPEN = 1;
-    private static final int URI_RECENT_CREATE = 2;
+    private static final int URI_RECENT = 1;
+    private static final int URI_STATE = 2;
     private static final int URI_RESUME = 3;
 
     static {
-        sMatcher.addURI(AUTHORITY, "recent_open", URI_RECENT_OPEN);
-        sMatcher.addURI(AUTHORITY, "recent_create", URI_RECENT_CREATE);
+        sMatcher.addURI(AUTHORITY, "recent", URI_RECENT);
+        // state/authority/rootId/docId
+        sMatcher.addURI(AUTHORITY, "state/*/*/*", URI_STATE);
+        // resume/packageName
         sMatcher.addURI(AUTHORITY, "resume/*", URI_RESUME);
     }
 
-    private static final String TABLE_RECENT_OPEN = "recent_open";
-    private static final String TABLE_RECENT_CREATE = "recent_create";
-    private static final String TABLE_RESUME = "resume";
+    public static final String TABLE_RECENT = "recent";
+    public static final String TABLE_STATE = "state";
+    public static final String TABLE_RESUME = "resume";
 
-    /**
-     * String of URIs pointing at a storage backend, stored as a JSON array,
-     * starting with root.
-     */
-    public static final String COL_PATH = "path";
-    public static final String COL_URI = "uri";
-    public static final String COL_PACKAGE_NAME = "package_name";
-    public static final String COL_TIMESTAMP = "timestamp";
-
-    @Deprecated
-    public static Uri buildRecentOpen() {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(AUTHORITY).appendPath("recent_open").build();
+    public static class RecentColumns {
+        public static final String STACK = "stack";
+        public static final String TIMESTAMP = "timestamp";
     }
 
-    public static Uri buildRecentCreate() {
+    public static class StateColumns {
+        public static final String AUTHORITY = "authority";
+        public static final String ROOT_ID = Root.COLUMN_ROOT_ID;
+        public static final String DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID;
+        public static final String MODE = "mode";
+        public static final String SORT_ORDER = "sortOrder";
+    }
+
+    public static class ResumeColumns {
+        public static final String PACKAGE_NAME = "package_name";
+        public static final String STACK = "stack";
+        public static final String TIMESTAMP = "timestamp";
+    }
+
+    public static Uri buildRecent() {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(AUTHORITY).appendPath("recent_create").build();
+                .authority(AUTHORITY).appendPath("recent").build();
+    }
+
+    public static Uri buildState(String authority, String rootId, String documentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+                .appendPath("state").appendPath(authority).appendPath(rootId).appendPath(documentId)
+                .build();
     }
 
     public static Uri buildResume(String packageName) {
@@ -83,35 +96,42 @@
         private static final String DB_NAME = "recents.db";
 
         private static final int VERSION_INIT = 1;
+        private static final int VERSION_AS_BLOB = 3;
 
         public DatabaseHelper(Context context) {
-            super(context, DB_NAME, null, VERSION_INIT);
+            super(context, DB_NAME, null, VERSION_AS_BLOB);
         }
 
         @Override
         public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE_RECENT_OPEN + " (" +
-                    COL_URI + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
-                    COL_TIMESTAMP + " INTEGER" +
+
+            db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" +
+                    RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," +
+                    RecentColumns.TIMESTAMP + " INTEGER" +
                     ")");
 
-            db.execSQL("CREATE TABLE " + TABLE_RECENT_CREATE + " (" +
-                    COL_PATH + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
-                    COL_TIMESTAMP + " INTEGER" +
+            db.execSQL("CREATE TABLE " + TABLE_STATE + " (" +
+                    StateColumns.AUTHORITY + " TEXT," +
+                    StateColumns.ROOT_ID + " TEXT," +
+                    StateColumns.DOCUMENT_ID + " TEXT," +
+                    StateColumns.MODE + " INTEGER," +
+                    StateColumns.SORT_ORDER + " INTEGER," +
+                    "PRIMARY KEY (" + StateColumns.AUTHORITY + ", " + StateColumns.ROOT_ID + ", "
+                    + StateColumns.DOCUMENT_ID + ")" +
                     ")");
 
             db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" +
-                    COL_PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
-                    COL_PATH + " TEXT," +
-                    COL_TIMESTAMP + " INTEGER" +
+                    ResumeColumns.PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
+                    ResumeColumns.STACK + " BLOB," +
+                    ResumeColumns.TIMESTAMP + " INTEGER" +
                     ")");
         }
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.w(TAG, "Upgrading database; wiping app data");
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_OPEN);
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_CREATE);
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT);
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATE);
             db.execSQL("DROP TABLE IF EXISTS " + TABLE_RESUME);
             onCreate(db);
         }
@@ -128,22 +148,23 @@
             String sortOrder) {
         final SQLiteDatabase db = mHelper.getReadableDatabase();
         switch (sMatcher.match(uri)) {
-            case URI_RECENT_OPEN: {
-                return db.query(TABLE_RECENT_OPEN, projection,
-                        buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null);
-            }
-            case URI_RECENT_CREATE: {
-                return db.query(TABLE_RECENT_CREATE, projection,
-                        buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null);
-            }
-            case URI_RESUME: {
+            case URI_RECENT:
+                return db.query(TABLE_RECENT, projection,
+                        RecentColumns.TIMESTAMP + "<" + MAX_HISTORY_IN_MILLIS, null, null, null,
+                        null);
+            case URI_STATE:
+                final String authority = uri.getPathSegments().get(1);
+                final String rootId = uri.getPathSegments().get(2);
+                final String documentId = uri.getPathSegments().get(3);
+                return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND "
+                        + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
+                        new String[] { authority, rootId, documentId }, null, null, null);
+            case URI_RESUME:
                 final String packageName = uri.getPathSegments().get(1);
-                return db.query(TABLE_RESUME, projection, COL_PACKAGE_NAME + "=?",
+                return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?",
                         new String[] { packageName }, null, null, null);
-            }
-            default: {
+            default:
                 throw new UnsupportedOperationException("Unsupported Uri " + uri);
-            }
         }
     }
 
@@ -156,28 +177,37 @@
     public Uri insert(Uri uri, ContentValues values) {
         final SQLiteDatabase db = mHelper.getWritableDatabase();
         switch (sMatcher.match(uri)) {
-            case URI_RECENT_OPEN: {
-                values.put(COL_TIMESTAMP, System.currentTimeMillis());
-                db.insert(TABLE_RECENT_OPEN, null, values);
-                db.delete(TABLE_RECENT_OPEN, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+            case URI_RECENT:
+                values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis());
+                db.insert(TABLE_RECENT, null, values);
+                db.delete(
+                        TABLE_RECENT, RecentColumns.TIMESTAMP + ">" + MAX_HISTORY_IN_MILLIS, null);
                 return uri;
-            }
-            case URI_RECENT_CREATE: {
-                values.put(COL_TIMESTAMP, System.currentTimeMillis());
-                db.insert(TABLE_RECENT_CREATE, null, values);
-                db.delete(TABLE_RECENT_CREATE, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+            case URI_STATE:
+                final String authority = uri.getPathSegments().get(1);
+                final String rootId = uri.getPathSegments().get(2);
+                final String documentId = uri.getPathSegments().get(3);
+
+                final ContentValues key = new ContentValues();
+                key.put(StateColumns.AUTHORITY, authority);
+                key.put(StateColumns.ROOT_ID, rootId);
+                key.put(StateColumns.DOCUMENT_ID, documentId);
+
+                // Ensure that row exists, then update with changed values
+                db.insertWithOnConflict(TABLE_STATE, null, key, SQLiteDatabase.CONFLICT_IGNORE);
+                db.update(TABLE_STATE, values, StateColumns.AUTHORITY + "=? AND "
+                        + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
+                        new String[] { authority, rootId, documentId });
+
                 return uri;
-            }
-            case URI_RESUME: {
+            case URI_RESUME:
                 final String packageName = uri.getPathSegments().get(1);
-                values.put(COL_PACKAGE_NAME, packageName);
-                values.put(COL_TIMESTAMP, System.currentTimeMillis());
+                values.put(ResumeColumns.PACKAGE_NAME, packageName);
+                values.put(ResumeColumns.TIMESTAMP, System.currentTimeMillis());
                 db.insert(TABLE_RESUME, null, values);
                 return uri;
-            }
-            default: {
+            default:
                 throw new UnsupportedOperationException("Unsupported Uri " + uri);
-            }
         }
     }
 
@@ -190,12 +220,4 @@
     public int delete(Uri uri, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException("Unsupported Uri " + uri);
     }
-
-    private static String buildWhereOlder(long deltaMillis) {
-        return COL_TIMESTAMP + "<" + (System.currentTimeMillis() - deltaMillis);
-    }
-
-    private static String buildWhereYounger(long deltaMillis) {
-        return COL_TIMESTAMP + ">" + (System.currentTimeMillis() - deltaMillis);
-    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index d4f1b39..8530a9f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -169,8 +169,9 @@
             if (state.localOnly && !localOnly) continue;
 
             // Only include roots that serve requested content
-            final boolean overlap = MimePredicate.mimeMatches(root.mimeTypes, state.acceptMimes)
-                    || MimePredicate.mimeMatches(state.acceptMimes, root.mimeTypes);
+            final boolean overlap =
+                    MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
+                    MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
             if (!overlap) {
                 continue;
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index a1489a5..c69103e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -20,6 +20,8 @@
 import android.content.ContentResolver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 
@@ -32,15 +34,16 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.util.Comparator;
 
 /**
  * Representation of a {@link Document}.
  */
-public class DocumentInfo implements Durable {
+public class DocumentInfo implements Durable, Parcelable {
     private static final int VERSION_INIT = 1;
+    private static final int VERSION_SPLIT_URI = 2;
 
-    public Uri uri;
+    public String authority;
+    public String documentId;
     public String mimeType;
     public String displayName;
     public long lastModified;
@@ -49,13 +52,17 @@
     public long size;
     public int icon;
 
+    /** Derived fields that aren't persisted */
+    public Uri derivedUri;
+
     public DocumentInfo() {
         reset();
     }
 
     @Override
     public void reset() {
-        uri = null;
+        authority = null;
+        documentId = null;
         mimeType = null;
         displayName = null;
         lastModified = -1;
@@ -63,6 +70,8 @@
         summary = null;
         size = -1;
         icon = 0;
+
+        derivedUri = null;
     }
 
     @Override
@@ -70,8 +79,10 @@
         final int version = in.readInt();
         switch (version) {
             case VERSION_INIT:
-                final String rawUri = DurableUtils.readNullableString(in);
-                uri = rawUri != null ? Uri.parse(rawUri) : null;
+                throw new ProtocolException("Ignored upgrade");
+            case VERSION_SPLIT_URI:
+                authority = DurableUtils.readNullableString(in);
+                documentId = DurableUtils.readNullableString(in);
                 mimeType = DurableUtils.readNullableString(in);
                 displayName = DurableUtils.readNullableString(in);
                 lastModified = in.readLong();
@@ -79,6 +90,7 @@
                 summary = DurableUtils.readNullableString(in);
                 size = in.readLong();
                 icon = in.readInt();
+                deriveFields();
                 break;
             default:
                 throw new ProtocolException("Unknown version " + version);
@@ -87,8 +99,9 @@
 
     @Override
     public void write(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_INIT);
-        DurableUtils.writeNullableString(out, uri.toString());
+        out.writeInt(VERSION_SPLIT_URI);
+        DurableUtils.writeNullableString(out, authority);
+        DurableUtils.writeNullableString(out, documentId);
         DurableUtils.writeNullableString(out, mimeType);
         DurableUtils.writeNullableString(out, displayName);
         out.writeLong(lastModified);
@@ -98,11 +111,41 @@
         out.writeInt(icon);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        DurableUtils.writeToParcel(dest, this);
+    }
+
+    public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() {
+        @Override
+        public DocumentInfo createFromParcel(Parcel in) {
+            final DocumentInfo doc = new DocumentInfo();
+            DurableUtils.readFromParcel(in, doc);
+            return doc;
+        }
+
+        @Override
+        public DocumentInfo[] newArray(int size) {
+            return new DocumentInfo[size];
+        }
+    };
+
     public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
-        final DocumentInfo doc = new DocumentInfo();
         final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
-        final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
-        doc.uri = DocumentsContract.buildDocumentUri(authority, docId);
+        return fromCursor(cursor, authority);
+    }
+
+    public static DocumentInfo fromCursor(Cursor cursor, String authority) {
+        final DocumentInfo doc = new DocumentInfo();
+        doc.authority = authority;
+        doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+        doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+        doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
         doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
         doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
         doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
@@ -110,6 +153,7 @@
         doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
         doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
         doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
+        doc.deriveFields();
         return doc;
     }
 
@@ -122,16 +166,7 @@
             if (!cursor.moveToFirst()) {
                 throw new FileNotFoundException("Missing details for " + uri);
             }
-            final DocumentInfo doc = new DocumentInfo();
-            doc.uri = uri;
-            doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-            doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
-            doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
-            doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-            doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
-            doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
-            doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
-            return doc;
+            return fromCursor(cursor, uri.getAuthority());
         } catch (Throwable t) {
             throw asFileNotFoundException(t);
         } finally {
@@ -140,9 +175,13 @@
         }
     }
 
+    private void deriveFields() {
+        derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
+    }
+
     @Override
     public String toString() {
-        return "Document{name=" + displayName + ", uri=" + uri + "}";
+        return "Document{name=" + displayName + ", docId=" + documentId + "}";
     }
 
     public boolean isCreateSupported() {
@@ -189,42 +228,14 @@
         }
     }
 
+    /**
+     * Missing or null values are returned as 0.
+     */
     public static int getCursorInt(Cursor cursor, String columnName) {
         final int index = cursor.getColumnIndex(columnName);
         return (index != -1) ? cursor.getInt(index) : 0;
     }
 
-    @Deprecated
-    public static class DisplayNameComparator implements Comparator<DocumentInfo> {
-        @Override
-        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
-            final boolean leftDir = lhs.isDirectory();
-            final boolean rightDir = rhs.isDirectory();
-
-            if (leftDir != rightDir) {
-                return leftDir ? -1 : 1;
-            } else {
-                return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
-            }
-        }
-    }
-
-    @Deprecated
-    public static class LastModifiedComparator implements Comparator<DocumentInfo> {
-        @Override
-        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
-            return Long.compare(rhs.lastModified, lhs.lastModified);
-        }
-    }
-
-    @Deprecated
-    public static class SizeComparator implements Comparator<DocumentInfo> {
-        @Override
-        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
-            return Long.compare(rhs.size, lhs.size);
-        }
-    }
-
     public static FileNotFoundException asFileNotFoundException(Throwable t)
             throws FileNotFoundException {
         if (t instanceof FileNotFoundException) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 33a1376..2541440 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -16,8 +16,6 @@
 
 package com.android.documentsui.model;
 
-import com.android.documentsui.RootsCache;
-
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -30,14 +28,13 @@
  */
 public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
     private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADD_ROOT = 2;
 
-    public RootInfo getRoot(RootsCache roots) {
-        return roots.findRoot(getLast().uri);
-    }
+    public RootInfo root;
 
-    public String getTitle(RootsCache roots) {
-        if (size() == 1) {
-            return getRoot(roots).title;
+    public String getTitle() {
+        if (size() == 1 && root != null) {
+            return root.title;
         } else if (size() > 1) {
             return peek().displayName;
         } else {
@@ -52,6 +49,7 @@
     @Override
     public void reset() {
         clear();
+        root = null;
     }
 
     @Override
@@ -59,6 +57,12 @@
         final int version = in.readInt();
         switch (version) {
             case VERSION_INIT:
+                throw new ProtocolException("Ignored upgrade");
+            case VERSION_ADD_ROOT:
+                if (in.readBoolean()) {
+                    root = new RootInfo();
+                    root.read(in);
+                }
                 final int size = in.readInt();
                 for (int i = 0; i < size; i++) {
                     final DocumentInfo doc = new DocumentInfo();
@@ -73,7 +77,13 @@
 
     @Override
     public void write(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_INIT);
+        out.writeInt(VERSION_ADD_ROOT);
+        if (root != null) {
+            out.writeBoolean(true);
+            root.write(out);
+        } else {
+            out.writeBoolean(false);
+        }
         final int size = size();
         out.writeInt(size);
         for (int i = 0; i < size; i++) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index a6ddf70..e0e8acf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -23,28 +23,121 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.provider.DocumentsContract.Root;
 
 import com.android.documentsui.IconUtils;
 import com.android.documentsui.R;
 
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
 import java.util.Objects;
 
 /**
  * Representation of a {@link Root}.
  */
-public class RootInfo {
+public class RootInfo implements Durable, Parcelable {
+    private static final int VERSION_INIT = 1;
+
     public String authority;
     public String rootId;
     public int rootType;
     public int flags;
     public int icon;
-    public int localIcon;
     public String title;
     public String summary;
     public String documentId;
     public long availableBytes;
-    public String[] mimeTypes;
+    public String mimeTypes;
+
+    /** Derived fields that aren't persisted */
+    public String[] derivedMimeTypes;
+    public int derivedIcon;
+
+    public RootInfo() {
+        reset();
+    }
+
+    @Override
+    public void reset() {
+        authority = null;
+        rootId = null;
+        rootType = 0;
+        flags = 0;
+        icon = 0;
+        title = null;
+        summary = null;
+        documentId = null;
+        availableBytes = -1;
+        mimeTypes = null;
+
+        derivedMimeTypes = null;
+        derivedIcon = 0;
+    }
+
+    @Override
+    public void read(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_INIT:
+                authority = DurableUtils.readNullableString(in);
+                rootId = DurableUtils.readNullableString(in);
+                rootType = in.readInt();
+                flags = in.readInt();
+                icon = in.readInt();
+                title = DurableUtils.readNullableString(in);
+                summary = DurableUtils.readNullableString(in);
+                documentId = DurableUtils.readNullableString(in);
+                availableBytes = in.readLong();
+                mimeTypes = DurableUtils.readNullableString(in);
+                deriveFields();
+                break;
+            default:
+                throw new ProtocolException("Unknown version " + version);
+        }
+    }
+
+    @Override
+    public void write(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_INIT);
+        DurableUtils.writeNullableString(out, authority);
+        DurableUtils.writeNullableString(out, rootId);
+        out.writeInt(rootType);
+        out.writeInt(flags);
+        out.writeInt(icon);
+        DurableUtils.writeNullableString(out, title);
+        DurableUtils.writeNullableString(out, summary);
+        DurableUtils.writeNullableString(out, documentId);
+        out.writeLong(availableBytes);
+        DurableUtils.writeNullableString(out, mimeTypes);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        DurableUtils.writeToParcel(dest, this);
+    }
+
+    public static final Creator<RootInfo> CREATOR = new Creator<RootInfo>() {
+        @Override
+        public RootInfo createFromParcel(Parcel in) {
+            final RootInfo root = new RootInfo();
+            DurableUtils.readFromParcel(in, root);
+            return root;
+        }
+
+        @Override
+        public RootInfo[] newArray(int size) {
+            return new RootInfo[size];
+        }
+    };
 
     public static RootInfo fromRootsCursor(String authority, Cursor cursor) {
         final RootInfo root = new RootInfo();
@@ -57,31 +150,38 @@
         root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY);
         root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID);
         root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES);
-
-        final String raw = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
-        root.mimeTypes = (raw != null) ? raw.split("\n") : null;
-
-        // TODO: remove these special case icons
-        if ("com.android.externalstorage.documents".equals(authority)) {
-            root.localIcon = R.drawable.ic_root_sdcard;
-        }
-        if ("com.android.providers.downloads.documents".equals(authority)) {
-            root.localIcon = R.drawable.ic_root_download;
-        }
-        if ("com.android.providers.media.documents".equals(authority)) {
-            if ("image".equals(root.rootId)) {
-                root.localIcon = R.drawable.ic_doc_image;
-            } else if ("audio".equals(root.rootId)) {
-                root.localIcon = R.drawable.ic_doc_audio;
-            }
-        }
-
+        root.mimeTypes = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
+        root.deriveFields();
         return root;
     }
 
+    private void deriveFields() {
+        derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
+
+        // TODO: remove these special case icons
+        if ("com.android.externalstorage.documents".equals(authority)) {
+            derivedIcon = R.drawable.ic_root_sdcard;
+        }
+        if ("com.android.providers.downloads.documents".equals(authority)) {
+            derivedIcon = R.drawable.ic_root_download;
+        }
+        if ("com.android.providers.media.documents".equals(authority)) {
+            if ("image".equals(rootId)) {
+                derivedIcon = R.drawable.ic_doc_image;
+            } else if ("audio".equals(rootId)) {
+                derivedIcon = R.drawable.ic_doc_audio;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Root{title=" + title + ", rootId=" + rootId + "}";
+    }
+
     public Drawable loadIcon(Context context) {
-        if (localIcon != 0) {
-            return context.getResources().getDrawable(localIcon);
+        if (derivedIcon != 0) {
+            return context.getResources().getDrawable(derivedIcon);
         } else {
             return IconUtils.loadPackageIcon(context, authority, icon);
         }
diff --git a/packages/SystemUI/res/drawable-nodpi/lightning.png b/packages/SystemUI/res/drawable-nodpi/lightning.png
deleted file mode 100644
index 29de308..0000000
--- a/packages/SystemUI/res/drawable-nodpi/lightning.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/values/arrays.xml b/packages/SystemUI/res/values/arrays.xml
index 0812e80..b2c8aee 100644
--- a/packages/SystemUI/res/values/arrays.xml
+++ b/packages/SystemUI/res/values/arrays.xml
@@ -51,5 +51,14 @@
         <item>#FFFF3300</item>
         <item>#FFFFFFFF</item>
     </array>
-
+    <array name="batterymeter_bolt_points">
+        <item>88</item> <item>0</item>
+        <item>459</item><item>1</item>
+        <item>238</item><item>333</item>
+        <item>525</item><item>310</item>
+        <item>120</item><item>840</item>
+        <item>82</item> <item>818</item>
+        <item>246</item><item>373</item>
+        <item>0</item>  <item>408</item>
+    </array>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index be5c326..2257617 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -23,12 +23,13 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.LightingColorFilter;
 import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
 import android.os.BatteryManager;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -43,26 +44,27 @@
     public static final boolean SINGLE_DIGIT_PERCENT = false;
     public static final boolean SHOW_100_PERCENT = false;
 
-    private static final LightingColorFilter LIGHTNING_FILTER_OPAQUE =
-            new LightingColorFilter(0x00000000, 0x00000000);
-    private static final LightingColorFilter LIGHTNING_FILTER_TRANS =
-            new LightingColorFilter(0x00999999, 0x00000000);
-
     public static final int FULL = 96;
     public static final int EMPTY = 4;
 
     int[] mColors;
 
     boolean mShowPercent = true;
-    Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint;
+    Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
     int mButtonHeight;
     private float mTextHeight, mWarningTextHeight;
-    Drawable mLightning;
 
     private int mHeight;
     private int mWidth;
     private String mWarningString;
     private final int mChargeColor;
+    private final float[] mBoltPoints;
+    private final Path mBoltPath = new Path();
+
+    private final RectF mFrame = new RectF();
+    private final RectF mButtonFrame = new RectF();
+    private final RectF mClipFrame = new RectF();
+    private final Rect mBoltFrame = new Rect();
 
     private class BatteryTracker extends BroadcastReceiver {
         // current battery status
@@ -175,7 +177,8 @@
             mColors[2*i] = levels.getInt(i, 0);
             mColors[2*i+1] = colors.getColor(i, 0);
         }
-
+        levels.recycle();
+        colors.recycle();
         mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
                 context.getContentResolver(), "status_bar_show_battery_percent", 0);
 
@@ -198,8 +201,28 @@
         mWarningTextPaint.setTypeface(font);
         mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
 
-        mLightning = getResources().getDrawable(R.drawable.lightning);
         mChargeColor = getResources().getColor(R.color.batterymeter_charge_color);
+
+        mBoltPaint = new Paint();
+        mBoltPaint.setAntiAlias(true);
+        mBoltPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));  // punch hole
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+        mBoltPoints = loadBoltPoints(res);
+    }
+
+    private static float[] loadBoltPoints(Resources res) {
+        final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
+        int maxX = 0, maxY = 0;
+        for (int i = 0; i < pts.length; i += 2) {
+            maxX = Math.max(maxX, pts[i]);
+            maxY = Math.max(maxY, pts[i + 1]);
+        }
+        final float[] ptsF = new float[pts.length];
+        for (int i = 0; i < pts.length; i += 2) {
+            ptsF[i] = (float)pts[i] / maxX;
+            ptsF[i + 1] = (float)pts[i + 1] / maxY;
+        }
+        return ptsF;
     }
 
     @Override
@@ -220,15 +243,6 @@
         return color;
     }
 
-    // TODO jspurlock - remove once we draw hollow bolt in code
-    public void setBarTransparent(boolean isTransparent) {
-        mLightning.setColorFilter(isTransparent ? LIGHTNING_FILTER_TRANS : LIGHTNING_FILTER_OPAQUE);
-        BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
-        if (tracker.plugged) {
-            postInvalidate();
-        }
-    }
-
     @Override
     public void draw(Canvas c) {
         BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
@@ -243,22 +257,19 @@
 
         mButtonHeight = (int) (height * 0.12f);
 
-        final RectF frame = new RectF(0, 0, width, height);
-        frame.offset(pl, pt);
+        mFrame.set(0, 0, width, height);
+        mFrame.offset(pl, pt);
 
-        // Log.v("BatteryGauge", String.format("canvas: %dx%d frame: %s",
-        // c.getWidth(), c.getHeight(), frame.toString()));
+        mButtonFrame.set(
+                mFrame.left + width * 0.25f,
+                mFrame.top,
+                mFrame.right - width * 0.25f,
+                mFrame.top + mButtonHeight);
 
-        final RectF buttonframe = new RectF(
-                frame.left + width * 0.25f,
-                frame.top,
-                frame.right - width * 0.25f,
-                frame.top + mButtonHeight);
-
-        frame.top += mButtonHeight;
+        mFrame.top += mButtonHeight;
 
         // first, draw the battery shape
-        c.drawRect(frame, mFramePaint);
+        c.drawRect(mFrame, mFramePaint);
 
         // fill 'er up
         final int pct = tracker.level;
@@ -271,15 +282,14 @@
             drawFrac = 0f;
         }
 
-        c.drawRect(buttonframe,
-                drawFrac == 1f ? mBatteryPaint : mFramePaint);
+        c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint);
 
-        RectF clip = new RectF(frame);
-        clip.top += (frame.height() * (1f - drawFrac));
+        mClipFrame.set(mFrame);
+        mClipFrame.top += (mFrame.height() * (1f - drawFrac));
 
         c.save(Canvas.CLIP_SAVE_FLAG);
-        c.clipRect(clip);
-        c.drawRect(frame, mBatteryPaint);
+        c.clipRect(mClipFrame);
+        c.drawRect(mFrame, mBatteryPaint);
         c.restore();
 
         if (level <= EMPTY) {
@@ -287,11 +297,28 @@
             final float y = (mHeight + mWarningTextHeight) * 0.48f;
             c.drawText(mWarningString, x, y, mWarningTextPaint);
         } else if (tracker.plugged) {
-            final Rect r = new Rect(
-                    (int)frame.left + width / 4,  (int)frame.top + height / 5,
-                    (int)frame.right - width / 4, (int)frame.bottom - height / 6);
-            mLightning.setBounds(r);
-            mLightning.draw(c);
+            // draw the bolt
+            final int bl = (int)(mFrame.left + width / 4f);
+            final int bt = (int)(mFrame.top + height / 6f);
+            final int br = (int)(mFrame.right - width / 5f);
+            final int bb = (int)(mFrame.bottom - height / 6f);
+            if (mBoltFrame.left != bl || mBoltFrame.top != bt
+                    || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+                mBoltFrame.set(bl, bt, br, bb);
+                mBoltPath.reset();
+                mBoltPath.moveTo(
+                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+                for (int i = 2; i < mBoltPoints.length; i += 2) {
+                    mBoltPath.lineTo(
+                            mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+                            mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+                }
+                mBoltPath.lineTo(
+                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+            }
+            c.drawPath(mBoltPath, mBoltPaint);
         } else if (mShowPercent && !(tracker.level == 100 && !SHOW_100_PERCENT)) {
             mTextPaint.setTextSize(height *
                     (SINGLE_DIGIT_PERCENT ? 0.75f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f8b6ca6..b263a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -28,7 +29,6 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.systemui.BatteryMeterView;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 
@@ -53,9 +53,7 @@
         private final int mTransparent;
         private final float mAlphaWhenOpaque;
         private final float mAlphaWhenTransparent = 1;
-        private View mLeftSide;
-        private View mRightSide;
-        private BatteryMeterView mBattery;
+        private View mLeftSide, mStatusIcons, mSignalCluster, mClock;
 
         public StatusBarTransitions(Context context) {
             super(context, PhoneStatusBarView.this);
@@ -66,8 +64,9 @@
 
         public void init() {
             mLeftSide = findViewById(R.id.notification_icon_area);
-            mRightSide = findViewById(R.id.system_icon_area);
-            mBattery = (BatteryMeterView) findViewById(R.id.battery);
+            mStatusIcons = findViewById(R.id.statusIcons);
+            mSignalCluster = findViewById(R.id.signal_battery_cluster);
+            mClock = findViewById(R.id.clock);
             applyMode(getMode(), false /*animate*/);
         }
 
@@ -96,17 +95,22 @@
         }
 
         private void applyMode(int mode, boolean animate) {
-            if (mLeftSide == null || mRightSide == null) return;
-            mBattery.setBarTransparent(isTransparent(mode));
+            if (mLeftSide == null) return; // pre-init
             float newAlpha = getAlphaFor(mode);
             if (animate) {
-                ObjectAnimator lhs = animateTransitionTo(mLeftSide, newAlpha);
-                lhs.start();
-                // TODO jspurlock - fix conflicting rhs animations on tablets
-                mRightSide.setAlpha(newAlpha);
+                AnimatorSet anims = new AnimatorSet();
+                anims.playTogether(
+                        animateTransitionTo(mLeftSide, newAlpha),
+                        animateTransitionTo(mStatusIcons, newAlpha),
+                        animateTransitionTo(mSignalCluster, newAlpha),
+                        animateTransitionTo(mClock, newAlpha)
+                        );
+                anims.start();
             } else {
                 mLeftSide.setAlpha(newAlpha);
-                mRightSide.setAlpha(newAlpha);
+                mStatusIcons.setAlpha(newAlpha);
+                mSignalCluster.setAlpha(newAlpha);
+                mClock.setAlpha(newAlpha);
             }
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 75cf5d0..1e3fb40 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1574,12 +1574,12 @@
             mSystemThread.installSystemApplicationInfo(info);
 
             synchronized (mSelf) {
-                ProcessRecord app = mSelf.newProcessRecordLocked(
-                        mSystemThread.getApplicationThread(), info,
+                ProcessRecord app = mSelf.newProcessRecordLocked(info,
                         info.processName, false);
                 app.persistent = true;
                 app.pid = MY_PID;
                 app.maxAdj = ProcessList.SYSTEM_ADJ;
+                app.makeActive(mSystemThread.getApplicationThread(), mSelf.mProcessStats);
                 mSelf.mProcessNames.put(app.processName, app.uid, app);
                 synchronized (mSelf.mPidsSelfLocked) {
                     mSelf.mPidsSelfLocked.put(app.pid, app);
@@ -2282,7 +2282,7 @@
         }
 
         if (app == null) {
-            app = newProcessRecordLocked(null, info, processName, isolated);
+            app = newProcessRecordLocked(info, processName, isolated);
             if (app == null) {
                 Slog.w(TAG, "Failed making new process record for "
                         + processName + "/" + info.uid + " isolated=" + isolated);
@@ -3628,6 +3628,7 @@
             info.append(" (").append(activity.shortComponentName).append(")");
         }
         info.append("\n");
+        info.append("PID: ").append(app.pid).append("\n");
         if (annotation != null) {
             info.append("Reason: ").append(annotation).append("\n");
         }
@@ -4487,7 +4488,7 @@
 
         EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
 
-        app.thread = thread;
+        app.makeActive(thread, mProcessStats);
         app.curAdj = app.setAdj = -100;
         app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
         app.forcingToForeground = null;
@@ -7544,8 +7545,8 @@
     // GLOBAL MANAGEMENT
     // =========================================================
 
-    final ProcessRecord newProcessRecordLocked(IApplicationThread thread,
-            ApplicationInfo info, String customProcess, boolean isolated) {
+    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
+            boolean isolated) {
         String proc = customProcess != null ? customProcess : info.processName;
         BatteryStatsImpl.Uid.Proc ps = null;
         BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
@@ -7573,8 +7574,7 @@
         synchronized (stats) {
             ps = stats.getProcessStatsLocked(info.uid, proc);
         }
-        return new ProcessRecord(ps, thread, info, proc, uid,
-                mProcessStats.getProcessStateLocked(info.packageName, info.uid, proc));
+        return new ProcessRecord(ps, info, proc, uid);
     }
 
     final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
@@ -7586,7 +7586,7 @@
         }
 
         if (app == null) {
-            app = newProcessRecordLocked(null, info, null, isolated);
+            app = newProcessRecordLocked(info, null, isolated);
             mProcessNames.put(info.processName, app.uid, app);
             if (isolated) {
                 mIsolatedProcesses.put(app.uid, app);
@@ -11788,7 +11788,7 @@
 
         app.resetPackageList(mProcessStats);
         app.unlinkDeathRecipient();
-        app.thread = null;
+        app.makeInactive(mProcessStats);
         app.forcingToForeground = null;
         app.foregroundServices = false;
         app.foregroundActivities = false;
@@ -14692,7 +14692,9 @@
     }
 
     private final void setProcessTrackerState(ProcessRecord proc, int memFactor, long now) {
-        proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+        if (proc.thread != null) {
+            proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+        }
     }
 
     private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 8d27c5c..2b76e71 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1110,8 +1110,10 @@
                                 case PAUSED:
                                     // This case created for transitioning activities from
                                     // translucent to opaque {@link Activity#convertToOpaque}.
-                                    mStackSupervisor.mStoppingActivities.remove(r);
-                                    stopActivityLocked(r);
+                                    if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+                                        mStackSupervisor.mStoppingActivities.add(r);
+                                    }
+                                    mStackSupervisor.scheduleIdleLocked();
                                     break;
 
                                 default:
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index f1a030e..283d122 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -52,13 +52,13 @@
     final int uid;              // uid of process; may be different from 'info' if isolated
     final int userId;           // user of process.
     final String processName;   // name of the process
-    final ProcessStats.ProcessState baseProcessTracker;
     // List of packages running in the process
     final ArrayMap<String, ProcessStats.ProcessState> pkgList
             = new ArrayMap<String, ProcessStats.ProcessState>();
     IApplicationThread thread;  // the actual proc...  may be null only if
                                 // 'persistent' is true (in which case we
                                 // are in the process of launching the app)
+    ProcessStats.ProcessState baseProcessTracker;
     int pid;                    // The process of this application; 0 if none
     boolean starting;           // True if the process is being started
     long lastActivityTime;      // For managing the LRU list
@@ -349,18 +349,15 @@
         }
     }
     
-    ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread,
-            ApplicationInfo _info, String _processName, int _uid,
-            ProcessStats.ProcessState tracker) {
+    ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info,
+            String _processName, int _uid) {
         batteryStats = _batteryStats;
         info = _info;
         isolated = _info.uid != _uid;
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        baseProcessTracker = tracker;
-        pkgList.put(_info.packageName, tracker);
-        thread = _thread;
+        pkgList.put(_info.packageName, null);
         maxAdj = ProcessList.UNKNOWN_ADJ;
         curRawAdj = setRawAdj = -100;
         curAdj = setAdj = -100;
@@ -374,7 +371,53 @@
         shortStringName = null;
         stringName = null;
     }
-    
+
+    public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
+        if (thread == null) {
+            final ProcessStats.ProcessState origBase = baseProcessTracker;
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
+                    processName);
+            baseProcessTracker.makeActive();
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName);
+                if (ps != baseProcessTracker) {
+                    ps.makeActive();
+                }
+                pkgList.setValueAt(i, ps);
+            }
+        }
+        thread = _thread;
+    }
+
+    public void makeInactive(ProcessStatsService tracker) {
+        if (thread != null) {
+            thread = null;
+            final ProcessStats.ProcessState origBase = baseProcessTracker;
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = null;
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                pkgList.setValueAt(i, null);
+            }
+        }
+    }
+
     /**
      * This method returns true if any of the activities within the process record are interesting
      * to the user. See HistoryRecord.isInterestingToUserLocked()
@@ -518,10 +561,22 @@
         long now = SystemClock.uptimeMillis();
         baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
                 tracker.getMemFactorLocked(), now, pkgList);
-        if (pkgList.size() != 1) {
+        final int N = pkgList.size();
+        if (N != 1) {
+            for (int i=0; i<N; i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != baseProcessTracker) {
+                    ps.makeInactive();
+                }
+
+            }
             pkgList.clear();
-            pkgList.put(info.packageName, tracker.getProcessStateLocked(
-                    info.packageName, info.uid, processName));
+            ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
+                    info.packageName, info.uid, processName);
+            pkgList.put(info.packageName, ps);
+            if (thread != null && ps != baseProcessTracker) {
+                ps.makeActive();
+            }
         }
     }
     
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index 43ae46f..c180f6e 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -54,12 +54,12 @@
     // exists in and the offset into the array to find it.  The constants below
     // define the encoding of that data in an integer.
 
-    static final int MAX_HISTORIC_STATES = 4;   // Maximum number of historic states we will keep.
+    static final int MAX_HISTORIC_STATES = 6;   // Maximum number of historic states we will keep.
     static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
     static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
     static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
-    static long COMMIT_PERIOD = 24*60*60*1000;  // Commit current stats every day.
+    static long COMMIT_PERIOD = 12*60*60*1000;  // Commit current stats every 12 hours.
 
     final ActivityManagerService mAm;
     final File mBaseDir;
@@ -132,7 +132,7 @@
                     ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
                     for (int k=0; k<services.size(); k++) {
                         ProcessStats.ServiceState service = services.valueAt(k);
-                        if (service.isActive()) {
+                        if (service.isInUse()) {
                             if (service.mStartedState != ProcessStats.STATE_NOTHING) {
                                 service.setStarted(true, memFactor, now);
                             }
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 39756c3..8293bb8 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -318,6 +318,7 @@
         if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
             tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
                     serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+            tracker.makeActive();
         }
         return tracker;
     }
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index 2563b58..926f822 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -41,6 +41,8 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -298,6 +300,27 @@
         }
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump PrintManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("PRINT MANAGER STATE (dumpsys print)");
+            final int userStateCount = mUserStates.size();
+            for (int i = 0; i < userStateCount; i++) {
+                UserState userState = mUserStates.get(i);
+                userState.dump(fd, pw, "");
+                pw.println();
+            }
+        }
+    }
+
     private void registerContentObservers() {
         final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
                 Settings.Secure.ENABLED_PRINT_SERVICES);
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 2ded202..ddff0ae 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.R;
 
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -137,6 +138,19 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
 
+    public void dump(PrintWriter pw, String prefix) {
+        String tab = "  ";
+        pw.append(prefix).append("service:").println();
+        pw.append(prefix).append(tab).append("componentName=")
+                .append(mComponentName.flattenToString()).println();
+        pw.append(prefix).append(tab).append("destroyed=")
+                .append(String.valueOf(mDestroyed)).println();
+        pw.append(prefix).append(tab).append("bound=")
+                .append(String.valueOf(isBound())).println();
+        pw.append(prefix).append(tab).append("hasDicoverySession=")
+                .append(String.valueOf(mHasPrinterDiscoverySession));
+    }
+
     private void failAllActivePrintJobs() {
         List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(mComponentName,
                 PrintJobInfo.STATE_ANY_ACTIVE, PrintManager.APP_ID_ANY);
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index db0eb33..28a6186 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -34,11 +34,14 @@
 import android.print.IPrintSpoolerClient;
 import android.print.PrintAttributes;
 import android.print.PrintJobInfo;
+import android.print.PrintManager;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
 import libcore.io.IoUtils;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -291,6 +294,28 @@
         }
     }
 
+    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.append(prefix).append("destroyed=")
+                    .append(String.valueOf(mDestroyed)).println();
+            pw.append(prefix).append("bound=")
+                    .append((mRemoteInstance != null) ? "true" : "false").println();
+            pw.append(prefix).append("print jobs:").println();
+            if (mRemoteInstance != null) {
+                List<PrintJobInfo> printJobs = getPrintJobInfos(null,
+                        PrintJobInfo.STATE_ANY, PrintManager.APP_ID_ANY);
+                if (printJobs != null) {
+                    final int printJobCount = printJobs.size();
+                    for (int i = 0; i < printJobCount; i++) {
+                        PrintJobInfo printJob = printJobs.get(i);
+                        pw.append(prefix).append(prefix).append(printJob.toString());
+                        pw.println();
+                    }
+                }
+            }
+        }
+    }
+
     private void onAllPrintJobsHandled() {
         synchronized (mLock) {
             throwIfDestroyedLocked();
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index b37a0d9..5392975 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -39,14 +39,16 @@
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.os.SomeArgs;
 import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -72,14 +74,14 @@
     private final Intent mQueryIntent =
             new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
 
-    private final Map<ComponentName, RemotePrintService> mActiveServices =
-            new HashMap<ComponentName, RemotePrintService>();
+    private final ArrayMap<ComponentName, RemotePrintService> mActiveServices =
+            new ArrayMap<ComponentName, RemotePrintService>();
 
     private final List<PrintServiceInfo> mInstalledServices =
             new ArrayList<PrintServiceInfo>();
 
     private final Set<ComponentName> mEnabledServices =
-            new HashSet<ComponentName>();
+            new ArraySet<ComponentName>();
 
     private final Object mLock;
 
@@ -318,6 +320,57 @@
         mDestroyed = true;
     }
 
+    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+        pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":");
+        pw.println();
+
+        String tab = "  ";
+
+        pw.append(prefix).append(tab).append("installed services:").println();
+        final int installedServiceCount = mInstalledServices.size();
+        for (int i = 0; i < installedServiceCount; i++) {
+            PrintServiceInfo installedService = mInstalledServices.get(i);
+            String installedServicePrefix = prefix + tab + tab;
+            pw.append(installedServicePrefix).append("service:").println();
+            ResolveInfo resolveInfo = installedService.getResolveInfo();
+            ComponentName componentName = new ComponentName(
+                    resolveInfo.serviceInfo.packageName,
+                    resolveInfo.serviceInfo.name);
+            pw.append(installedServicePrefix).append(tab).append("componentName=")
+                    .append(componentName.flattenToString()).println();
+            pw.append(installedServicePrefix).append(tab).append("settingsActivity=")
+                    .append(installedService.getSettingsActivityName()).println();
+            pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=")
+                    .append(installedService.getAddPrintersActivityName()).println();
+        }
+
+        pw.append(prefix).append(tab).append("enabled services:").println();
+        for (ComponentName enabledService : mEnabledServices) {
+            String enabledServicePrefix = prefix + tab + tab;
+            pw.append(enabledServicePrefix).append("service:").println();
+            pw.append(enabledServicePrefix).append(tab).append("componentName=")
+                    .append(enabledService.flattenToString());
+            pw.println();
+        }
+
+        pw.append(prefix).append(tab).append("active services:").println();
+        final int activeServiceCount = mActiveServices.size();
+        for (int i = 0; i < activeServiceCount; i++) {
+            RemotePrintService activeService = mActiveServices.valueAt(i);
+            activeService.dump(pw, prefix + tab + tab);
+            pw.println();
+        }
+
+        pw.append(prefix).append(tab).append("discovery mediator:").println();
+        if (mPrinterDiscoverySession != null) {
+            mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
+        }
+
+        pw.append(prefix).append(tab).append("print spooler:").println();
+        mSpooler.dump(fd, pw, prefix + tab + tab);
+        pw.println();
+    }
+
     private boolean readConfigurationLocked() {
         boolean somethingChanged = false;
         somethingChanged |= readInstalledPrintServicesLocked();
@@ -814,6 +867,47 @@
             }
         }
 
+        public void dump(PrintWriter pw, String prefix) {
+            pw.append(prefix).append("destroyed=")
+                    .append(String.valueOf(mDestroyed)).println();
+
+            pw.append(prefix).append("printDiscoveryInProgress=")
+                    .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println();
+
+            String tab = "  ";
+
+            pw.append(prefix).append(tab).append("printer discovery observers:").println();
+            final int observerCount = mDiscoveryObservers.beginBroadcast();
+            for (int i = 0; i < observerCount; i++) {
+                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
+                pw.append(prefix).append(prefix).append(observer.toString());
+                pw.println();
+            }
+            mDiscoveryObservers.finishBroadcast();
+
+            pw.append(prefix).append(tab).append("start discovery requests:").println();
+            final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
+            for (int i = 0; i < tokenCount; i++) {
+                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
+                pw.append(prefix).append(tab).append(tab).append(token.toString()).println();
+            }
+
+            pw.append(prefix).append(tab).append("tracked printer requests:").println();
+            final int trackedPrinters = mStateTrackedPrinters.size();
+            for (int i = 0; i < trackedPrinters; i++) {
+                PrinterId printer = mStateTrackedPrinters.get(i);
+                pw.append(prefix).append(tab).append(tab).append(printer.toString()).println();
+            }
+
+            pw.append(prefix).append(tab).append("printers:").println();
+            final int pritnerCount = mPrinters.size();
+            for (int i = 0; i < pritnerCount; i++) {
+                PrinterInfo printer = mPrinters.valueAt(i);
+                pw.append(prefix).append(tab).append(tab).append(
+                        printer.toString()).println();
+            }
+        }
+
         private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) {
             final int observerCount = mDiscoveryObservers.beginBroadcast();
             for (int i = 0; i < observerCount; i++) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 5256b58..04ce9d0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -24,10 +24,13 @@
 
 import android.content.res.BridgeResources.NinePatchInputStream;
 import android.graphics.BitmapFactory.Options;
+import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.EnumSet;
+import java.util.Set;
 
 /**
  * Delegate implementing the native methods of android.graphics.BitmapFactory
@@ -98,8 +101,12 @@
         //TODO support rescaling
 
         Density density = Density.MEDIUM;
+        Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE);
         if (opts != null) {
             density = Density.getEnum(opts.inDensity);
+            if (opts.inPremultiplied) {
+                bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
+            }
         }
 
         try {
@@ -112,7 +119,7 @@
                         npis, true /*is9Patch*/, false /*convert*/);
 
                 // get the bitmap and chunk objects.
-                bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), true /*isMutable*/,
+                bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), bitmapCreateFlags,
                         density);
                 NinePatchChunk chunk = ninePatch.getChunk();
 
@@ -127,7 +134,7 @@
                 padding.bottom = paddingarray[3];
             } else {
                 // load the bitmap directly.
-                bm = Bitmap_Delegate.createBitmap(is, true, density);
+                bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density);
             }
         } catch (IOException e) {
             Bridge.getLog().error(null,"Failed to load image" , e, null);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 4121f79..ec284ac 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -33,6 +33,8 @@
 import java.io.OutputStream;
 import java.nio.Buffer;
 import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
 
 import javax.imageio.ImageIO;
 
@@ -51,6 +53,10 @@
  */
 public final class Bitmap_Delegate {
 
+    public enum BitmapCreateFlags {
+        PREMULTIPLIED, MUTABLE
+    }
+
     // ---- delegate manager ----
     private static final DelegateManager<Bitmap_Delegate> sManager =
             new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
@@ -93,10 +99,25 @@
      */
     public static Bitmap createBitmap(File input, boolean isMutable, Density density)
             throws IOException {
+        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given file content.
+     *
+     * @param input the file from which to read the bitmap content
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+            Density density) throws IOException {
         // create a delegate with the content of the file.
         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
 
-        return createBitmap(delegate, isMutable, density.getDpiValue());
+        return createBitmap(delegate, createFlags, density.getDpiValue());
     }
 
     /**
@@ -111,10 +132,26 @@
      */
     public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
             throws IOException {
+        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given stream content.
+     *
+     * @param input the stream from which to read the bitmap content
+     * @param createFlags
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
+            Density density) throws IOException {
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
 
-        return createBitmap(delegate, isMutable, density.getDpiValue());
+        return createBitmap(delegate, createFlags, density.getDpiValue());
     }
 
     /**
@@ -129,10 +166,26 @@
      */
     public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
             Density density) throws IOException {
+        return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+     *
+     * @param image the bitmap content
+     * @param createFlags
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
+            Density density) throws IOException {
         // create a delegate with the given image.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
 
-        return createBitmap(delegate, isMutable, density.getDpiValue());
+        return createBitmap(delegate, createFlags, density.getDpiValue());
     }
 
     /**
@@ -203,7 +256,7 @@
 
     @LayoutlibDelegate
     /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
-            int height, int nativeConfig, boolean mutable) {
+            int height, int nativeConfig, boolean isMutable) {
         int imageType = getBufferedImageType(nativeConfig);
 
         // create the image
@@ -216,7 +269,8 @@
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
 
-        return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
+        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+                            Bitmap.getDefaultDensity());
     }
 
     @LayoutlibDelegate
@@ -244,7 +298,8 @@
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
 
-        return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity());
+        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+                Bitmap.getDefaultDensity());
     }
 
     @LayoutlibDelegate
@@ -464,7 +519,7 @@
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
 
         // the density doesn't matter, it's set by the Java method.
-        return createBitmap(delegate, false /*isMutable*/,
+        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
                 Density.DEFAULT_DENSITY /*density*/);
     }
 
@@ -546,15 +601,27 @@
         mConfig = config;
     }
 
-    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
+    private static Bitmap createBitmap(Bitmap_Delegate delegate,
+            Set<BitmapCreateFlags> createFlags, int density) {
         // get its native_int
         int nativeInt = sManager.addNewDelegate(delegate);
 
+        int width = delegate.mImage.getWidth();
+        int height = delegate.mImage.getHeight();
+        boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
+        boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
+
         // and create/return a new Bitmap with it
-        // TODO: pass correct width, height, isPremultiplied
-        return new Bitmap(nativeInt, null /* buffer */, -1 /* width */, -1 /* height */, density,
-                          isMutable, true /* isPremultiplied */,
-                          null /*ninePatchChunk*/, null /* layoutBounds */);
+        return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
+                          isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
+    }
+
+    private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
+        Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
+        if (isMutable) {
+            createFlags.add(BitmapCreateFlags.MUTABLE);
+        }
+        return createFlags;
     }
 
     /**