Merge "Update the comment of Settings.ACTION_USER_DICTIONARY_SETTINGS"
diff --git a/api/current.txt b/api/current.txt
index 8daa592..6c41433 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -291,6 +291,7 @@
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
     field public static final int autoLink = 16842928; // 0x10100b0
+    field public static final int autoMirrored = 16843752; // 0x10103e8
     field public static final int autoStart = 16843445; // 0x10102b5
     field public static final deprecated int autoText = 16843114; // 0x101016a
     field public static final int autoUrlDetect = 16843404; // 0x101028c
@@ -387,6 +388,7 @@
     field public static final int cropToPadding = 16843043; // 0x1010123
     field public static final int cursorVisible = 16843090; // 0x1010152
     field public static final int customNavigationLayout = 16843474; // 0x10102d2
+    field public static final int customRoots = 16843751; // 0x10103e7
     field public static final int customTokens = 16843579; // 0x101033b
     field public static final int cycles = 16843220; // 0x10101d4
     field public static final int dashGap = 16843175; // 0x10101a7
@@ -605,6 +607,7 @@
     field public static final int installLocation = 16843447; // 0x10102b7
     field public static final int interpolator = 16843073; // 0x1010141
     field public static final int isAlwaysSyncable = 16843571; // 0x1010333
+    field public static final int isAsciiCapable = 16843750; // 0x10103e6
     field public static final int isAuxiliary = 16843647; // 0x101037f
     field public static final int isDefault = 16843297; // 0x1010221
     field public static final int isIndicator = 16843079; // 0x1010147
@@ -3162,6 +3165,7 @@
     field public static final int OP_COARSE_LOCATION = 0; // 0x0
     field public static final int OP_FINE_LOCATION = 1; // 0x1
     field public static final int OP_GPS = 2; // 0x2
+    field public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42; // 0x2a
     field public static final int OP_MONITOR_LOCATION = 41; // 0x29
     field public static final int OP_NONE = -1; // 0xffffffff
   }
@@ -7623,7 +7627,7 @@
     method public void recycle();
   }
 
-  public abstract interface XmlResourceParser implements android.util.AttributeSet org.xmlpull.v1.XmlPullParser {
+  public abstract interface XmlResourceParser implements android.util.AttributeSet java.lang.AutoCloseable org.xmlpull.v1.XmlPullParser {
     method public abstract void close();
   }
 
@@ -9342,6 +9346,7 @@
     field public static final int ANTI_ALIAS_FLAG = 1; // 0x1
     field public static final int DEV_KERN_TEXT_FLAG = 256; // 0x100
     field public static final int DITHER_FLAG = 4; // 0x4
+    field public static final int EMBEDDED_BITMAP_TEXT_FLAG = 1024; // 0x400
     field public static final int FAKE_BOLD_TEXT_FLAG = 32; // 0x20
     field public static final int FILTER_BITMAP_FLAG = 2; // 0x2
     field public static final int HINTING_OFF = 0; // 0x0
@@ -9887,6 +9892,7 @@
     method public android.graphics.Shader.TileMode getTileModeY();
     method public boolean hasAntiAlias();
     method public boolean hasMipMap();
+    method public final boolean isAutoMirrored();
     method public void setAlpha(int);
     method public void setAntiAlias(boolean);
     method public void setColorFilter(android.graphics.ColorFilter);
@@ -9953,6 +9959,7 @@
     method public android.graphics.Region getTransparentRegion();
     method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public void invalidateSelf();
+    method public boolean isAutoMirrored();
     method public boolean isStateful();
     method public final boolean isVisible();
     method public void jumpToCurrentState();
@@ -9963,6 +9970,7 @@
     method public static int resolveOpacity(int, int);
     method public void scheduleSelf(java.lang.Runnable, long);
     method public abstract void setAlpha(int);
+    method public void setAutoMirrored(boolean);
     method public void setBounds(int, int, int, int);
     method public void setBounds(android.graphics.Rect);
     method public final void setCallback(android.graphics.drawable.Drawable.Callback);
@@ -11649,6 +11657,7 @@
     field public static final int ADJUST_SAME = 0; // 0x0
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+    field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
     field public static final int AUDIOFOCUS_LOSS = -1; // 0xffffffff
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe
@@ -18352,6 +18361,7 @@
     method public boolean hasHeaders();
     method public void invalidateHeaders();
     method public boolean isMultiPane();
+    method protected boolean isValidFragment(java.lang.String);
     method public void loadHeadersFromResource(int, java.util.List<android.preference.PreferenceActivity.Header>);
     method public void onBuildHeaders(java.util.List<android.preference.PreferenceActivity.Header>);
     method public android.content.Intent onBuildStartFragmentIntent(java.lang.String, android.os.Bundle, int, int);
@@ -18542,7 +18552,6 @@
     method public void clear();
     method public int describeContents();
     method public int getColorMode();
-    method public int getCopies();
     method public int getDuplexMode();
     method public int getFittingMode();
     method public android.print.PrintAttributes.Tray getInputTray();
@@ -18568,7 +18577,6 @@
     ctor public PrintAttributes.Builder();
     method public android.print.PrintAttributes create();
     method public android.print.PrintAttributes.Builder setColorMode(int);
-    method public android.print.PrintAttributes.Builder setCopyCount(int);
     method public android.print.PrintAttributes.Builder setDuplexMode(int);
     method public android.print.PrintAttributes.Builder setFittingMode(int);
     method public android.print.PrintAttributes.Builder setInputTray(android.print.PrintAttributes.Tray);
@@ -18654,18 +18662,20 @@
     method public void onFinish();
     method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle);
     method public void onStart();
-    method public abstract void onWrite(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
+    method public abstract void onWrite(android.print.PageRange[], java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
     field public static final java.lang.String METADATA_KEY_PRINT_PREVIEW = "KEY_METADATA_PRINT_PREVIEW";
   }
 
   public static abstract class PrintDocumentAdapter.LayoutResultCallback {
+    method public void onLayoutCancelled();
     method public void onLayoutFailed(java.lang.CharSequence);
     method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean);
   }
 
   public static abstract class PrintDocumentAdapter.WriteResultCallback {
+    method public void onWriteCancelled();
     method public void onWriteFailed(java.lang.CharSequence);
-    method public void onWriteFinished(java.util.List<android.print.PageRange>);
+    method public void onWriteFinished(android.print.PageRange[]);
   }
 
   public final class PrintDocumentInfo implements android.os.Parcelable {
@@ -18696,6 +18706,7 @@
   public final class PrintJobInfo implements android.os.Parcelable {
     method public int describeContents();
     method public android.print.PrintAttributes getAttributes();
+    method public int getCopies();
     method public int getId();
     method public java.lang.CharSequence getLabel();
     method public android.print.PageRange[] getPages();
@@ -18740,6 +18751,7 @@
     method public java.util.List<android.print.PrintAttributes.Tray> getOutputTrays();
     method public java.util.List<android.print.PrintAttributes.Resolution> getResolutions();
     method public int getStatus();
+    method public boolean hasAllRequiredAttributes();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int STATUS_READY = 1; // 0x1
@@ -18825,9 +18837,11 @@
     method protected void onDisconnected();
     method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
     method protected void onRequestCancelPrintJob(android.printservice.PrintJob);
+    method protected void onRequestUpdatePrinters(java.util.List<android.print.PrinterId>);
     method protected abstract void onStartPrinterDiscovery();
     method protected abstract void onStopPrinterDiscovery();
     method public final void removeDiscoveredPrinters(java.util.List<android.print.PrinterId>);
+    method public final void updateDiscoveredPrinters(java.util.List<android.print.PrinterInfo>);
     field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
     field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
   }
@@ -25128,6 +25142,13 @@
     method public android.util.JsonWriter value(java.lang.Number) throws java.io.IOException;
   }
 
+  public abstract interface LayoutDirection {
+    field public static final int INHERIT = 2; // 0x2
+    field public static final int LOCALE = 3; // 0x3
+    field public static final int LTR = 0; // 0x0
+    field public static final int RTL = 1; // 0x1
+  }
+
   public final class Log {
     method public static int d(java.lang.String, java.lang.String);
     method public static int d(java.lang.String, java.lang.String, java.lang.Throwable);
@@ -28904,8 +28925,8 @@
   }
 
   public final class InputMethodSubtype implements android.os.Parcelable {
-    ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
-    ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int);
+    ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
+    ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int);
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
@@ -28915,12 +28936,27 @@
     method public java.lang.String getLocale();
     method public java.lang.String getMode();
     method public int getNameResId();
+    method public boolean isAsciiCapable();
     method public boolean isAuxiliary();
     method public boolean overridesImplicitlyEnabledSubtype();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
+  public static class InputMethodSubtype.InputMethodSubtypeBuilder {
+    ctor public InputMethodSubtype.InputMethodSubtypeBuilder();
+    method public android.view.inputmethod.InputMethodSubtype build();
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeLocale(java.lang.String);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeMode(java.lang.String);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
+  }
+
 }
 
 package android.view.textservice {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8d47236..0f988ed 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -67,6 +67,7 @@
     //  - increment _NUM_OP
     //  - add rows to sOpToSwitch, sOpNames, sOpPerms
     //  - add descriptive strings to Settings/res/values/arrays.xml
+    //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
 
     /** No operation specified. */
     public static final int OP_NONE = -1;
@@ -154,15 +155,17 @@
     public static final int OP_WAKE_LOCK = 40;
     /** Continually monitoring location data. */
     public static final int OP_MONITOR_LOCATION = 41;
+    /** Continually monitoring location data with a relatively high power request. */
+    public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42;
     /** @hide */
-    public static final int _NUM_OP = 42;
+    public static final int _NUM_OP = 43;
 
     /**
      * This maps each operation to the operation that serves as the
      * switch to determine whether it is allowed.  Generally this is
      * a 1:1 mapping, but for some things (like location) that have
      * multiple low-level operations being tracked that should be
-     * presented to hte user as one switch then this can be used to
+     * presented to the user as one switch then this can be used to
      * make them all controlled by the same single operation.
      */
     private static int[] sOpToSwitch = new int[] {
@@ -208,6 +211,7 @@
             OP_AUDIO_BLUETOOTH_VOLUME,
             OP_WAKE_LOCK,
             OP_COARSE_LOCATION,
+            OP_COARSE_LOCATION,
     };
 
     /**
@@ -257,6 +261,7 @@
             "AUDIO_BLUETOOTH_VOLUME",
             "WAKE_LOCK",
             "MONITOR_LOCATION",
+            "MONITOR_HIGH_POWER_LOCATION",
     };
 
     /**
@@ -306,6 +311,7 @@
             null, // no permission for changing bluetooth volume
             android.Manifest.permission.WAKE_LOCK,
             null, // no permission for generic location monitoring
+            null, // no permission for high power location monitoring
     };
 
     /**
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6933a7a..f8a1d82 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -580,6 +580,10 @@
             if (clazz == null) {
                 // Class not found in the cache, see if it's real, and try to add it
                 clazz = context.getClassLoader().loadClass(fname);
+                if (!Fragment.class.isAssignableFrom(clazz)) {
+                    throw new InstantiationException("Trying to instantiate a class " + fname
+                            + " that is not a Fragment", new ClassCastException());
+                }
                 sClassMap.put(fname, clazz);
             }
             Fragment f = (Fragment)clazz.newInstance();
diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java
index c59e6d4..5af49d4 100644
--- a/core/java/android/content/res/XmlResourceParser.java
+++ b/core/java/android/content/res/XmlResourceParser.java
@@ -26,7 +26,7 @@
  * an additional close() method on this interface for the client to indicate
  * when it is done reading the resource.
  */
-public interface XmlResourceParser extends XmlPullParser, AttributeSet {
+public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
     /**
      * Close this interface to the resource.  Calls on the interface are no
      * longer value after this call.
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 842a482..220b40d 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -97,7 +97,9 @@
     }
 
     /*
-     * Handle introductory packet.
+     * Handle introductory packet. This is called during JNI_CreateJavaVM
+     * before frameworks native methods are registered, so be careful not
+     * to call any APIs that depend on frameworks native code.
      */
     private Chunk handleHELO(Chunk request) {
         if (false)
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 0f7c930..1370654 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -22,7 +22,6 @@
 import android.hardware.IProCameraUser;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -279,7 +278,7 @@
 
     // TODO: this class needs unit tests
     // TODO: extract class into top level
-    private class CameraServiceListener extends Binder implements ICameraServiceListener  {
+    private class CameraServiceListener extends ICameraServiceListener.Stub {
 
         // Keep up-to-date with ICameraServiceListener.h
 
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 9f569cb..4172238 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -25,6 +25,6 @@
      * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
      */
 
-    void notifyCallback(int msgType, int ext1, int ext2);
-    void onResultReceived(int frameId, in CameraMetadata result);
+    oneway void notifyCallback(int msgType, int ext1, int ext2);
+    oneway void onResultReceived(int frameId, in CameraMetadata result);
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 9587680..fa79051 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -24,7 +24,6 @@
 import android.hardware.camera2.CameraProperties;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -40,6 +39,7 @@
 public class CameraDevice implements android.hardware.camera2.CameraDevice {
 
     private final String TAG;
+    private final boolean DEBUG;
 
     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
     private ICameraDeviceUser mRemoteDevice;
@@ -59,6 +59,7 @@
     public CameraDevice(String cameraId) {
         mCameraId = cameraId;
         TAG = String.format("CameraDevice-%s-JV", mCameraId);
+        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     }
 
     public CameraDeviceCallbacks getCallbacks() {
@@ -288,7 +289,7 @@
     }
 
     // TODO: unit tests
-    public class CameraDeviceCallbacks extends Binder implements ICameraDeviceCallbacks {
+    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
 
         @Override
         public IBinder asBinder() {
@@ -298,14 +299,17 @@
         // TODO: consider rename to onMessageReceived
         @Override
         public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
-            Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+            if (DEBUG) {
+                Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+            }
             // TODO implement rest
         }
 
         @Override
-        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
-            Log.d(TAG, "Received result for frameId " + frameId);
-
+        public void onResultReceived(int requestId, CameraMetadata result) throws RemoteException {
+            if (DEBUG) {
+                Log.d(TAG, "Received result for id " + requestId);
+            }
             CaptureListenerHolder holder;
 
             synchronized (mLock) {
@@ -313,7 +317,7 @@
                 //        exposing the methods necessary like subscribeToRequest, unsubscribe..
                 // TODO: make class static class
 
-                holder = CameraDevice.this.mCaptureListenerMap.get(frameId);
+                holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
 
                 // Clean up listener once we no longer expect to see it.
 
@@ -321,7 +325,7 @@
                 // we probably want cancelRequest to return # of times it already enqueued and
                 // keep a counter.
                 if (holder != null && !holder.isRepeating()) {
-                    CameraDevice.this.mCaptureListenerMap.remove(frameId);
+                    CameraDevice.this.mCaptureListenerMap.remove(requestId);
                 }
             }
 
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 75f8b59..6ab810c 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -128,6 +128,9 @@
         return interfaceNames;
     }
 
+    /**
+     * Returns all the addresses on this link.
+     */
     public Collection<InetAddress> getAddresses() {
         Collection<InetAddress> addresses = new ArrayList<InetAddress>();
         for (LinkAddress linkAddress : mLinkAddresses) {
@@ -136,14 +139,43 @@
         return Collections.unmodifiableCollection(addresses);
     }
 
+    /**
+     * Returns all the addresses on this link and all the links stacked above it.
+     */
+    public Collection<InetAddress> getAllAddresses() {
+        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+        for (LinkAddress linkAddress : mLinkAddresses) {
+            addresses.add(linkAddress.getAddress());
+        }
+        for (LinkProperties stacked: mStackedLinks.values()) {
+            addresses.addAll(stacked.getAllAddresses());
+        }
+        return addresses;
+    }
+
     public void addLinkAddress(LinkAddress address) {
         if (address != null) mLinkAddresses.add(address);
     }
 
+    /**
+     * Returns all the addresses on this link.
+     */
     public Collection<LinkAddress> getLinkAddresses() {
         return Collections.unmodifiableCollection(mLinkAddresses);
     }
 
+    /**
+     * Returns all the addresses on this link and all the links stacked above it.
+     */
+    public Collection<LinkAddress> getAllLinkAddresses() {
+        Collection<LinkAddress> addresses = new ArrayList<LinkAddress>();
+        addresses.addAll(mLinkAddresses);
+        for (LinkProperties stacked: mStackedLinks.values()) {
+            addresses.addAll(stacked.getAllLinkAddresses());
+        }
+        return addresses;
+    }
+
     public void addDns(InetAddress dns) {
         if (dns != null) mDnses.add(dns);
     }
@@ -426,13 +458,11 @@
     }
 
     /**
-     * Return two lists, a list of addresses that would be removed from
-     * mLinkAddresses and a list of addresses that would be added to
-     * mLinkAddress which would then result in target and mLinkAddresses
-     * being the same list.
+     * Compares the addresses in this LinkProperties with another
+     * LinkProperties, examining only addresses on the base link.
      *
-     * @param target is a LinkProperties with the new list of addresses
-     * @return the removed and added lists.
+     * @param target a LinkProperties with the new list of addresses
+     * @return the differences between the addresses.
      */
     public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
         /*
@@ -456,13 +486,11 @@
     }
 
     /**
-     * Return two lists, a list of dns addresses that would be removed from
-     * mDnses and a list of addresses that would be added to
-     * mDnses which would then result in target and mDnses
-     * being the same list.
+     * Compares the DNS addresses in this LinkProperties with another
+     * LinkProperties, examining only DNS addresses on the base link.
      *
-     * @param target is a LinkProperties with the new list of dns addresses
-     * @return the removed and added lists.
+     * @param target a LinkProperties with the new list of dns addresses
+     * @return the differences between the DNS addresses.
      */
     public CompareResult<InetAddress> compareDnses(LinkProperties target) {
         /*
@@ -487,15 +515,13 @@
     }
 
     /**
-     * Return two lists, a list of routes that would be removed from
-     * mRoutes and a list of routes that would be added to
-     * mRoutes which would then result in target and mRoutes
-     * being the same list.
+     * Compares all routes in this LinkProperties with another LinkProperties,
+     * examining both the the base link and all stacked links.
      *
-     * @param target is a LinkProperties with the new list of routes
-     * @return the removed and added lists.
+     * @param target a LinkProperties with the new list of routes
+     * @return the differences between the routes.
      */
-    public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
+    public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
         /*
          * Duplicate the RouteInfos into removed, we will be removing
          * routes which are common between mRoutes and target
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index e8c6daf..54273ee 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -52,7 +52,7 @@
 
     private static final String TAG = "MobileDataStateTracker";
     private static final boolean DBG = true;
-    private static final boolean VDBG = true;
+    private static final boolean VDBG = false;
 
     private PhoneConstants.DataState mMobileDataState;
     private ITelephony mPhoneService;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1d482dc..ab0543d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -27,6 +27,8 @@
 import java.io.OutputStreamWriter;
 import java.util.ArrayList;
 
+import libcore.io.Libcore;
+
 /*package*/ class ZygoteStartFailedEx extends Exception {
     /**
      * Something prevented the zygote process startup from happening normally
@@ -647,19 +649,25 @@
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
-    public static final native int myPid();
+    public static final int myPid() {
+        return Libcore.os.getpid();
+    }
 
     /**
      * Returns the identifier of this process' parent.
      * @hide
      */
-    public static native int myPpid();
+    public static final int myPpid() {
+        return Libcore.os.getppid();
+    }
 
     /**
      * Returns the identifier of the calling thread, which be used with
      * {@link #setThreadPriority(int, int)}.
      */
-    public static final native int myTid();
+    public static final int myTid() {
+        return Libcore.os.gettid();
+    }
 
     /**
      * Returns the identifier of this process's uid.  This is the kernel uid
@@ -667,7 +675,9 @@
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
-    public static final native int myUid();
+    public static final int myUid() {
+        return Libcore.os.getuid();
+    }
 
     /**
      * Returns this process's user handle.  This is the
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index ec97efb..3e387d7 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -33,6 +33,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.LayoutInflater;
@@ -124,6 +125,8 @@
         PreferenceManager.OnPreferenceTreeClickListener,
         PreferenceFragment.OnPreferenceStartFragmentCallback {
 
+    private static final String TAG = "PreferenceActivity";
+
     // Constants for state save/restore
     private static final String HEADERS_TAG = ":android:headers";
     private static final String CUR_HEADER_TAG = ":android:cur_header";
@@ -132,6 +135,9 @@
     /**
      * When starting this activity, the invoking Intent can contain this extra
      * string to specify which fragment should be initially displayed.
+     * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
+     * will call isValidFragment() to confirm that the fragment class name is valid for this
+     * activity.
      */
     public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
 
@@ -299,7 +305,7 @@
      * are valid.
      */
     public static final long HEADER_ID_UNDEFINED = -1;
-    
+
     /**
      * Description of a single Header item that the user can select.
      */
@@ -877,7 +883,27 @@
         } finally {
             if (parser != null) parser.close();
         }
+    }
 
+    /**
+     * Subclasses should override this method and verify that the given fragment is a valid type
+     * to be attached to this activity. The default implementation returns <code>true</code> prior
+     * to Key Lime Pie, <code>false</code> otherwise.
+     * @param fragmentName the class name of the Fragment about to be attached to this activity.
+     * @return true if the fragment class name is valid for this Activity and false otherwise.
+     */
+    protected boolean isValidFragment(String fragmentName) {
+        if (getApplicationInfo().targetSdkVersion  >= android.os.Build.VERSION_CODES.KEY_LIME_PIE) {
+            Log.w(TAG, "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;
+        }
     }
 
     /**
@@ -1146,6 +1172,10 @@
     private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {
         getFragmentManager().popBackStack(BACK_STACK_PREFS,
                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
+        if (!isValidFragment(fragmentName)) {
+            throw new IllegalArgumentException("Invalid fragment for this activity: "
+                    + fragmentName);
+        }
         Fragment f = Fragment.instantiate(this, fragmentName, args);
         FragmentTransaction transaction = getFragmentManager().beginTransaction();
         transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
@@ -1275,6 +1305,10 @@
         if (mSinglePane) {
             startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
         } else {
+            if (!isValidFragment(fragmentClass)) {
+                throw new IllegalArgumentException("Invalid fragment for this activity: "
+                        + fragmentClass);
+            }
             Fragment f = Fragment.instantiate(this, fragmentClass, args);
             if (resultTo != null) {
                 f.setTargetFragment(resultTo, resultRequestCode);
@@ -1291,7 +1325,7 @@
             transaction.commitAllowingStateLoss();
         }
     }
-    
+
     /**
      * Called by a preference panel fragment to finish itself.
      * 
diff --git a/core/java/android/print/FileDocumentAdapter.java b/core/java/android/print/FileDocumentAdapter.java
index c7011f4..d642a61 100644
--- a/core/java/android/print/FileDocumentAdapter.java
+++ b/core/java/android/print/FileDocumentAdapter.java
@@ -34,8 +34,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Adapter for printing files.
@@ -69,7 +67,7 @@
     }
 
     @Override
-    public void onWrite(List<PageRange> pages, FileDescriptor destination,
+    public void onWrite(PageRange[] pages, FileDescriptor destination,
             CancellationSignal cancellationSignal, WriteResultCallback callback) {
         mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback);
         mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
@@ -127,9 +125,7 @@
 
         @Override
         protected void onPostExecute(Void result) {
-            List<PageRange> pages = new ArrayList<PageRange>();
-            pages.add(PageRange.ALL_PAGES);
-            mResultCallback.onWriteFinished(pages);
+            mResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
         }
 
         @Override
diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl
index e4d79f3..43b8c30 100644
--- a/core/java/android/print/ILayoutResultCallback.aidl
+++ b/core/java/android/print/ILayoutResultCallback.aidl
@@ -16,7 +16,6 @@
 
 package android.print;
 
-import android.os.ICancellationSignal;
 import android.print.PrintDocumentInfo;
 
 /**
@@ -25,7 +24,6 @@
  * @hide
  */
 oneway interface ILayoutResultCallback {
-    void onLayoutStarted(ICancellationSignal cancellationSignal);
-    void onLayoutFinished(in PrintDocumentInfo info, boolean changed);
-    void onLayoutFailed(CharSequence error);
+    void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
+    void onLayoutFailed(CharSequence error, int sequence);
 }
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 04da157..b12c922 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -31,8 +31,8 @@
 oneway interface IPrintDocumentAdapter {
     void start();
     void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes,
-            ILayoutResultCallback callback, in Bundle metadata);
-    void write(in List<PageRange> pages, in ParcelFileDescriptor fd,
-            IWriteResultCallback callback);
+            ILayoutResultCallback callback, in Bundle metadata, int sequence);
+    void write(in PageRange[] pages, in ParcelFileDescriptor fd,
+            IWriteResultCallback callback, int sequence);
     void finish();
 }
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index a466e74..37ae2ca 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -33,5 +33,4 @@
             in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
             int appId, int userId);
     void cancelPrintJob(int printJobId, int appId, int userId);
-
 }
diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl
index 7912964..51b5439 100644
--- a/core/java/android/print/IPrintSpoolerCallbacks.aidl
+++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl
@@ -28,9 +28,9 @@
  */
 oneway interface IPrintSpoolerCallbacks {
     void onGetPrintJobInfosResult(in List<PrintJobInfo> printJob, int sequence);
-    void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
     void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence);
     void onCancelPrintJobResult(boolean canceled, int sequence);
     void onSetPrintJobStateResult(boolean success, int sequence);
     void onSetPrintJobTagResult(boolean success, int sequence);
+    void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
 }
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 47975e1..46857e4 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.print.IPrinterDiscoveryObserver;
+import android.print.PrinterId;
 import android.print.PrintJobInfo;
 
 
@@ -30,6 +31,7 @@
     void onPrintJobQueued(in PrintJobInfo printJob);
     void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
     void onStopPrinterDiscovery();
+    void onRequestUpdatePrinters(in List<PrinterId> printerIds);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
 }
diff --git a/core/java/android/print/IPrintSpoolerObserver.aidl b/core/java/android/print/IPrintSpoolerObserver.aidl
deleted file mode 100644
index 7b8f40e..0000000
--- a/core/java/android/print/IPrintSpoolerObserver.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.print;
-
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for observing the state of the print spooler.
- *
- * @hide
- */
-oneway interface IPrinterDiscoveryObserver {
-    void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob);
-    void onAllPrintJobsHandled(in ComponentName printService);
-    void onAllPrintJobsHandled();
-}
diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl
index 39aeb8c..deabbcb 100644
--- a/core/java/android/print/IPrinterDiscoveryObserver.aidl
+++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl
@@ -25,6 +25,7 @@
  * @hide
  */
 oneway interface IPrinterDiscoveryObserver {
-    void addDiscoveredPrinters(in List<PrinterInfo> printers);
-    void removeDiscoveredPrinters(in List<PrinterId> printers);
+    void onPrintersAdded(in List<PrinterInfo> printers);
+    void onPrintersRemoved(in List<PrinterId> printers);
+    void onPrintersUpdated(in List<PrinterInfo> printers);
 }
diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl
index d5428b1..8281c4e 100644
--- a/core/java/android/print/IWriteResultCallback.aidl
+++ b/core/java/android/print/IWriteResultCallback.aidl
@@ -16,7 +16,6 @@
 
 package android.print;
 
-import android.os.ICancellationSignal;
 import android.print.PageRange;
 
 /**
@@ -25,7 +24,6 @@
  * @hide
  */
 oneway interface IWriteResultCallback {
-    void onWriteStarted(ICancellationSignal cancellationSignal);
-    void onWriteFinished(in List<PageRange> pages);
-    void onWriteFailed(CharSequence error);
+    void onWriteFinished(in PageRange[] pages, int sequence);
+    void onWriteFailed(CharSequence error, int sequence);
 }
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 9257a04..ba455f6 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -93,8 +93,38 @@
     }
 
     @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mEnd;
+        result = prime * result + mStart;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PageRange other = (PageRange) obj;
+        if (mEnd != other.mEnd) {
+            return false;
+        }
+        if (mStart != other.mStart) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
     public String toString() {
-        if (this == ALL_PAGES) {
+        if (mStart == 0 && mEnd == Integer.MAX_VALUE) {
             return "PageRange[<all pages>]";
         }
         StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 87d75c0..911e380 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -77,7 +77,6 @@
     private int mColorMode;
     private int mFittingMode;
     private int mOrientation;
-    private int mCopies;
 
     PrintAttributes() {
         /* hide constructor */
@@ -93,7 +92,6 @@
         mColorMode = parcel.readInt();
         mFittingMode = parcel.readInt();
         mOrientation = parcel.readInt();
-        mCopies = parcel.readInt();
     }
 
     /**
@@ -302,29 +300,6 @@
         mOrientation = orientation;
     }
 
-    /**
-     * Gets the number of copies.
-     *
-     * @return The number of copies or zero if not set.
-     */
-    public int getCopies() {
-        return mCopies;
-    }
-
-    /**
-     * Sets the number of copies.
-     *
-     * @param copyCount The number of copies.
-     *
-     * @hide
-     */
-    public void setCopies(int copyCount) {
-        if (copyCount < 1) {
-            throw new IllegalArgumentException("Copies must be more than one.");
-        }
-        mCopies = copyCount;
-    }
-
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         if (mMediaSize != null) {
@@ -361,7 +336,6 @@
         parcel.writeInt(mColorMode);
         parcel.writeInt(mFittingMode);
         parcel.writeInt(mOrientation);
-        parcel.writeInt(mCopies);
     }
 
     @Override
@@ -369,6 +343,101 @@
         return 0;
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mColorMode;
+        result = prime * result + mDuplexMode;
+        result = prime * result + mFittingMode;
+        result = prime * result + mOrientation;
+        result = prime * result + ((mInputTray == null) ? 0 : mInputTray.hashCode());
+        result = prime * result + ((mMargins == null) ? 0 : mMargins.hashCode());
+        result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
+        result = prime * result + ((mOutputTray == null) ? 0 : mOutputTray.hashCode());
+        result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PrintAttributes other = (PrintAttributes) obj;
+        if (mColorMode != other.mColorMode) {
+            return false;
+        }
+        if (mDuplexMode != other.mDuplexMode) {
+            return false;
+        }
+        if (mFittingMode != other.mFittingMode) {
+            return false;
+        }
+        if (mOrientation != other.mOrientation) {
+            return false;
+        }
+        if (mInputTray == null) {
+            if (other.mInputTray != null) {
+                return false;
+            }
+        } else if (!mInputTray.equals(other.mInputTray)) {
+            return false;
+        }
+        if (mOutputTray == null) {
+            if (other.mOutputTray != null) {
+                return false;
+            }
+        } else if (!mOutputTray.equals(other.mOutputTray)) {
+            return false;
+        }
+        if (mMargins == null) {
+            if (other.mMargins != null) {
+                return false;
+            }
+        } else if (!mMargins.equals(other.mMargins)) {
+            return false;
+        }
+        if (mMediaSize == null) {
+            if (other.mMediaSize != null) {
+                return false;
+            }
+        } else if (!mMediaSize.equals(other.mMediaSize)) {
+            return false;
+        }
+        if (mResolution == null) {
+            if (other.mResolution != null) {
+                return false;
+            }
+        } else if (!mResolution.equals(other.mResolution)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("PrintAttributes{");
+        builder.append("mediaSize: ").append(mMediaSize);
+        builder.append(", resolution: ").append(mResolution);
+        builder.append(", margins: ").append(mMargins);
+        builder.append(", inputTray: ").append(mInputTray);
+        builder.append(", outputTray: ").append(mOutputTray);
+        builder.append(", colorMode: ").append(colorModeToString(mColorMode));
+        builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
+        builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
+        builder.append(", orientation: ").append(orientationToString(mOrientation));
+        builder.append("}");
+        return builder.toString();
+    }
+
     /** hide */
     public void clear() {
         mMediaSize = null;
@@ -380,7 +449,6 @@
         mColorMode = 0;
         mFittingMode = 0;
         mOrientation = 0;
-        mCopies = 0;
     }
 
     /**
@@ -396,7 +464,6 @@
         mColorMode = other.mColorMode;
         mFittingMode = other.mFittingMode;
         mOrientation = other.mOrientation;
-        mCopies = other.mCopies;
     }
 
     /**
@@ -954,6 +1021,44 @@
         }
 
         @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+            result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+            result = prime * result + mWidthMils;
+            result = prime * result + mHeightMils;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            MediaSize other = (MediaSize) obj;
+            if (!TextUtils.equals(mId, other.mId)) {
+                return false;
+            }
+            if (!TextUtils.equals(mLabel, other.mLabel)) {
+                return false;
+            }
+            if (mWidthMils != other.mWidthMils) {
+                return false;
+            }
+            if (mHeightMils != other.mHeightMils) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append("MediaSize{");
@@ -1061,6 +1166,44 @@
         }
 
         @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+            result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+            result = prime * result + mHorizontalDpi;
+            result = prime * result + mVerticalDpi;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            Resolution other = (Resolution) obj;
+            if (!TextUtils.equals(mId, other.mId)) {
+                return false;
+            }
+            if (!TextUtils.equals(mLabel, other.mLabel)) {
+                return false;
+            }
+            if (mHorizontalDpi != other.mHorizontalDpi) {
+                return false;
+            }
+            if (mVerticalDpi != other.mVerticalDpi) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append("Resolution{");
@@ -1166,6 +1309,44 @@
         }
 
         @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mBottomMils;
+            result = prime * result + mLeftMils;
+            result = prime * result + mRightMils;
+            result = prime * result + mTopMils;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            Margins other = (Margins) obj;
+            if (mBottomMils != other.mBottomMils) {
+                return false;
+            }
+            if (mLeftMils != other.mLeftMils) {
+                return false;
+            }
+            if (mRightMils != other.mRightMils) {
+                return false;
+            }
+            if (mTopMils != other.mTopMils) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append("Margins{");
@@ -1235,6 +1416,36 @@
         }
 
         @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+            result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            Tray other = (Tray) obj;
+            if (!TextUtils.equals(mId, other.mId)) {
+                return false;
+            }
+            if (!TextUtils.equals(mLabel, other.mLabel)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append("Tray{");
@@ -1246,21 +1457,6 @@
         }
     }
 
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("PrintAttributes{");
-        builder.append("mediaSize: ").append(mMediaSize);
-        builder.append(", resolution: ").append(mResolution);
-        builder.append(", margins: ").append(mMargins);
-        builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
-        builder.append(", colorMode: ").append(colorModeToString(mColorMode));
-        builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
-        builder.append(", orientation: ").append(orientationToString(mOrientation));
-        builder.append(", copies: ").append(mCopies);
-        return builder.toString();
-    }
-
     private static String duplexModeToString(int duplexMode) {
         switch (duplexMode) {
             case DUPLEX_MODE_NONE: {
@@ -1412,7 +1608,7 @@
          * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
          */
         public Builder setDuplexMode(int duplexMode) {
-            if (Integer.bitCount(duplexMode) != 1) {
+            if (Integer.bitCount(duplexMode) > 1) {
                 throw new IllegalArgumentException("can specify at most one duplexMode bit.");
             }
             mAttributes.setDuplexMode(duplexMode);
@@ -1471,17 +1667,6 @@
         }
 
         /**
-         * Sets the number of copies.
-         *
-         * @param copyCount A greater or equal to zero copy count.
-         * @return This builder.
-         */
-        public Builder setCopyCount(int copyCount) {
-            mAttributes.setCopies(copyCount);
-            return this;
-        }
-
-        /**
          * Creates a new {@link PrintAttributes} instance.
          *
          * @return The new instance.
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index 1f83a45..d320226 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -41,7 +41,7 @@
  * <li>
  * After every call to {@link #onLayout(PrintAttributes, PrintAttributes,
  * CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to
- * {@link #onWrite(List, FileDescriptor, CancellationSignal, WriteResultCallback)}
+ * {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)}
  * asking you to write a PDF file with the content for specific pages.
  * </li>
  * <li>
@@ -64,7 +64,7 @@
  * PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on
  * the UI thread (assuming onStart initializes resources needed for layout).
  * This will ensure that the UI does not change while you are laying out the
- * printed content. Then you can handle {@link #onWrite(List, FileDescriptor,
+ * printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor,
  * CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another
  * thread. This will ensure that the UI is frozen for the minimal amount of
  * time. Also this assumes that you will generate the printed content in
@@ -141,7 +141,7 @@
      * made on the main thread.
      * </p>
      *
-     * @param pages The pages whose content to print.
+     * @param pages The pages whose content to print - non-overlapping in ascending order.
      * @param destination The destination file descriptor to which to write.
      * @param cancellationSignal Signal for observing cancel writing requests.
      * @param callback Callback to inform the system for the write result.
@@ -149,7 +149,7 @@
      * @see WriteResultCallback
      * @see CancellationSignal
      */
-    public abstract void onWrite(List<PageRange> pages, FileDescriptor destination,
+    public abstract void onWrite(PageRange[] pages, FileDescriptor destination,
             CancellationSignal cancellationSignal, WriteResultCallback callback);
 
     /**
@@ -163,7 +163,7 @@
 
     /**
      * Base class for implementing a callback for the result of {@link
-     * PrintDocumentAdapter#onWrite(List, FileDescriptor, CancellationSignal,
+     * PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal,
      * WriteResultCallback)}.
      */
     public static abstract class WriteResultCallback {
@@ -178,9 +178,9 @@
         /**
          * Notifies that all the data was written.
          *
-         * @param pages The pages that were written.
+         * @param pages The pages that were written. Cannot be null or empty.
          */
-        public void onWriteFinished(List<PageRange> pages) {
+        public void onWriteFinished(PageRange[] pages) {
             /* do nothing - stub */
         }
 
@@ -192,6 +192,13 @@
         public void onWriteFailed(CharSequence error) {
             /* do nothing - stub */
         }
+
+        /**
+         * Notifies that write was cancelled as a result of a cancellation request.
+         */
+        public void onWriteCancelled() {
+            /* do nothing - stub */
+        }
     }
 
     /**
@@ -211,7 +218,7 @@
         /**
          * Notifies that the layout finished and whether the content changed.
          *
-         * @param info An info object describing the document.
+         * @param info An info object describing the document. Cannot be null.
          * @param changed Whether the layout changed.
          *
          * @see PrintDocumentInfo
@@ -228,5 +235,12 @@
         public void onLayoutFailed(CharSequence error) {
             /* do nothing - stub */
         }
+
+        /**
+         * Notifies that layout was cancelled as a result of a cancellation request.
+         */
+        public void onLayoutCancelled() {
+            /* do nothing - stub */
+        }
     }
 }
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 7d42b3a..29e8e7c 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -111,6 +111,36 @@
     }
 
     @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mContentType;
+        result = prime * result + mPageCount;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PrintDocumentInfo other = (PrintDocumentInfo) obj;
+        if (mContentType != other.mContentType) {
+            return false;
+        }
+        if (mPageCount != other.mPageCount) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrintDocumentInfo{");
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index a5e0b79..de28bd3 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -55,6 +55,9 @@
      * @return The print job info.
      */
     public PrintJobInfo getInfo() {
+        if (isInImmutableState()) {
+            return mCachedInfo;
+        }
         PrintJobInfo info = mPrintManager.getPrintJobInfo(mId);
         if (info != null) {
             mCachedInfo = info;
@@ -66,7 +69,15 @@
      * Cancels this print job.
      */
     public void cancel() {
-        mPrintManager.cancelPrintJob(mId);
+        if (!isInImmutableState()) {
+            mPrintManager.cancelPrintJob(mId);
+        }
+    }
+
+    private boolean isInImmutableState() {
+        final int state = mCachedInfo.getState();
+        return state == PrintJobInfo.STATE_COMPLETED
+                || state == PrintJobInfo.STATE_CANCELED;
     }
 
     @Override
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 97384d9..39546f3 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+
 /**
  * This class represents the description of a print job.
  */
@@ -119,6 +121,9 @@
     /** Optional tag assigned by a print service.*/
     private String mTag;
 
+    /** How many copies to print. */
+    private int mCopies;
+
     /** The pages to print */
     private PageRange[] mPageRanges;
 
@@ -142,6 +147,8 @@
         mAppId = other.mAppId;
         mUserId = other.mUserId;
         mTag = other.mTag;
+        mCopies = other.mCopies;
+        mPageRanges = other.mPageRanges;
         mAttributes = other.mAttributes;
         mDocumentInfo = other.mDocumentInfo;
     }
@@ -154,8 +161,13 @@
         mAppId = parcel.readInt();
         mUserId = parcel.readInt();
         mTag = parcel.readString();
+        mCopies = parcel.readInt();
         if (parcel.readInt() == 1) {
-            mPageRanges = (PageRange[]) parcel.readParcelableArray(null);
+            Parcelable[] parcelables = parcel.readParcelableArray(null);
+            mPageRanges = new PageRange[parcelables.length];
+            for (int i = 0; i < parcelables.length; i++) {
+                mPageRanges[i] = (PageRange) parcelables[i];
+            }
         }
         if (parcel.readInt() == 1) {
             mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel);
@@ -310,6 +322,29 @@
     }
 
     /**
+     * Gets the number of copies.
+     *
+     * @return The number of copies or zero if not set.
+     */
+    public int getCopies() {
+        return mCopies;
+    }
+
+    /**
+     * Sets the number of copies.
+     *
+     * @param copyCount The number of copies.
+     *
+     * @hide
+     */
+    public void setCopies(int copyCount) {
+        if (copyCount < 1) {
+            throw new IllegalArgumentException("Copies must be more than one.");
+        }
+        mCopies = copyCount;
+    }
+
+    /**
      * Gets the included pages.
      *
      * @return The included pages or <code>null</code> if not set.
@@ -385,6 +420,7 @@
         parcel.writeInt(mAppId);
         parcel.writeInt(mUserId);
         parcel.writeString(mTag);
+        parcel.writeInt(mCopies);
         if (mPageRanges != null) {
             parcel.writeInt(1);
             parcel.writeParcelableArray(mPageRanges, flags);
@@ -413,10 +449,14 @@
         builder.append(", id: ").append(mId);
         builder.append(", status: ").append(stateToString(mState));
         builder.append(", printer: " + mPrinterId);
+        builder.append(", tag: ").append(mTag);
+        builder.append(", copies: ").append(mCopies);
         builder.append(", attributes: " + (mAttributes != null
                 ? mAttributes.toString() : null));
         builder.append(", documentInfo: " + (mDocumentInfo != null
                 ? mDocumentInfo.toString() : null));
+        builder.append(", pages: " + (mPageRanges != null
+                ? Arrays.toString(mPageRanges) : null));
         builder.append("}");
         return builder.toString();
     }
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index f9f53f6..9e8cfad 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -223,6 +222,11 @@
     }
 
     private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
+
+        private final Object mLock = new Object();
+
+        private CancellationSignal mLayoutOrWriteCancellation;
+
         private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
 
         private Handler mHandler; // Strong reference OK - cleared in finish()
@@ -239,22 +243,36 @@
 
         @Override
         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
-                ILayoutResultCallback callback, Bundle metadata) {
+                ILayoutResultCallback callback, Bundle metadata, int sequence) {
+            synchronized (mLock) {
+                if (mLayoutOrWriteCancellation != null) {
+                    mLayoutOrWriteCancellation.cancel();
+                }
+            }
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = oldAttributes;
             args.arg2 = newAttributes;
             args.arg3 = callback;
             args.arg4 = metadata;
+            args.argi1 = sequence;
+            mHandler.removeMessages(MyHandler.MSG_LAYOUT);
             mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
         }
 
         @Override
-        public void write(List<PageRange> pages, ParcelFileDescriptor fd,
-            IWriteResultCallback callback) {
+        public void write(PageRange[] pages, ParcelFileDescriptor fd,
+            IWriteResultCallback callback, int sequence) {
+            synchronized (mLock) {
+                if (mLayoutOrWriteCancellation != null) {
+                    mLayoutOrWriteCancellation.cancel();
+                }
+            }
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = pages;
             args.arg2 = fd.getFileDescriptor();
             args.arg3 = callback;
+            args.argi1 = sequence;
+            mHandler.removeMessages(MyHandler.MSG_WRITE);
             mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
         }
 
@@ -283,7 +301,6 @@
             }
 
             @Override
-            @SuppressWarnings("unchecked")
             public void handleMessage(Message message) {
                 if (isFinished()) {
                     return;
@@ -295,42 +312,116 @@
 
                     case MSG_LAYOUT: {
                         SomeArgs args = (SomeArgs) message.obj;
-                        PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
-                        PrintAttributes newAttributes = (PrintAttributes) args.arg2;
-                        ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
-                        Bundle metadata = (Bundle) args.arg4;
+                        final PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
+                        final PrintAttributes newAttributes = (PrintAttributes) args.arg2;
+                        final ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
+                        final Bundle metadata = (Bundle) args.arg4;
+                        final int sequence = args.argi1;
                         args.recycle();
 
-                        try {
-                            ICancellationSignal remoteSignal = CancellationSignal.createTransport();
-                            callback.onLayoutStarted(remoteSignal);
-
-                            mDocumentAdapter.onLayout(oldAttributes, newAttributes,
-                                    CancellationSignal.fromTransport(remoteSignal),
-                                    new LayoutResultCallbackWrapper(callback), metadata);
-                        } catch (RemoteException re) {
-                            Log.e(LOG_TAG, "Error printing", re);
+                        CancellationSignal cancellation = new CancellationSignal();
+                        synchronized (mLock) {
+                            mLayoutOrWriteCancellation = cancellation;
                         }
+
+                        mDocumentAdapter.onLayout(oldAttributes, newAttributes,
+                                cancellation, new LayoutResultCallback() {
+                            @Override
+                            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+                                if (info == null) {
+                                    throw new IllegalArgumentException("info cannot be null");
+                                }
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                                try {
+                                    callback.onLayoutFinished(info, changed, sequence);
+                                } catch (RemoteException re) {
+                                    Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
+                                }
+                            }
+
+                            @Override
+                            public void onLayoutFailed(CharSequence error) {
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                                try {
+                                    callback.onLayoutFailed(error, sequence);
+                                } catch (RemoteException re) {
+                                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+                                }
+                            }
+
+                            @Override
+                            public void onLayoutCancelled() {
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                            }
+                        }, metadata);
                     } break;
 
                     case MSG_WRITE: {
                         SomeArgs args = (SomeArgs) message.obj;
-                        List<PageRange> pages = (List<PageRange>) args.arg1;
-                        FileDescriptor fd = (FileDescriptor) args.arg2;
-                        IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+                        final PageRange[] pages = (PageRange[]) args.arg1;
+                        final FileDescriptor fd = (FileDescriptor) args.arg2;
+                        final IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+                        final int sequence = args.argi1;
                         args.recycle();
 
-                        try {
-                            ICancellationSignal remoteSignal = CancellationSignal.createTransport();
-                            callback.onWriteStarted(remoteSignal);
-
-                            mDocumentAdapter.onWrite(pages, fd,
-                                    CancellationSignal.fromTransport(remoteSignal),
-                                    new WriteResultCallbackWrapper(callback, fd));
-                        } catch (RemoteException re) {
-                            Log.e(LOG_TAG, "Error printing", re);
-                            IoUtils.closeQuietly(fd);
+                        CancellationSignal cancellation = new CancellationSignal();
+                        synchronized (mLock) {
+                            mLayoutOrWriteCancellation = cancellation;
                         }
+
+                        mDocumentAdapter.onWrite(pages, fd, cancellation,
+                                new WriteResultCallback() {
+                            @Override
+                            public void onWriteFinished(PageRange[] pages) {
+                                if (pages == null) {
+                                    throw new IllegalArgumentException("pages cannot be null");
+                                }
+                                if (pages.length == 0) {
+                                    throw new IllegalArgumentException("pages cannot be empty");
+                                }
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                                // Close before notifying the other end. We want
+                                // to be ready by the time we announce it.
+                                IoUtils.closeQuietly(fd);
+                                try {
+                                    callback.onWriteFinished(pages, sequence);
+                                } catch (RemoteException re) {
+                                    Log.e(LOG_TAG, "Error calling onWriteFinished", re);
+                                }
+                            }
+
+                            @Override
+                            public void onWriteFailed(CharSequence error) {
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                                // Close before notifying the other end. We want
+                                // to be ready by the time we announce it.
+                                IoUtils.closeQuietly(fd);
+                                try {
+                                    callback.onWriteFailed(error, sequence);
+                                } catch (RemoteException re) {
+                                    Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+                                }
+                            }
+
+                            @Override
+                            public void onWriteCancelled() {
+                                synchronized (mLock) {
+                                    mLayoutOrWriteCancellation = null;
+                                }
+                                // Just close the fd for now.
+                                IoUtils.closeQuietly(fd);
+                            }
+                        });
                     } break;
 
                     case MSG_FINISH: {
@@ -346,67 +437,4 @@
             }
         }
     }
-
-    private static final class WriteResultCallbackWrapper extends WriteResultCallback {
-
-        private final IWriteResultCallback mWrappedCallback;
-        private final FileDescriptor mFd;
-
-        public WriteResultCallbackWrapper(IWriteResultCallback callback,
-                FileDescriptor fd) {
-            mWrappedCallback = callback;
-            mFd = fd;
-        }
-
-        @Override
-        public void onWriteFinished(List<PageRange> pages) {
-            try {
-                // Close before notifying the other end. We want
-                // to be ready by the time we announce it.
-                IoUtils.closeQuietly(mFd);
-                mWrappedCallback.onWriteFinished(pages);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onWriteFinished", re);
-            }
-        }
-
-        @Override
-        public void onWriteFailed(CharSequence error) {
-            try {
-                // Close before notifying the other end. We want
-                // to be ready by the time we announce it.
-                IoUtils.closeQuietly(mFd);
-                mWrappedCallback.onWriteFailed(error);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onWriteFailed", re);
-            }
-        }
-    }
-
-    private static final class LayoutResultCallbackWrapper extends LayoutResultCallback {
-
-        private final ILayoutResultCallback mWrappedCallback;
-
-        public LayoutResultCallbackWrapper(ILayoutResultCallback callback) {
-            mWrappedCallback = callback;
-        }
-
-        @Override
-        public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-            try {
-                mWrappedCallback.onLayoutFinished(info, changed);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
-            }
-        }
-
-        @Override
-        public void onLayoutFailed(CharSequence error) {
-            try {
-                mWrappedCallback.onLayoutFailed(error);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
-            }
-        }
-    }
 }
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index e884026..e27fbb2 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 /**
  * This class represents the unique id of a printer.
@@ -98,7 +99,7 @@
         } else if (!mServiceComponentName.equals(other.mServiceComponentName)) {
             return false;
         }
-        if (mLocalId != other.mLocalId) {
+        if (!TextUtils.equals(mLocalId, other.mLocalId)) {
             return false;
         }
         return true;
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index da3b6bc..15bcb73 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -23,9 +23,9 @@
 import android.print.PrintAttributes.Resolution;
 import android.print.PrintAttributes.Tray;
 import android.text.TextUtils;
-import android.util.SparseIntArray;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -41,8 +41,6 @@
      */
     public static final int DEFAULT_UNDEFINED = -1;
 
-    private static final int MIN_COPIES = 1;
-
     private static final int PROPERTY_MEDIA_SIZE = 0;
     private static final int PROPERTY_RESOLUTION = 1;
     private static final int PROPERTY_INPUT_TRAY = 2;
@@ -51,19 +49,22 @@
     private static final int PROPERTY_COLOR_MODE = 5;
     private static final int PROPERTY_FITTING_MODE = 6;
     private static final int PROPERTY_ORIENTATION = 7;
+    private static final int PROPERTY_COUNT = 8;
 
     /** Printer status: the printer is ready to print. */
     public static final int STATUS_READY = 1;
 
+    private static final Margins DEFAULT_MARGINS = new Margins(0,  0,  0,  0);
+
     // TODO: Add printer status constants.
 
     private PrinterId mId;
     private CharSequence mLabel;
     private int mStatus;
 
-    private Margins mMinMargins;
-    private final List<MediaSize> mMediaSizes = new ArrayList<MediaSize>(); // required
-    private final List<Resolution> mResolutions = new ArrayList<Resolution>(); // required
+    private Margins mMinMargins = DEFAULT_MARGINS;
+    private List<MediaSize> mMediaSizes;
+    private List<Resolution> mResolutions;
     private List<Tray> mInputTrays;
     private List<Tray> mOutputTrays;
 
@@ -72,43 +73,83 @@
     private int mFittingModes;
     private int mOrientations;
 
-    private final SparseIntArray mDefaults = new SparseIntArray();
-    private Margins mDefaultMargins;
+    private final int[] mDefaults = new int[PROPERTY_COUNT];
+    private Margins mDefaultMargins = DEFAULT_MARGINS;
 
-    private PrinterInfo() {
-        mDefaults.put(PROPERTY_MEDIA_SIZE, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_RESOLUTION, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_INPUT_TRAY, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_OUTPUT_TRAY, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_DUPLEX_MODE, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_COLOR_MODE, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_FITTING_MODE, DEFAULT_UNDEFINED);
-        mDefaults.put(PROPERTY_ORIENTATION, DEFAULT_UNDEFINED);
+    /**
+     * @hide
+     */
+    public PrinterInfo() {
+        Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
     }
 
     private PrinterInfo(PrinterInfo prototype) {
-        mId = prototype.mId;
-        mLabel = prototype.mLabel;
-        mStatus = prototype.mStatus;
+        copyFrom(prototype);
+    }
 
-        mMinMargins = prototype.mMinMargins;
-        mMediaSizes.addAll(prototype.mMediaSizes);
-        mResolutions.addAll(prototype.mResolutions);
-        mInputTrays = (prototype.mInputTrays != null)
-                ? new ArrayList<Tray>(prototype.mInputTrays) : null;
-        mOutputTrays = (prototype.mOutputTrays != null)
-                ? new ArrayList<Tray>(prototype.mOutputTrays) : null;
+    /**
+     * @hide
+     */
+    public void copyFrom(PrinterInfo other) {
+        mId = other.mId;
+        mLabel = other.mLabel;
+        mStatus = other.mStatus;
 
-        mDuplexModes = prototype.mDuplexModes;
-        mColorModes = prototype.mColorModes;
-        mFittingModes = prototype.mFittingModes;
-        mOrientations = prototype.mOrientations;
-
-        final int defaultCount = prototype.mDefaults.size();
-        for (int i = 0; i < defaultCount; i++) {
-            mDefaults.put(prototype.mDefaults.keyAt(i), prototype.mDefaults.valueAt(i));
+        mMinMargins = other.mMinMargins;
+        if (other.mMediaSizes != null) {
+            if (mMediaSizes != null) {
+                mMediaSizes.clear();
+                mMediaSizes.addAll(other.mMediaSizes);
+            } else {
+                mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
+            }
+        } else {
+            mMediaSizes = null;
         }
-        mDefaultMargins = prototype.mDefaultMargins;
+
+        if (other.mResolutions != null) {
+            if (mResolutions != null) {
+                mResolutions.clear();
+                mResolutions.addAll(other.mResolutions);
+            } else {
+                mResolutions = new ArrayList<Resolution>(other.mResolutions);
+            }
+        } else {
+            mResolutions = null;
+        }
+
+        if (other.mInputTrays != null) {
+            if (mInputTrays != null) {
+                mInputTrays.clear();
+                mInputTrays.addAll(other.mInputTrays);
+            } else {
+                mInputTrays = new ArrayList<Tray>(other.mInputTrays);
+            }
+        } else {
+            mInputTrays = null;
+        }
+
+        if (other.mOutputTrays != null) {
+            if (mOutputTrays != null) {
+                mOutputTrays.clear();
+                mOutputTrays.addAll(other.mOutputTrays);
+            } else {
+                mOutputTrays = new ArrayList<Tray>(other.mOutputTrays);
+            }
+        } else {
+            mOutputTrays = null;
+        }
+
+        mDuplexModes = other.mDuplexModes;
+        mColorModes = other.mColorModes;
+        mFittingModes = other.mFittingModes;
+        mOrientations = other.mOrientations;
+
+        final int defaultCount = other.mDefaults.length;
+        for (int i = 0; i < defaultCount; i++) {
+            mDefaults[i] = other.mDefaults[i];
+        }
+        mDefaultMargins = other.mDefaultMargins;
     }
 
     /**
@@ -240,52 +281,66 @@
     public void getDefaults(PrintAttributes outAttributes) {
         outAttributes.clear();
 
-        // TODO: Do we want a printer to specify default copies?
-        outAttributes.setCopies(MIN_COPIES);
-
         outAttributes.setMargins(mDefaultMargins);
 
-        final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE);
+        final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
         if (mediaSizeIndex >= 0) {
             outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
         }
 
-        final int resolutionIndex = mDefaults.get(PROPERTY_RESOLUTION);
+        final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
         if (resolutionIndex >= 0) {
             outAttributes.setResolution(mResolutions.get(resolutionIndex));
         }
 
-        final int inputTrayIndex = mDefaults.get(PROPERTY_INPUT_TRAY);
+        final int inputTrayIndex = mDefaults[PROPERTY_INPUT_TRAY];
         if (inputTrayIndex >= 0) {
             outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
         }
 
-        final int outputTrayIndex = mDefaults.get(PROPERTY_OUTPUT_TRAY);
+        final int outputTrayIndex = mDefaults[PROPERTY_OUTPUT_TRAY];
         if (outputTrayIndex >= 0) {
             outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
         }
 
-        final int duplexMode = mDefaults.get(PROPERTY_DUPLEX_MODE);
+        final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
         if (duplexMode > 0) {
             outAttributes.setDuplexMode(duplexMode);
         }
 
-        final int colorMode = mDefaults.get(PROPERTY_COLOR_MODE);
+        final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
         if (colorMode > 0) {
             outAttributes.setColorMode(mColorModes & colorMode);
         }
 
-        final int fittingMode = mDefaults.get(PROPERTY_FITTING_MODE);
+        final int fittingMode = mDefaults[PROPERTY_FITTING_MODE];
         if (fittingMode > 0) {
             outAttributes.setFittingMode(fittingMode);
         }
 
-        final int orientation = mDefaults.get(PROPERTY_ORIENTATION);
+        final int orientation = mDefaults[PROPERTY_ORIENTATION];
         if (orientation > 0) {
             outAttributes.setOrientation(orientation);
         }
     }
 
+    /**
+     * Gets whether this printer info is fully-populated, i.e. whether
+     * all required attributes are specified. See the {@link Builder}
+     * documentation for which attributes are required.
+     *
+     * @return Whether this info has all required attributes.
+     */
+    public boolean hasAllRequiredAttributes() {
+        return (mMediaSizes != null && !mMediaSizes.isEmpty()
+                && mResolutions != null && !mResolutions.isEmpty()
+                && mColorModes != 0 || mOrientations != 0
+                && mDefaults[PROPERTY_MEDIA_SIZE] != DEFAULT_UNDEFINED
+                && mDefaults[PROPERTY_RESOLUTION] != DEFAULT_UNDEFINED
+                && mDefaults[PROPERTY_COLOR_MODE] != DEFAULT_UNDEFINED
+                && mDefaults[PROPERTY_ORIENTATION] != DEFAULT_UNDEFINED);
+    }
+
     private PrinterInfo(Parcel parcel) {
         mId = parcel.readParcelable(null);
         mLabel = parcel.readCharSequence();
@@ -333,6 +388,112 @@
     }
 
     @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+        result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+        result = prime * result + mStatus;
+        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
+        result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
+        result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
+        result = prime * result + ((mInputTrays == null) ? 0 : mInputTrays.hashCode());
+        result = prime * result + ((mOutputTrays == null) ? 0 : mOutputTrays.hashCode());
+        result = prime * result + mDuplexModes;
+        result = prime * result + mColorModes;
+        result = prime * result + mFittingModes;
+        result = prime * result + mOrientations;
+        result = prime * result + Arrays.hashCode(mDefaults);
+        result = prime * result + ((mDefaultMargins == null) ? 0 : mDefaultMargins.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PrinterInfo other = (PrinterInfo) obj;
+        if (mId == null) {
+            if (other.mId != null) {
+                return false;
+            }
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        }
+        if (!TextUtils.equals(mLabel, other.mLabel)) {
+            return false;
+        }
+        if (mStatus != other.mStatus) {
+            return false;
+        }
+        if (mMinMargins == null) {
+            if (other.mMinMargins != null) {
+                return false;
+            }
+        } else if (!mMinMargins.equals(other.mMinMargins)) {
+            return false;
+        }
+        if (mMediaSizes == null) {
+            if (other.mMediaSizes != null) {
+                return false;
+            }
+        } else if (!mMediaSizes.equals(other.mMediaSizes)) {
+            return false;
+        }
+        if (mResolutions == null) {
+            if (other.mResolutions != null) {
+                return false;
+            }
+        } else if (!mResolutions.equals(other.mResolutions)) {
+            return false;
+        }
+        if (mInputTrays == null) {
+            if (other.mInputTrays != null) {
+                return false;
+            }
+        } else if (!mInputTrays.equals(other.mInputTrays)) {
+            return false;
+        }
+        if (mOutputTrays == null) {
+            if (other.mOutputTrays != null) {
+                return false;
+            }
+        } else if (!mOutputTrays.equals(other.mOutputTrays)) {
+            return false;
+        }
+        if (mDuplexModes != other.mDuplexModes) {
+            return false;
+        }
+        if (mColorModes != other.mColorModes) {
+            return false;
+        }
+        if (mFittingModes != other.mFittingModes) {
+            return false;
+        }
+        if (mOrientations != other.mOrientations) {
+            return false;
+        }
+        if (!Arrays.equals(mDefaults, other.mDefaults)) {
+            return false;
+        }
+        if (mDefaultMargins == null) {
+            if (other.mDefaultMargins != null) {
+                return false;
+            }
+        } else if (!mDefaultMargins.equals(other.mDefaultMargins)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrinterInfo{");
@@ -356,12 +517,19 @@
 
     private void readMediaSizes(Parcel parcel) {
         final int mediaSizeCount = parcel.readInt();
+        if (mediaSizeCount > 0 && mMediaSizes == null) {
+            mMediaSizes = new ArrayList<MediaSize>();
+        }
         for (int i = 0; i < mediaSizeCount; i++) {
             mMediaSizes.add(MediaSize.createFromParcel(parcel));
         }
     }
 
     private void writeResolutions(Parcel parcel) {
+        if (mResolutions == null) {
+            parcel.writeInt(0);
+            return;
+        }
         final int resolutionCount = mResolutions.size();
         parcel.writeInt(resolutionCount);
         for (int i = 0; i < resolutionCount; i++) {
@@ -371,6 +539,9 @@
 
     private void readResolutions(Parcel parcel) {
         final int resolutionCount = parcel.readInt();
+        if (resolutionCount > 0 && mResolutions == null) {
+            mResolutions = new ArrayList<Resolution>();
+        }
         for (int i = 0; i < resolutionCount; i++) {
             mResolutions.add(Resolution.createFromParcel(parcel));
         }
@@ -440,15 +611,15 @@
     private void readDefaults(Parcel parcel) {
         final int defaultCount = parcel.readInt();
         for (int i = 0; i < defaultCount; i++) {
-            mDefaults.append(mDefaults.size(), parcel.readInt());
+            mDefaults[i] = parcel.readInt();
         }
     }
 
     private void writeDefaults(Parcel parcel) {
-        final int defaultCount = mDefaults.size();
+        final int defaultCount = mDefaults.length;
         parcel.writeInt(defaultCount);
         for (int i = 0; i < defaultCount; i++) {
-            parcel.writeInt(mDefaults.valueAt(i));
+            parcel.writeInt(mDefaults[i]);
         }
     }
 
@@ -467,11 +638,11 @@
         /**
          * Creates a new instance.
          *
-         * @param printerId The printer id.
-         * @param label The human readable printer label.
+         * @param printerId The printer id. Cannot be null.
+         * @param label The human readable printer label. Cannot be null or empty.
          *
-         * @throws IllegalArgumentException IF the printer id is null.
-         * @throws IllegalArgumentException IF the label is empty.
+         * @throws IllegalArgumentException If the printer id is null.
+         * @throws IllegalArgumentException If the label is empty.
          */
         public Builder(PrinterId printerId, CharSequence label) {
             if (printerId == null) {
@@ -514,11 +685,14 @@
          * @see PrintAttributes.MediaSize
          */
         public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
+            if (mPrototype.mMediaSizes == null) {
+                mPrototype.mMediaSizes = new ArrayList<MediaSize>();
+            }
             final int insertionIndex = mPrototype.mMediaSizes.size();
             mPrototype.mMediaSizes.add(mediaSize);
             if (isDefault) {
                 throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
-                mPrototype.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex);
+                mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
             }
             return this;
         }
@@ -539,11 +713,14 @@
          * @see PrintAttributes.Resolution
          */
         public Builder addResolution(Resolution resolution, boolean isDefault) {
+            if (mPrototype.mResolutions == null) {
+                mPrototype.mResolutions = new ArrayList<Resolution>();
+            }
             final int insertionIndex = mPrototype.mResolutions.size();
             mPrototype.mResolutions.add(resolution);
             if (isDefault) {
                 throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
-                mPrototype.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex);
+                mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
             }
             return this;
         }
@@ -596,7 +773,7 @@
             mPrototype.mInputTrays.add(inputTray);
             if (isDefault) {
                 throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
-                mPrototype.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex);
+                mPrototype.mDefaults[PROPERTY_INPUT_TRAY] = insertionIndex;
             }
             return this;
         }
@@ -624,7 +801,7 @@
             mPrototype.mOutputTrays.add(outputTray);
             if (isDefault) {
                 throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
-                mPrototype.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex);
+                mPrototype.mDefaults[PROPERTY_OUTPUT_TRAY] = insertionIndex;
             }
             return this;
         }
@@ -657,7 +834,7 @@
             }
             PrintAttributes.enforceValidColorMode(colorModes);
             mPrototype.mColorModes = colorModes;
-            mPrototype.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode);
+            mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
             return this;
         }
 
@@ -690,7 +867,7 @@
             }
             PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
             mPrototype.mDuplexModes = duplexModes;
-            mPrototype.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode);
+            mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
             return this;
         }
 
@@ -722,7 +899,7 @@
             }
             PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
             mPrototype.mFittingModes = fittingModes;
-            mPrototype.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode);
+            mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
             return this;
         }
 
@@ -754,55 +931,21 @@
             }
             PrintAttributes.enforceValidOrientation(defaultOrientation);
             mPrototype.mOrientations = orientations;
-            mPrototype.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation);
+            mPrototype.mDefaults[PROPERTY_ORIENTATION] = defaultOrientation;
             return this;
         }
 
         /**
-         * Crates a new {@link PrinterInfo} enforcing that all required properties
-         * have need specified. See individual methods in this class for reference
-         * about required attributes.
+         * Crates a new {@link PrinterInfo}.
          *
          * @return A new {@link PrinterInfo}.
-         *
-         * @throws IllegalStateException If a required attribute was not specified.
          */
         public PrinterInfo create() {
-            if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) {
-                throw new IllegalStateException("No media size specified.");
-            }
-            if (mPrototype.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) {
-                throw new IllegalStateException("No default media size specified.");
-            }
-            if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) {
-                throw new IllegalStateException("No resolution specified.");
-            }
-            if (mPrototype.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) {
-                throw new IllegalStateException("No default resolution specified.");
-            }
-            if (mPrototype.mColorModes == 0) {
-                throw new IllegalStateException("No color mode specified.");
-            }
-            if (mPrototype.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) {
-                throw new IllegalStateException("No default color mode specified.");
-            }
-            if (mPrototype.mOrientations == 0) {
-                throw new IllegalStateException("No oprientation specified.");
-            }
-            if (mPrototype.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) {
-                throw new IllegalStateException("No default orientation specified.");
-            }
-            if (mPrototype.mMinMargins == null) {
-                mPrototype.mMinMargins  = new Margins(0, 0, 0, 0);
-            }
-            if (mPrototype.mDefaultMargins == null) {
-                mPrototype.mDefaultMargins = mPrototype.mMinMargins;
-            }
             return new PrinterInfo(mPrototype);
         }
 
         private void throwIfDefaultAlreadySpecified(int propertyIndex) {
-            if (mPrototype.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) {
+            if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
                 throw new IllegalArgumentException("Default already specified.");
             }
         }
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index c72385a..e6fdbf9 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -29,8 +29,9 @@
  */
 oneway interface IPrintService {
     void setClient(IPrintServiceClient client);
-    void requestCancelPrintJob(in PrintJobInfo printJobInfo);
+    void onRequestUpdatePrinters(in List<PrinterId> printerIds);
+    void onRequestCancelPrintJob(in PrintJobInfo printJobInfo);
     void onPrintJobQueued(in PrintJobInfo printJobInfo);
-    void startPrinterDiscovery(IPrinterDiscoveryObserver observer);
-    void stopPrinterDiscovery();
+    void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
+    void onStopPrinterDiscovery();
 }
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 80530a7..0ac5a13 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -61,6 +61,9 @@
      * @return The print job info.
      */
     public PrintJobInfo getInfo() {
+        if (isInImmutableState()) {
+            return mCachedInfo;
+        }
         PrintJobInfo info = null;
         try {
             info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId());
@@ -182,6 +185,9 @@
      * @return True if the tag was set, false otherwise.
      */
     public boolean setTag(String tag) {
+        if (isInImmutableState()) {
+            return false;
+        }
         try {
             return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag);
         } catch (RemoteException re) {
@@ -210,6 +216,12 @@
         return mCachedInfo.getId();
     }
 
+    private boolean isInImmutableState() {
+        final int state = mCachedInfo.getState();
+        return state == PrintJobInfo.STATE_COMPLETED
+                || state == PrintJobInfo.STATE_CANCELED;
+    }
+
     private boolean setState(int state) {
         try {
             if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index dde31d2..15e1b73 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -207,13 +207,14 @@
      * Callback requesting from this service to start printer discovery.
      * At the end of the printer discovery period the system will call
      * {@link #onStopPrinterDiscovery()}. Discovered printers should be
-     * reported by calling #addDiscoveredPrinters(List) and reported ones
-     * that disappear should be reported by calling
+     * reported by calling {@link #addDiscoveredPrinters(List)} and reported
+     * ones that disappear should be reported by calling
      * {@link #removeDiscoveredPrinters(List)}.
      *
      * @see #onStopPrinterDiscovery()
      * @see #addDiscoveredPrinters(List)
      * @see #removeDiscoveredPrinters(List)
+     * @see #updateDiscoveredPrinters(List)
      */
     protected abstract void onStartPrinterDiscovery();
 
@@ -223,6 +224,7 @@
      * @see #onStartPrinterDiscovery()
      * @see #addDiscoveredPrinters(List)
      * @see #removeDiscoveredPrinters(List)
+     * @see #updateDiscoveredPrinters(List)
      */
     protected abstract void onStopPrinterDiscovery();
 
@@ -236,12 +238,23 @@
      * printers have to be added. You can call this method as many times as
      * necessary during the discovery period but should not pass in already
      * added printers. If a printer is already added in the same printer
-     * discovery period, it will be ignored. If you want to update an already
-     * added printer, you should removed it and then re-add it.
+     * discovery period, it will be ignored.
      * </p>
+     * <p>
+     * A {@link PrinterInfo} can have all of its required attributes specified,
+     * or not. Whether all attributes are specified can be verified by calling
+     * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
+     * regardless if all required attributes are specified. When the system
+     * (and the user) needs to interact with a printer, you will receive a
+     * call to {@link #onRequestUpdatePrinters(List)}. If you fail to update
+     * a printer that was added without all required attributes via calling
+     * {@link #updateDiscoveredPrinters(List)}, then this printer will be
+     * ignored, i.e. considered unavailable.
+     * <p>
      *
      * @param printers A list with discovered printers.
      *
+     * @see #updateDiscoveredPrinters(List)
      * @see #removeDiscoveredPrinters(List)
      * @see #onStartPrinterDiscovery()
      * @see #onStopPrinterDiscovery()
@@ -253,7 +266,7 @@
         }
         if (observer != null) {
             try {
-                observer.addDiscoveredPrinters(printers);
+                observer.onPrintersAdded(printers);
             } catch (RemoteException re) {
                 Log.e(LOG_TAG, "Error adding discovered printers", re);
             }
@@ -271,14 +284,13 @@
      * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
      * this method as many times as necessary during the discovery period
      * but should not pass in already removed printer ids. If a printer with
-     * a given id is already removed in the same discovery period, it will
-     * be ignored. If you want to update an already added printer, you should
-     * removed it and then re-add it.
+     * a given id is already removed, it will be ignored.
      * </p>
      *
      * @param printerIds A list with disappeared printer ids.
      *
      * @see #addDiscoveredPrinters(List)
+     * @see #updateDiscoveredPrinters(List)
      * @see #onStartPrinterDiscovery()
      * @see #onStopPrinterDiscovery()
      */
@@ -289,7 +301,7 @@
         }
         if (observer != null) {
             try {
-                observer.removeDiscoveredPrinters(printerIds);
+                observer.onPrintersRemoved(printerIds);
             } catch (RemoteException re) {
                 Log.e(LOG_TAG, "Error removing discovered printers", re);
             }
@@ -297,6 +309,65 @@
     }
 
     /**
+     * Updates discovered printers that are already added. This method should
+     * be called during a printer discovery period, i.e. after a call to
+     * {@link #onStartPrinterDiscovery()} and before the corresponding
+     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
+     * <p>
+     * For every printer discovery period all printers have to be added. You
+     * should update only printers that were added in this printer discovery
+     * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
+     * this method as many times as necessary during the discovery period
+     * but should not try to update already removed or never added printers.
+     * If a printer is already removed or never added, it will be ignored.
+     * </p>
+     *
+     * @param printers A list with updated printers.
+     *
+     * @see #addDiscoveredPrinters(List)
+     * @see #removeDiscoveredPrinters(List)
+     * @see #onStartPrinterDiscovery()
+     * @see #onStopPrinterDiscovery()
+     */
+    public final void updateDiscoveredPrinters(List<PrinterInfo> printers) {
+        final IPrinterDiscoveryObserver observer;
+        synchronized (mLock) {
+            observer = mDiscoveryObserver;
+        }
+        if (observer != null) {
+            try {
+                observer.onPrintersUpdated(printers);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error updating discovered printers", re);
+            }
+        }
+    }
+
+    /**
+     * Called when the system will start interacting with a printer
+     * giving you a change to update it in case some of its capabilities
+     * have changed. For example, this method will be called when the
+     * user selects a printer. Hence, it updating this printer should
+     * be done as quickly as possible in order to achieve maximally
+     * smooth user experience.
+     * <p>
+     * A {@link PrinterInfo} can have all of its required attributes specified,
+     * or not. Whether all attributes are specified can be verified by calling
+     * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
+     * regardless if all required attributes are specified. When the system
+     * (and the user) needs to interact with a printer, you will receive a
+     * call to this method. If you fail to update a printer that was added
+     * without all required attributes via calling
+     * {@link #updateDiscoveredPrinters(List)}, then this printer will be
+     * ignored, i.e. considered unavailable.
+     * </p>
+     *
+     * @param printerIds The printers to be updated.
+     */
+    protected void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+    }
+
+    /**
      * Called when canceling of a print job is requested. The service
      * should do best effort to fulfill the request. After the cancellation
      * is performed, the print job should be set to a cancelled state by
@@ -373,74 +444,87 @@
         return new IPrintService.Stub() {
             @Override
             public void setClient(IPrintServiceClient client) {
-                mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget();
+                mHandler.obtainMessage(MyHandler.MSG_SET_CLEINT, client).sendToTarget();
             }
 
             @Override
-            public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
-                mHandler.obtainMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY,
+            public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) {
+                mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
                         observer).sendToTarget();
             }
 
             @Override
-            public void stopPrinterDiscovery() {
-                mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY);
+            public void onStopPrinterDiscovery() {
+                mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY);
             }
 
             @Override
-            public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
-                mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB,
+            public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+                mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
+                        printerIds).sendToTarget();
+            }
+
+            @Override
+            public void onRequestCancelPrintJob(PrintJobInfo printJobInfo) {
+                mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
                         printJobInfo).sendToTarget();
             }
 
             @Override
             public void onPrintJobQueued(PrintJobInfo printJobInfo) {
-                mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED,
+                mHandler.obtainMessage(MyHandler.MSG_ON_PRINTJOB_QUEUED,
                         printJobInfo).sendToTarget();
             }
         };
     }
 
     private final class MyHandler extends Handler {
-        public static final int MESSAGE_START_PRINTER_DISCOVERY = 1;
-        public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2;
-        public static final int MESSAGE_CANCEL_PRINTJOB = 3;
-        public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4;
-        public static final int MESSAGE_SET_CLEINT = 5;
+        public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
+        public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
+        public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
+        public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 4;
+        public static final int MSG_ON_PRINTJOB_QUEUED = 5;
+        public static final int MSG_SET_CLEINT = 6;
 
         public MyHandler(Looper looper) {
             super(looper, null, true);
         }
 
         @Override
+        @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
             final int action = message.what;
             switch (action) {
-                case MESSAGE_START_PRINTER_DISCOVERY: {
+                case MSG_ON_START_PRINTER_DISCOVERY: {
                     synchronized (mLock) {
                         mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj;
                     }
                     onStartPrinterDiscovery();
                 } break;
 
-                case MESSAGE_STOP_PRINTER_DISCOVERY: {
+                case MSG_ON_STOP_PRINTER_DISCOVERY: {
                     synchronized (mLock) {
                         mDiscoveryObserver = null;
                     }
                     onStopPrinterDiscovery();
                 } break;
 
-                case MESSAGE_CANCEL_PRINTJOB: {
+                case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
                     onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
                 } break;
 
-                case MESSAGE_ON_PRINTJOB_QUEUED: {
+                case MSG_ON_REQUEST_UPDATE_PRINTERS: {
+                    List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+                    onRequestUpdatePrinters(printerIds);
+                } break;
+
+                case MSG_ON_PRINTJOB_QUEUED: {
                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
                     onPrintJobQueued(new PrintJob(printJobInfo, mClient));
                 } break;
 
-                case MESSAGE_SET_CLEINT: {
+                case MSG_SET_CLEINT: {
                     IPrintServiceClient client = (IPrintServiceClient) message.obj;
                     synchronized (mLock) {
                         mClient = client;
diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java
new file mode 100644
index 0000000..e37d2f2
--- /dev/null
+++ b/core/java/android/util/LayoutDirection.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util;
+
+/**
+ * An interface for defining layout directions. A layout direction can be left-to-right (LTR)
+ * or right-to-left (RTL). It can also be inherited (from a parent) or deduced from the default
+ * language script of a locale.
+ */
+public interface LayoutDirection {
+    /**
+     * Horizontal layout direction is from Left to Right.
+     */
+    public static final int LTR = 0;
+
+    /**
+     * Horizontal layout direction is from Right to Left.
+     */
+    public static final int RTL = 1;
+
+    /**
+     * Horizontal layout direction is inherited.
+     */
+    public static final int INHERIT = 2;
+
+    /**
+     * Horizontal layout direction is deduced from the default language script for the locale.
+     */
+    public static final int LOCALE = 3;
+}
diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java
index f29fb65..09f1f8e 100644
--- a/core/java/android/util/MapCollections.java
+++ b/core/java/android/util/MapCollections.java
@@ -183,13 +183,27 @@
         }
 
         @Override
-        public boolean contains(Object object) {
-            throw new UnsupportedOperationException();
+        public boolean contains(Object o) {
+            if (!(o instanceof Map.Entry))
+                return false;
+            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+            int index = colIndexOfKey(e.getKey());
+            if (index < 0) {
+                return false;
+            }
+            Object foundVal = colGetEntry(index, 1);
+            return Objects.equal(foundVal, e.getValue());
         }
 
         @Override
         public boolean containsAll(Collection<?> collection) {
-            throw new UnsupportedOperationException();
+            Iterator<?> it = collection.iterator();
+            while (it.hasNext()) {
+                if (!contains(it.next())) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         @Override
@@ -231,6 +245,23 @@
         public <T> T[] toArray(T[] array) {
             throw new UnsupportedOperationException();
         }
+
+        @Override
+        public boolean equals(Object object) {
+            return equalsSetHelper(this, object);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            for (int i=colGetSize()-1; i>=0; i--) {
+                final Object key = colGetEntry(i, 0);
+                final Object value = colGetEntry(i, 1);
+                result += ( (key == null ? 0 : key.hashCode()) ^
+                        (value == null ? 0 : value.hashCode()) );
+            }
+            return result;
+        }
     };
 
     final class KeySet implements Set<K> {
@@ -257,7 +288,7 @@
 
         @Override
         public boolean containsAll(Collection<?> collection) {
-            return removeAllHelper(colGetMap(), collection);
+            return containsAllHelper(colGetMap(), collection);
         }
 
         @Override
@@ -304,6 +335,21 @@
         public <T> T[] toArray(T[] array) {
             return toArrayHelper(array, 1);
         }
+
+        @Override
+        public boolean equals(Object object) {
+            return equalsSetHelper(this, object);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            for (int i=colGetSize()-1; i>=0; i--) {
+                Object obj = colGetEntry(i, 0);
+                result += obj == null ? 0 : obj.hashCode();
+            }
+            return result;
+        }
     };
 
     final class ValuesCollection implements Collection<V> {
@@ -437,7 +483,6 @@
         return oldSize != map.size();
     }
 
-
     public Object[] toArrayHelper(int offset) {
         final int N = colGetSize();
         Object[] result = new Object[N];
@@ -463,6 +508,24 @@
         return array;
     }
 
+    public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
+        if (set == object) {
+            return true;
+        }
+        if (object instanceof Set) {
+            Set<?> s = (Set<?>) object;
+
+            try {
+                return set.size() == s.size() && set.containsAll(s);
+            } catch (NullPointerException ignored) {
+                return false;
+            } catch (ClassCastException ignored) {
+                return false;
+            }
+        }
+        return false;
+    }
+
     public Set<Map.Entry<K, V>> getEntrySet() {
         if (mEntrySet == null) {
             mEntrySet = new EntrySet();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 299c4a2..7624b56 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -51,6 +51,7 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.LayoutDirection;
 import android.util.Log;
 import android.util.LongSparseLongArray;
 import android.util.Pools.SynchronizedPool;
@@ -1801,25 +1802,25 @@
      * Horizontal layout direction of this view is from Left to Right.
      * Use with {@link #setLayoutDirection}.
      */
-    public static final int LAYOUT_DIRECTION_LTR = 0;
+    public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR;
 
     /**
      * Horizontal layout direction of this view is from Right to Left.
      * Use with {@link #setLayoutDirection}.
      */
-    public static final int LAYOUT_DIRECTION_RTL = 1;
+    public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL;
 
     /**
      * Horizontal layout direction of this view is inherited from its parent.
      * Use with {@link #setLayoutDirection}.
      */
-    public static final int LAYOUT_DIRECTION_INHERIT = 2;
+    public static final int LAYOUT_DIRECTION_INHERIT = LayoutDirection.INHERIT;
 
     /**
      * Horizontal layout direction of this view is from deduced from the default language
      * script for the locale. Use with {@link #setLayoutDirection}.
      */
-    public static final int LAYOUT_DIRECTION_LOCALE = 3;
+    public static final int LAYOUT_DIRECTION_LOCALE = LayoutDirection.LOCALE;
 
     /**
      * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 54c2ba5..feaab3e 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -36,6 +36,7 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.Xml;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -78,7 +79,7 @@
      */
     private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>();
 
-    private boolean mIsAuxIme;
+    private final boolean mIsAuxIme;
 
     /**
      * Cavert: mForceDefault must be false for production. This flag is only for test.
@@ -112,7 +113,7 @@
         mService = service;
         ServiceInfo si = service.serviceInfo;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
-        mIsAuxIme = true;
+        boolean isAuxIme = true;
         mForceDefault = false;
 
         PackageManager pm = context.getPackageManager();
@@ -162,26 +163,28 @@
                     }
                     final TypedArray a = res.obtainAttributes(
                             attrs, com.android.internal.R.styleable.InputMethod_Subtype);
-                    InputMethodSubtype subtype = new InputMethodSubtype(
-                            a.getResourceId(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_label, 0),
-                            a.getResourceId(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_icon, 0),
-                            a.getString(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_imeSubtypeLocale),
-                            a.getString(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_imeSubtypeMode),
-                            a.getString(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_imeSubtypeExtraValue),
-                            a.getBoolean(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_isAuxiliary, false),
-                            a.getBoolean(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false),
-                            a.getInt(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)
-                            );
+                    final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
+                            .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_label, 0))
+                            .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_icon, 0))
+                            .setSubtypeLocale(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeLocale))
+                            .setSubtypeMode(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeMode))
+                            .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeExtraValue))
+                            .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_isAuxiliary, false))
+                            .setOverridesImplicitlyEnabledSubtype(a.getBoolean(
+                                    com.android.internal.R.styleable
+                                    .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false))
+                            .setSubtypeId(a.getInt(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
+                            .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_isAsciiCapable, false)).build();
                     if (!subtype.isAuxiliary()) {
-                        mIsAuxIme = false;
+                        isAuxIme = false;
                     }
                     mSubtypes.add(subtype);
                 }
@@ -194,7 +197,7 @@
         }
 
         if (mSubtypes.size() == 0) {
-            mIsAuxIme = false;
+            isAuxIme = false;
         }
 
         if (additionalSubtypesMap != null && additionalSubtypesMap.containsKey(mId)) {
@@ -212,6 +215,7 @@
         }
         mSettingsActivityName = settingsActivityComponent;
         mIsDefaultResId = isDefaultResId;
+        mIsAuxIme = isAuxIme;
     }
 
     InputMethodInfo(Parcel source) {
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 7895e6f..88b2977 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -52,6 +52,7 @@
 
     private final boolean mIsAuxiliary;
     private final boolean mOverridesImplicitlyEnabledSubtype;
+    private final boolean mIsAsciiCapable;
     private final int mSubtypeHashCode;
     private final int mSubtypeIconResId;
     private final int mSubtypeNameResId;
@@ -62,24 +63,145 @@
     private volatile HashMap<String, String> mExtraValueHashMapCache;
 
     /**
+     * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
+     * This class is designed to be used with
+     * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
+     * The developer needs to be aware of what each parameter means.
+     */
+    public static class InputMethodSubtypeBuilder {
+        /**
+         * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
+         * An auxiliary subtype has the following differences with a regular subtype:
+         * - An auxiliary subtype cannot be chosen as the default IME in Settings.
+         * - The framework will never switch to this subtype through
+         *   {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+         * Note that the subtype will still be available in the IME switcher.
+         * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
+         * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
+         */
+        public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
+            mIsAuxiliary = isAuxiliary;
+            return this;
+        }
+        private boolean mIsAuxiliary = false;
+
+        /**
+         * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
+         * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
+         * subtype with this parameter set will not be shown in the list of subtypes in each IME's
+         * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
+         * subtype that adapts to the current system language.
+         */
+        public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
+                boolean overridesImplicitlyEnabledSubtype) {
+            mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+            return this;
+        }
+        private boolean mOverridesImplicitlyEnabledSubtype = false;
+
+        /**
+         * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
+         * is ASCII capable, it should guarantee that the user can input ASCII characters with
+         * this subtype. This is important because many password fields only allow
+         * ASCII-characters.
+         */
+        public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
+            mIsAsciiCapable = isAsciiCapable;
+            return this;
+        }
+        private boolean mIsAsciiCapable = false;
+
+        /**
+         * @param subtypeIconResId is a resource ID of the subtype icon drawable.
+         */
+        public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
+            mSubtypeIconResId = subtypeIconResId;
+            return this;
+        }
+        private int mSubtypeIconResId = 0;
+
+        /**
+         * @param subtypeNameResId is the resource ID of the subtype name string.
+         * The string resource may have exactly one %s in it. If present,
+         * the %s part will be replaced with the locale's display name by
+         * the formatter. Please refer to {@link #getDisplayName} for details.
+         */
+        public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
+            mSubtypeNameResId = subtypeNameResId;
+            return this;
+        }
+        private int mSubtypeNameResId = 0;
+
+        /**
+         * @param subtypeId is the unique ID for this subtype. The input method framework keeps
+         * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
+         * stay enabled even if other attributes are different. If the ID is unspecified or 0,
+         * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+         * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
+         */
+        public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
+            mSubtypeId = subtypeId;
+            return this;
+        }
+        private int mSubtypeId = 0;
+
+        /**
+         * @param subtypeLocale is the locale supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
+            mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
+            return this;
+        }
+        private String mSubtypeLocale = "";
+
+        /**
+         * @param subtypeMode is the mode supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
+            mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
+            return this;
+        }
+        private String mSubtypeMode = "";
+        /**
+         * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
+         * but the API supplies tools to deal with a key-value comma-separated list; see
+         * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+         */
+        public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
+            mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
+            return this;
+        }
+        private String mSubtypeExtraValue = "";
+
+        /**
+         * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
+         */
+        public InputMethodSubtype build() {
+            return new InputMethodSubtype(this);
+        }
+     }
+
+     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
+             String mode, String extraValue, boolean isAuxiliary,
+             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+         builder.mSubtypeNameResId = nameId;
+         builder.mSubtypeIconResId = iconId;
+         builder.mSubtypeLocale = locale;
+         builder.mSubtypeMode = mode;
+         builder.mSubtypeExtraValue = extraValue;
+         builder.mIsAuxiliary = isAuxiliary;
+         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+         builder.mSubtypeId = id;
+         builder.mIsAsciiCapable = isAsciiCapable;
+         return builder;
+     }
+
+    /**
      * Constructor with no subtype ID specified, overridesImplicitlyEnabledSubtype not specified.
-     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
-     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
-     * the formatter. Please refer to {@link #getDisplayName} for details.
-     * @param iconId Resource ID of the subtype icon drawable.
-     * @param locale The locale supported by the subtype
-     * @param mode The mode supported by the subtype
-     * @param extraValue The extra value of the subtype. This string is free-form, but the API
-     * supplies tools to deal with a key-value comma-separated list; see
-     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
-     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
-     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
-     * the Settings even when this subtype is enabled. Please note that this subtype will still
-     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
-     * to this subtype while an IME is shown. The framework will never switch the current IME to
-     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
-     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
-     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+     * Arguments for this constructor have the same meanings as
+     * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+     * boolean, int)} except "id" and "overridesImplicitlyEnabledSubtype".
      * @hide
      */
     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
@@ -89,27 +211,10 @@
 
     /**
      * Constructor with no subtype ID specified.
-     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
-     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
-     * the formatter. Please refer to {@link #getDisplayName} for details.
-     * @param iconId Resource ID of the subtype icon drawable.
-     * @param locale The locale supported by the subtype
-     * @param mode The mode supported by the subtype
-     * @param extraValue The extra value of the subtype. This string is free-form, but the API
-     * supplies tools to deal with a key-value comma-separated list; see
-     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
-     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
-     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
-     * the Settings even when this subtype is enabled. Please note that this subtype will still
-     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
-     * to this subtype while an IME is shown. The framework will never switch the current IME to
-     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
-     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
-     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
-     * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
-     * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
-     * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
-     * Having an "automatic" subtype is an example use of this flag.
+     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+     * Arguments for this constructor have the same meanings as
+     * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+     * boolean, int)} except "id".
      */
     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
@@ -119,6 +224,8 @@
 
     /**
      * Constructor.
+     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+     * "isAsciiCapable" is "false" in this constructor.
      * @param nameId Resource ID of the subtype name string. The string resource may have exactly
      * one %s in it. If there is, the %s part will be replaced with the locale's display name by
      * the formatter. Please refer to {@link #getDisplayName} for details.
@@ -148,18 +255,29 @@
      */
     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
-        mSubtypeNameResId = nameId;
-        mSubtypeIconResId = iconId;
-        mSubtypeLocale = locale != null ? locale : "";
-        mSubtypeMode = mode != null ? mode : "";
-        mSubtypeExtraValue = extraValue != null ? extraValue : "";
-        mIsAuxiliary = isAuxiliary;
-        mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+        this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+                overridesImplicitlyEnabledSubtype, id, false));
+    }
+
+    /**
+     * Constructor.
+     * @param builder Builder for InputMethodSubtype
+     */
+    private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
+        mSubtypeNameResId = builder.mSubtypeNameResId;
+        mSubtypeIconResId = builder.mSubtypeIconResId;
+        mSubtypeLocale = builder.mSubtypeLocale;
+        mSubtypeMode = builder.mSubtypeMode;
+        mSubtypeExtraValue = builder.mSubtypeExtraValue;
+        mIsAuxiliary = builder.mIsAuxiliary;
+        mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
+        mSubtypeId = builder.mSubtypeId;
+        mIsAsciiCapable = builder.mIsAsciiCapable;
         // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
         // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
-        mSubtypeHashCode = id != 0 ? id : hashCodeInternal(mSubtypeLocale, mSubtypeMode,
-                mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
-        mSubtypeId = id;
+        mSubtypeHashCode = mSubtypeId != 0 ? mSubtypeId : hashCodeInternal(mSubtypeLocale,
+                mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype,
+                mIsAsciiCapable);
     }
 
     InputMethodSubtype(Parcel source) {
@@ -176,6 +294,7 @@
         mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
         mSubtypeHashCode = source.readInt();
         mSubtypeId = source.readInt();
+        mIsAsciiCapable = (source.readInt() == 1);
     }
 
     /**
@@ -239,6 +358,15 @@
     }
 
     /**
+     * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
+     * capable, it should guarantee that the user can input ASCII characters with this subtype.
+     * This is important because many password fields only allow ASCII-characters.
+     */
+    public boolean isAsciiCapable() {
+        return mIsAsciiCapable;
+    }
+
+    /**
      * @param context Context will be used for getting Locale and PackageManager.
      * @param packageName The package name of the IME
      * @param appInfo The application info of the IME
@@ -336,7 +464,8 @@
                 && (subtype.getIconResId() == getIconResId())
                 && (subtype.getLocale().equals(getLocale()))
                 && (subtype.getExtraValue().equals(getExtraValue()))
-                && (subtype.isAuxiliary() == isAuxiliary());
+                && (subtype.isAuxiliary() == isAuxiliary())
+                && (subtype.isAsciiCapable() == isAsciiCapable());
         }
         return false;
     }
@@ -357,6 +486,7 @@
         dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
         dest.writeInt(mSubtypeHashCode);
         dest.writeInt(mSubtypeId);
+        dest.writeInt(mIsAsciiCapable ? 1 : 0);
     }
 
     public static final Parcelable.Creator<InputMethodSubtype> CREATOR
@@ -389,9 +519,10 @@
     }
 
     private static int hashCodeInternal(String locale, String mode, String extraValue,
-            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
+            boolean isAsciiCapable) {
         return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
-                overridesImplicitlyEnabledSubtype});
+                overridesImplicitlyEnabledSubtype, isAsciiCapable});
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index d924447..7e6bac4 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -28,7 +28,7 @@
  * @hide
  */
 public final class WebViewFactory {
-    private static final boolean DEFAULT_TO_EXPERIMENTAL_WEBVIEW = false;
+    private static final boolean DEFAULT_TO_EXPERIMENTAL_WEBVIEW = true;
     private static final String EXPERIMENTAL_PROPERTY_DEFAULT_OFF = "persist.sys.webview.exp";
     private static final String EXPERIMENTAL_PROPERTY_DEFAULT_ON  = "persist.sys.webview.exp_on";
 
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 33fd8ce..3e53b91 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -732,6 +732,15 @@
         }
     }
 
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        if (mDrawable != null) {
+            mDrawable.setLayoutDirection(layoutDirection);
+        }
+    }
+
     private static final Matrix.ScaleToFit[] sS2FArray = {
         Matrix.ScaleToFit.FILL,
         Matrix.ScaleToFit.START,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 816bb18..3181164 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2042,6 +2042,8 @@
             dr.mDrawableRightInitial = right;
         }
 
+        resetResolvedDrawables();
+        resolveDrawables();
         invalidate();
         requestLayout();
     }
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index e092fff..066d6c3 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import android.animation.ValueAnimator;
+import android.view.ViewParent;
 import com.android.internal.view.ActionBarPolicy;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPopupHelper;
@@ -75,7 +77,6 @@
 
     private ActionBarOverlayLayout mOverlayLayout;
     private ActionBarContainer mContainerView;
-    private ViewGroup mTopVisibilityView;
     private ActionBarView mActionView;
     private ActionBarContextView mContextView;
     private ActionBarContainer mSplitView;
@@ -125,12 +126,12 @@
         public void onAnimationEnd(Animator animation) {
             if (mContentAnimations && mContentView != null) {
                 mContentView.setTranslationY(0);
-                mTopVisibilityView.setTranslationY(0);
+                mContainerView.setTranslationY(0);
             }
             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
                 mSplitView.setVisibility(View.GONE);
             }
-            mTopVisibilityView.setVisibility(View.GONE);
+            mContainerView.setVisibility(View.GONE);
             mContainerView.setTransitioning(false);
             mCurrentShowAnim = null;
             completeDeferredDestroyActionMode();
@@ -144,7 +145,16 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             mCurrentShowAnim = null;
-            mTopVisibilityView.requestLayout();
+            mContainerView.requestLayout();
+        }
+    };
+
+    final ValueAnimator.AnimatorUpdateListener mUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            final ViewParent parent = mContainerView.getParent();
+            ((View) parent).invalidate();
         }
     };
 
@@ -153,7 +163,7 @@
         Window window = activity.getWindow();
         View decor = window.getDecorView();
         boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
-        init(decor, overlayMode);
+        init(decor);
         if (!overlayMode) {
             mContentView = decor.findViewById(android.R.id.content);
         }
@@ -161,26 +171,21 @@
 
     public ActionBarImpl(Dialog dialog) {
         mDialog = dialog;
-        init(dialog.getWindow().getDecorView(), false);
+        init(dialog.getWindow().getDecorView());
     }
 
-    private void init(View decor, boolean overlayMode) {
+    private void init(View decor) {
         mContext = decor.getContext();
         mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
                 com.android.internal.R.id.action_bar_overlay_layout);
         if (mOverlayLayout != null) {
-            mOverlayLayout.setActionBar(this, overlayMode);
+            mOverlayLayout.setActionBar(this);
         }
         mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
         mContextView = (ActionBarContextView) decor.findViewById(
                 com.android.internal.R.id.action_context_bar);
         mContainerView = (ActionBarContainer) decor.findViewById(
                 com.android.internal.R.id.action_bar_container);
-        mTopVisibilityView = (ViewGroup)decor.findViewById(
-                com.android.internal.R.id.top_action_bar);
-        if (mTopVisibilityView == null) {
-            mTopVisibilityView = mContainerView;
-        }
         mSplitView = (ActionBarContainer) decor.findViewById(
                 com.android.internal.R.id.split_action_bar);
 
@@ -675,29 +680,30 @@
         if (mCurrentShowAnim != null) {
             mCurrentShowAnim.end();
         }
-        mTopVisibilityView.setVisibility(View.VISIBLE);
+        mContainerView.setVisibility(View.VISIBLE);
 
         if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled
                 || fromSystem)) {
-            mTopVisibilityView.setTranslationY(0); // because we're about to ask its window loc
-            float startingY = -mTopVisibilityView.getHeight();
+            mContainerView.setTranslationY(0); // because we're about to ask its window loc
+            float startingY = -mContainerView.getHeight();
             if (fromSystem) {
                 int topLeft[] = {0, 0};
-                mTopVisibilityView.getLocationInWindow(topLeft);
+                mContainerView.getLocationInWindow(topLeft);
                 startingY -= topLeft[1];
             }
-            mTopVisibilityView.setTranslationY(startingY);
+            mContainerView.setTranslationY(startingY);
             AnimatorSet anim = new AnimatorSet();
-            AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
-                    "translationY", 0));
+            ObjectAnimator a = ObjectAnimator.ofFloat(mContainerView, View.TRANSLATION_Y, 0);
+            a.addUpdateListener(mUpdateListener);
+            AnimatorSet.Builder b = anim.play(a);
             if (mContentAnimations && mContentView != null) {
-                b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
+                b.with(ObjectAnimator.ofFloat(mContentView, View.TRANSLATION_Y,
                         startingY, 0));
             }
             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
                 mSplitView.setTranslationY(mSplitView.getHeight());
                 mSplitView.setVisibility(View.VISIBLE);
-                b.with(ObjectAnimator.ofFloat(mSplitView, "translationY", 0));
+                b.with(ObjectAnimator.ofFloat(mSplitView, View.TRANSLATION_Y, 0));
             }
             anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
                     com.android.internal.R.interpolator.decelerate_cubic));
@@ -713,8 +719,8 @@
             mCurrentShowAnim = anim;
             anim.start();
         } else {
-            mTopVisibilityView.setAlpha(1);
-            mTopVisibilityView.setTranslationY(0);
+            mContainerView.setAlpha(1);
+            mContainerView.setTranslationY(0);
             if (mContentAnimations && mContentView != null) {
                 mContentView.setTranslationY(0);
             }
@@ -737,24 +743,25 @@
 
         if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled
                 || fromSystem)) {
-            mTopVisibilityView.setAlpha(1);
+            mContainerView.setAlpha(1);
             mContainerView.setTransitioning(true);
             AnimatorSet anim = new AnimatorSet();
-            float endingY = -mTopVisibilityView.getHeight();
+            float endingY = -mContainerView.getHeight();
             if (fromSystem) {
                 int topLeft[] = {0, 0};
-                mTopVisibilityView.getLocationInWindow(topLeft);
+                mContainerView.getLocationInWindow(topLeft);
                 endingY -= topLeft[1];
             }
-            AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
-                    "translationY", endingY));
+            ObjectAnimator a = ObjectAnimator.ofFloat(mContainerView, View.TRANSLATION_Y, endingY);
+            a.addUpdateListener(mUpdateListener);
+            AnimatorSet.Builder b = anim.play(a);
             if (mContentAnimations && mContentView != null) {
-                b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
+                b.with(ObjectAnimator.ofFloat(mContentView, View.TRANSLATION_Y,
                         0, endingY));
             }
             if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
                 mSplitView.setAlpha(1);
-                b.with(ObjectAnimator.ofFloat(mSplitView, "translationY",
+                b.with(ObjectAnimator.ofFloat(mSplitView, View.TRANSLATION_Y,
                         mSplitView.getHeight()));
             }
             anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 7b4352e..c7b5697 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -72,6 +72,28 @@
         }
         return sb.toString();
     }
+
+    public static String getApiCallStack() {
+        String apiCallStack = "";
+        try {
+            throw new RuntimeException();
+        } catch (RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
+            for (int j = 1; j < frames.length; ++j) {
+                final String tempCallStack = frames[j].toString();
+                if (TextUtils.isEmpty(apiCallStack)) {
+                    // Overwrite apiCallStack if it's empty
+                    apiCallStack = tempCallStack;
+                } else if (tempCallStack.indexOf("Transact(") < 0) {
+                    // Overwrite apiCallStack if it's not a binder call
+                    apiCallStack = tempCallStack;
+                } else {
+                    break;
+                }
+            }
+        }
+        return apiCallStack;
+    }
     // ----------------------------------------------------------------------
 
     public static boolean isSystemIme(InputMethodInfo inputMethod) {
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index f359146..88ff7e2 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.widget;
 
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
 import android.view.ViewGroup;
 import com.android.internal.app.ActionBarImpl;
 
@@ -31,18 +35,23 @@
  * has request that its layout ignore them.
  */
 public class ActionBarOverlayLayout extends ViewGroup {
+    private static final String TAG = "ActionBarOverlayLayout";
+
     private int mActionBarHeight;
     private ActionBarImpl mActionBar;
     private int mWindowVisibility = View.VISIBLE;
 
     // The main UI elements that we handle the layout of.
     private View mContent;
-    private View mActionBarTop;
     private View mActionBarBottom;
+    private ActionBarContainer mActionBarTop;
 
     // Some interior UI elements.
-    private ActionBarContainer mContainerView;
-    private ActionBarView mActionView;
+    private ActionBarView mActionBarView;
+
+    // Content overlay drawable - generally the action bar's shadow
+    private Drawable mWindowContentOverlay;
+    private boolean mIgnoreWindowContentOverlay;
 
     private boolean mOverlayMode;
     private int mLastSystemUiVisibility;
@@ -53,8 +62,9 @@
     private final Rect mInnerInsets = new Rect();
     private final Rect mLastInnerInsets = new Rect();
 
-    static final int[] mActionBarSizeAttr = new int [] {
-            com.android.internal.R.attr.actionBarSize
+    static final int[] ATTRS = new int [] {
+            com.android.internal.R.attr.actionBarSize,
+            com.android.internal.R.attr.windowContentOverlay
     };
 
     public ActionBarOverlayLayout(Context context) {
@@ -68,14 +78,18 @@
     }
 
     private void init(Context context) {
-        TypedArray ta = getContext().getTheme().obtainStyledAttributes(mActionBarSizeAttr);
+        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
         mActionBarHeight = ta.getDimensionPixelSize(0, 0);
+        mWindowContentOverlay = ta.getDrawable(1);
+        setWillNotDraw(mWindowContentOverlay == null);
         ta.recycle();
+
+        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
+                Build.VERSION_CODES.KEY_LIME_PIE;
     }
 
-    public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
+    public void setActionBar(ActionBarImpl impl) {
         mActionBar = impl;
-        mOverlayMode = overlayMode;
         if (getWindowToken() != null) {
             // This is being initialized after being added to a window;
             // make sure to update all state now.
@@ -88,6 +102,18 @@
         }
     }
 
+    public void setOverlayMode(boolean overlayMode) {
+        mOverlayMode = overlayMode;
+
+        /*
+         * Drawing the window content overlay was broken before K so starting to draw it
+         * again unexpectedly will cause artifacts in some apps. They should fix it.
+         */
+        mIgnoreWindowContentOverlay = overlayMode &&
+                getContext().getApplicationInfo().targetSdkVersion <
+                        Build.VERSION_CODES.KEY_LIME_PIE;
+    }
+
     public void setShowingForActionMode(boolean showing) {
         if (showing) {
             // Here's a fun hack: if the status bar is currently being hidden,
@@ -253,7 +279,7 @@
             // we can't depend on the size currently reported by it -- this must remain constant.
             topInset = mActionBarHeight;
             if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
-                View tabs = mContainerView.getTabContainer();
+                View tabs = mActionBarTop.getTabContainer();
                 if (tabs != null) {
                     // If tabs are not embedded, increase space on top to account for them.
                     topInset += mActionBarHeight;
@@ -265,7 +291,7 @@
             topInset = mActionBarTop.getMeasuredHeight();
         }
 
-        if (mActionView.isSplitActionBar()) {
+        if (mActionBarView.isSplitActionBar()) {
             // If action bar is split, adjust bottom insets for it.
             if (mActionBarBottom != null) {
                 if (stable) {
@@ -352,6 +378,18 @@
     }
 
     @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+        if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
+            final int top = mActionBarTop.getVisibility() == VISIBLE ?
+                    (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0;
+            mWindowContentOverlay.setBounds(0, top, getWidth(),
+                    top + mWindowContentOverlay.getIntrinsicHeight());
+            mWindowContentOverlay.draw(c);
+        }
+    }
+
+    @Override
     public boolean shouldDelayChildPressedState() {
         return false;
     }
@@ -359,10 +397,9 @@
     void pullChildren() {
         if (mContent == null) {
             mContent = findViewById(com.android.internal.R.id.content);
-            mActionBarTop = findViewById(com.android.internal.R.id.top_action_bar);
-            mContainerView = (ActionBarContainer)findViewById(
+            mActionBarTop = (ActionBarContainer)findViewById(
                     com.android.internal.R.id.action_bar_container);
-            mActionView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
+            mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
             mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
         }
     }
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 32f6ecf..2c23f9d 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -586,7 +586,7 @@
     }
 
     ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, &name)) {
+    if (!am->getResources().getResourceName(resid, true, &name)) {
         return NULL;
     }
 
@@ -594,19 +594,27 @@
     if (name.package != NULL) {
         str.setTo(name.package, name.packageLen);
     }
-    if (name.type != NULL) {
+    if (name.type8 != NULL || name.type != NULL) {
         if (str.size() > 0) {
             char16_t div = ':';
             str.append(&div, 1);
         }
-        str.append(name.type, name.typeLen);
+        if (name.type8 != NULL) {
+            str.append(String16(name.type8, name.typeLen));
+        } else {
+            str.append(name.type, name.typeLen);
+        }
     }
-    if (name.name != NULL) {
+    if (name.name8 != NULL || name.name != NULL) {
         if (str.size() > 0) {
             char16_t div = '/';
             str.append(&div, 1);
         }
-        str.append(name.name, name.nameLen);
+        if (name.name8 != NULL) {
+            str.append(String16(name.name8, name.nameLen));
+        } else {
+            str.append(name.name, name.nameLen);
+        }
     }
 
     return env->NewString((const jchar*)str.string(), str.size());
@@ -621,7 +629,7 @@
     }
 
     ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, &name)) {
+    if (!am->getResources().getResourceName(resid, true, &name)) {
         return NULL;
     }
 
@@ -641,10 +649,14 @@
     }
 
     ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, &name)) {
+    if (!am->getResources().getResourceName(resid, true, &name)) {
         return NULL;
     }
 
+    if (name.type8 != NULL) {
+        return env->NewStringUTF(name.type8);
+    }
+
     if (name.type != NULL) {
         return env->NewString((const jchar*)name.type, name.typeLen);
     }
@@ -661,10 +673,14 @@
     }
 
     ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, &name)) {
+    if (!am->getResources().getResourceName(resid, true, &name)) {
         return NULL;
     }
 
+    if (name.name8 != NULL) {
+        return env->NewStringUTF(name.name8);
+    }
+
     if (name.name != NULL) {
         return env->NewString((const jchar*)name.name, name.nameLen);
     }
@@ -680,7 +696,7 @@
 {
     if (outValue == NULL) {
          jniThrowNullPointerException(env, "outValue");
-         return NULL;
+         return 0;
     }
     AssetManager* am = assetManagerForJavaObject(env, clazz);
     if (am == NULL) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 6d97d01..8325217 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -95,26 +95,6 @@
     }
 }
 
-jint android_os_Process_myPid(JNIEnv* env, jobject clazz)
-{
-    return getpid();
-}
-
-jint android_os_Process_myPpid(JNIEnv* env, jobject clazz)
-{
-    return getppid();
-}
-
-jint android_os_Process_myUid(JNIEnv* env, jobject clazz)
-{
-    return getuid();
-}
-
-jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
-{
-    return androidGetTid();
-}
-
 jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
 {
     if (name == NULL) {
@@ -349,8 +329,7 @@
 void android_os_Process_setCallingThreadPriority(JNIEnv* env, jobject clazz,
                                                         jint pri)
 {
-    jint tid = android_os_Process_myTid(env, clazz);
-    android_os_Process_setThreadPriority(env, clazz, tid, pri);
+    android_os_Process_setThreadPriority(env, clazz, androidGetTid(), pri);
 }
 
 jint android_os_Process_getThreadPriority(JNIEnv* env, jobject clazz,
@@ -1034,10 +1013,6 @@
 }
 
 static const JNINativeMethod methods[] = {
-    {"myPid",       "()I", (void*)android_os_Process_myPid},
-    {"myPpid",      "()I", (void*)android_os_Process_myPpid},
-    {"myTid",       "()I", (void*)android_os_Process_myTid},
-    {"myUid",       "()I", (void*)android_os_Process_myUid},
     {"getUidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
     {"getGidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
     {"setThreadPriority",   "(II)V", (void*)android_os_Process_setThreadPriority},
diff --git a/core/res/res/layout-xlarge/screen_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml
index 4f286780..e495e53 100644
--- a/core/res/res/layout-xlarge/screen_action_bar.xml
+++ b/core/res/res/layout-xlarge/screen_action_bar.xml
@@ -28,30 +28,23 @@
     <FrameLayout android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-    <LinearLayout android:id="@+id/top_action_bar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content">
-        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
+    <com.android.internal.widget.ActionBarContainer
+        android:id="@+id/action_bar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        style="?android:attr/actionBarStyle"
+        android:gravity="top">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            style="?android:attr/actionBarStyle"
-            android:gravity="top">
-            <com.android.internal.widget.ActionBarView
-                android:id="@+id/action_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="?android:attr/actionBarStyle" />
-            <com.android.internal.widget.ActionBarContextView
-                android:id="@+id/action_context_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                style="?android:attr/actionModeStyle" />
-        </com.android.internal.widget.ActionBarContainer>
-        <ImageView android:src="?android:attr/windowContentOverlay"
-                   android:scaleType="fitXY"
-                   android:layout_width="match_parent"
-                   android:layout_height="wrap_content" />
-    </LinearLayout>
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            style="?android:attr/actionModeStyle" />
+    </com.android.internal.widget.ActionBarContainer>
 </com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index e310bf5..b1889a2 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -25,34 +25,27 @@
     android:layout_height="match_parent"
     android:splitMotionEvents="false">
     <FrameLayout android:id="@android:id/content"
+                 android:layout_width="match_parent"
+                 android:layout_height="match_parent" />
+    <com.android.internal.widget.ActionBarContainer
+        android:id="@+id/action_bar_container"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-    <LinearLayout android:id="@+id/top_action_bar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content">
-        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        style="?android:attr/actionBarStyle"
+        android:gravity="top">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            style="?android:attr/actionBarStyle"
-            android:gravity="top">
-            <com.android.internal.widget.ActionBarView
-                android:id="@+id/action_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="?android:attr/actionBarStyle" />
-            <com.android.internal.widget.ActionBarContextView
-                android:id="@+id/action_context_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                style="?android:attr/actionModeStyle" />
-        </com.android.internal.widget.ActionBarContainer>
-        <ImageView android:src="?android:attr/windowContentOverlay"
-                   android:scaleType="fitXY"
-                   android:layout_width="match_parent"
-                   android:layout_height="wrap_content" />
-    </LinearLayout>
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            style="?android:attr/actionModeStyle" />
+    </com.android.internal.widget.ActionBarContainer>
     <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 15edacd..67a32fd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2407,6 +2407,11 @@
              constructor or 0. Arrays.hashCode(new Object[] {locale, mode, extraValue,
              isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead. -->
         <attr name="subtypeId" format="integer"/>
+        <!-- Set to true if this subtype is ASCII capable. If the subtype is ASCII
+             capable, it should guarantee that the user can input ASCII characters with
+             this subtype. This is important because many password fields only allow
+             ASCII-characters. -->
+        <attr name="isAsciiCapable" format="boolean" />
     </declare-styleable>
 
     <!-- Use <code>spell-checker</code> as the root tag of the XML resource that
@@ -3900,6 +3905,10 @@
              value is false.  See
              {@link android.graphics.drawable.Drawable#setVisible}. -->
         <attr name="visible" format="boolean" />
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left).  See
+             {@link android.graphics.drawable.Drawable#setAutoMirrored}. -->
+        <attr name="autoMirrored" format="boolean" />
     </declare-styleable>
 
     <!-- Drawable used to render several states. Each state is represented by
@@ -3927,6 +3936,9 @@
         <attr name="enterFadeDuration" format="integer" />
         <!-- Amount of time (in milliseconds) to fade out an old state drawable. -->
         <attr name="exitFadeDuration" format="integer" />
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left). -->
+        <attr name="autoMirrored"/>
     </declare-styleable>
 
     <!-- Drawable used to render several animated frames. -->
@@ -4078,6 +4090,9 @@
             <!-- The layer has translucent pixels. -->
             <enum name="translucent" value="-3" />
         </attr>
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left). -->
+        <attr name="autoMirrored" />
     </declare-styleable>
 
     <!-- Describes an item (or child) of a LayerDrawable. -->
@@ -4167,6 +4182,9 @@
             {@link android.graphics.Bitmap#setHasMipMap(boolean)} for more information.
             Default value is false. -->
         <attr name="mipMap" format="boolean" />
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left). -->
+        <attr name="autoMirrored" />
     </declare-styleable>
 
     <!-- Drawable used to draw 9-patches. -->
@@ -4177,6 +4195,9 @@
              same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with
              an RGB 565 screen). -->
         <attr name="dither" />
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left). -->
+        <attr name="autoMirrored" />
     </declare-styleable>
 
     <!-- Drawable used to draw a single color. -->
@@ -6002,4 +6023,9 @@
         <attr name="digit" format="integer" />
         <attr name="textView" format="reference" />
     </declare-styleable>
+
+    <declare-styleable name="DocumentsProviderInfo">
+        <attr name="customRoots" format="boolean" />
+    </declare-styleable>
+
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4a04d3e..80c9184 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2069,4 +2069,8 @@
   <public type="attr" name="addPrintersActivity" />
   <public type="attr" name="vendor" />
   <public type="attr" name="category" />
+  <public type="attr" name="isAsciiCapable" />
+  <public type="attr" name="customRoots" />
+  <public type="attr" name="autoMirrored" />
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 008c334..3e29221 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -175,7 +175,6 @@
   <java-symbol type="id" name="to_common" />
   <java-symbol type="id" name="to_org" />
   <java-symbol type="id" name="to_org_unit" />
-  <java-symbol type="id" name="top_action_bar" />
   <java-symbol type="id" name="topPanel" />
   <java-symbol type="id" name="up" />
   <java-symbol type="id" name="value" />
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index d6a7ee2..d8290f4 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -273,28 +273,47 @@
         // Check comparisons work.
         LinkProperties lp2 = new LinkProperties(lp);
         assertAllRoutesHaveInterface("wlan0", lp);
-        assertEquals(0, lp.compareRoutes(lp2).added.size());
-        assertEquals(0, lp.compareRoutes(lp2).removed.size());
+        assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+        assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
 
         lp2.setInterfaceName("p2p0");
         assertAllRoutesHaveInterface("p2p0", lp2);
-        assertEquals(3, lp.compareRoutes(lp2).added.size());
-        assertEquals(3, lp.compareRoutes(lp2).removed.size());
+        assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+        assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
     }
 
     @SmallTest
     public void testStackedInterfaces() {
         LinkProperties rmnet0 = new LinkProperties();
         rmnet0.setInterfaceName("rmnet0");
+        rmnet0.addLinkAddress(new LinkAddress(
+                    NetworkUtils.numericToInetAddress(ADDRV6), 128));
 
         LinkProperties clat4 = new LinkProperties();
         clat4.setInterfaceName("clat4");
+        clat4.addLinkAddress(new LinkAddress(
+                    NetworkUtils.numericToInetAddress(ADDRV4), 32));
 
         assertEquals(0, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(1, rmnet0.getAllAddresses().size());
+        assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
         rmnet0.addStackedLink(clat4);
         assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllAddresses().size());
+        assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
         rmnet0.addStackedLink(clat4);
         assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllAddresses().size());
+        assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
         assertEquals(0, clat4.getStackedLinks().size());
 
         // Modify an item in the returned collection to see what happens.
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 2169c8e..5f0779c 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -21,6 +21,9 @@
 - from: /sdk/compatibility-library.html
   to: /tools/support-library/index.html
 
+- from: /training/basics/fragments/support-lib.html
+  to: /tools/support-library/setup.html
+
 - from: /sdk/eclipse-adt.html
   to: /tools/sdk/eclipse-adt.html
 
@@ -299,6 +302,9 @@
 
 # ------------------- TRAINING -------------------
 
+- from: /guide/topics/ui/layout/tabs.html
+  to: /training/implementing-navigation/lateral.html
+
 - from: /training/cloudsync/aesync.html
   to: /google/gcm/index.html
 
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index 880d7e9..6e4a03c 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -48,7 +48,7 @@
 the Android platform.</p>
 
 <p>For information about how to target your application to devices based on
-platform version, read <a 
+platform version, read <a
 href="{@docRoot}training/basics/supporting-devices/platforms.html">Supporting Different
 Platform Versions</a>.</p>
 
@@ -57,7 +57,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 14-day period ending on July 8, 2013.
+<p style="clear:both"><em>Data collected during a 14-day period ending on August 1, 2013.
 <br/>Any versions with less than 0.1% distribution are not shown.</em>
 </p>
 
@@ -83,7 +83,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 14-day period ending on July 8, 2013
+<p style="clear:both"><em>Data collected during a 14-day period ending on August 1, 2013
 <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
 
 
@@ -130,7 +130,7 @@
 
 
 
-<p style="clear:both"><em>Data collected during a 14-day period ending on July 8, 2013</em></p>
+<p style="clear:both"><em>Data collected during a 14-day period ending on August 1, 2013</em></p>
 
 
 
@@ -148,7 +148,7 @@
 var VERSION_DATA =
 [
   {
-    "chart": "//chart.googleapis.com/chart?chl=Eclair%7CFroyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean&chco=c4df9b%2C6fad0c&chd=t%3A1.5%2C3.1%2C34.1%2C0.1%2C23.3%2C37.9&chf=bg%2Cs%2C00000000&chs=500x250&cht=p",
+    "chart": "//chart.googleapis.com/chart?cht=p&chs=500x250&chl=Eclair%7CFroyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean&chf=bg%2Cs%2C00000000&chd=t%3A1.3%2C2.5%2C33.1%2C0.1%2C22.5%2C40.5&chco=c4df9b%2C6fad0c",
     "data": [
       {
         "api": 4,
@@ -158,17 +158,22 @@
       {
         "api": 7,
         "name": "Eclair",
-        "perc": "1.4"
+        "perc": "1.2"
       },
       {
         "api": 8,
         "name": "Froyo",
-        "perc": "3.1"
+        "perc": "2.5"
+      },
+      {
+        "api": 9,
+        "name": "Gingerbread",
+        "perc": "0.1"
       },
       {
         "api": 10,
         "name": "Gingerbread",
-        "perc": "34.1"
+        "perc": "33.0"
       },
       {
         "api": 13,
@@ -178,17 +183,17 @@
       {
         "api": 15,
         "name": "Ice Cream Sandwich",
-        "perc": "23.3"
+        "perc": "22.5"
       },
       {
         "api": 16,
         "name": "Jelly Bean",
-        "perc": "32.3"
+        "perc": "34.0"
       },
       {
         "api": 17,
         "name": "Jelly Bean",
-        "perc": "5.6"
+        "perc": "6.5"
       }
     ]
   }
@@ -204,21 +209,21 @@
     "data": {
       "Large": {
         "hdpi": "0.4",
-        "ldpi": "0.6",
+        "ldpi": "0.5",
         "mdpi": "3.2",
-        "tvdpi": "1.0",
+        "tvdpi": "1.1",
         "xhdpi": "0.5"
       },
       "Normal": {
-        "hdpi": "34.9",
+        "hdpi": "34.5",
         "ldpi": "0.1",
-        "mdpi": "16.0",
-        "xhdpi": "24.0",
-        "xxhdpi": "4.9"
+        "mdpi": "15.9",
+        "xhdpi": "23.9",
+        "xxhdpi": "5.7"
       },
       "Small": {
         "hdpi": "0.1",
-        "ldpi": "9.9"
+        "ldpi": "9.7"
       },
       "Xlarge": {
         "hdpi": "0.2",
@@ -226,8 +231,8 @@
         "xhdpi": "0.1"
       }
     },
-    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chco=c4df9b%2C6fad0c&chd=t%3A10.7%2C23.3%2C1.0%2C35.6%2C24.6%2C4.9&chf=bg%2Cs%2C00000000&chs=400x250&cht=p",
-    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chco=c4df9b%2C6fad0c&chd=t%3A4.4%2C5.7%2C79.9%2C10.1&chf=bg%2Cs%2C00000000&chs=400x250&cht=p"
+    "densitychart": "//chart.googleapis.com/chart?cht=p&chs=400x250&chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A10.3%2C23.2%2C1.1%2C35.2%2C24.5%2C5.7&chco=c4df9b%2C6fad0c",
+    "layoutchart": "//chart.googleapis.com/chart?cht=p&chs=400x250&chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.4%2C5.7%2C80.2%2C9.8&chco=c4df9b%2C6fad0c"
   }
 ];
 
@@ -236,14 +241,14 @@
 var VERSION_NAMES =
 [
   {"api":0},{"api":1},{"api":2},{"api":3},
-  { 
+  {
     "api":4,
     "link":"<a href='/about/versions/android-1.6.html'>1.6</a>",
     "codename":"Donut",
   },
   { "api":5},
   { "api":6},
-  { 
+  {
     "api":7,
     "link":"<a href='/about/versions/android-2.1.html'>2.1</a>",
     "codename":"Eclair",
diff --git a/docs/html/about/versions/android-4.3.jd b/docs/html/about/versions/android-4.3.jd
index bccc9d5..e18c285 100644
--- a/docs/html/about/versions/android-4.3.jd
+++ b/docs/html/about/versions/android-4.3.jd
@@ -19,6 +19,7 @@
     <ol>
       <li><a href="#BehaviorsIntents">If your app uses implicit intents...</a></li>
       <li><a href="#BehaviorsAccounts">If your app depends on accounts...</a></li>
+      <li><a href="#BehaviorsVideoView">If your app uses VideoView...</a></li>
     </ol>
   </li>
   <li><a href="#RestrictedProfiles">Restricted Profiles</a>
@@ -213,6 +214,21 @@
 below about <a href="#AccountsInProfile">Supporting accounts in a restricted profile</a>.</p>
 
 
+<h3 id="BehaviorsVideoView">If your app uses VideoView...</h3>
+
+<p>Your video might appear smaller on Android 4.3.</p>
+
+<p>On previous versions of Android, the {@link android.widget.VideoView} widget incorrectly
+calculated the {@code "wrap_content"} value for {@link android.R.attr#layout_height} and {@link
+android.R.attr#layout_width} to be the same as {@code "match_parent"}. So while using {@code
+"wrap_content"} for the height or width may have previously provided your desired video layout,
+doing so may result in a much smaller video on Android 4.3 and higher. To fix the issue, replace
+{@code "wrap_content"} with {@code "match_parent"} and verify your video appears as expected on
+Android 4.3 as well as on older versions.</p>
+
+
+
+
 
 
 <h2 id="RestrictedProfiles">Restricted Profiles</h2>
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 419d5ea..78b8739 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -57,8 +57,8 @@
 policies</a>, the <a
 href="http://play.google.com/about/developer-distribution-agreement.html"
 target="_policies">developer agreement</a>,  and <a
-href="https://play.google.com/about/developer-distribution-agreement-addendum.
-html" target="_policies">Google Play for Education Addendum</a>.</p>
+href="https://play.google.com/about/developer-distribution-agreement-addendum.html"
+target="_policies">Google Play for Education Addendum</a>.</p>
 
 <h3 id="developing">2. Design and develop a great app for education</h3>
 
@@ -129,8 +129,8 @@
 href="http://play.google.com/about/developer-distribution-agreement.html"
 target="_policies">Developer Distribution Agreement</a>,
 including a <a
-href="https://play.google.com/about/developer-distribution-agreement-addendum.
-html" target="_policies">Google Play for Education
+href="https://play.google.com/about/developer-distribution-agreement-addendum.html"
+target="_policies">Google Play for Education
 Addendum</a>. If you are not familiar with these policy documents or the
 Addendum, make sure to read them before opting-in. </p>
 
diff --git a/docs/html/distribute/googleplay/publish/localizing.jd b/docs/html/distribute/googleplay/publish/localizing.jd
index a7f1976..29b27c8 100644
--- a/docs/html/distribute/googleplay/publish/localizing.jd
+++ b/docs/html/distribute/googleplay/publish/localizing.jd
@@ -111,8 +111,8 @@
 
 <p>In cases where your UI can't accommodate text in one of your target
 languages, you can create an <a
-href="{@docRoot}guide/topics/resources/providing-resources.
-html#AlternativeResources">alternative layout</a> for that language only.
+href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">alternative
+layout</a> for that language only.
 Android makes it easy to declare sets of layouts and other resources to load for
 specific languages, locales, screen sizes, and so on, simply by tagging them
 with the appropriate resource qualifiers. </p>
diff --git a/docs/html/distribute/googleplay/quality/tablet.jd b/docs/html/distribute/googleplay/quality/tablet.jd
index 36e7345..fe046d4 100644
--- a/docs/html/distribute/googleplay/quality/tablet.jd
+++ b/docs/html/distribute/googleplay/quality/tablet.jd
@@ -501,8 +501,11 @@
 
 <p>To ensure the broadest possible distribution to tablets, make sure that your
 app properly targets the Android versions that support tablets. Initial support for
-tablets was added in <a href="{@docRoot}about/versions/android-3.0">Android 3.0</a> (API level 11). Unified UI
-framework support for tablets, phones, and other devices was introduced in <a href="{@docRoot}about/versions/android-4.0">Android 4.0</a> (API level 14) and is supported in later versions.
+tablets was added in <a href="{@docRoot}about/versions/android-3.0.html">Android 3.0</a>
+(API level 11). Unified UI
+framework support for tablets, phones, and other devices was introduced in <a
+href="{@docRoot}about/versions/android-4.0.html">Android 4.0</a> (API level 14) and is
+supported in later versions.
 
 <p>You can set the app's
 range of targeted Android versions in the manifest file, in the
@@ -809,7 +812,7 @@
     </li>
     <li>
       <a href=
-      "{@docRoot}distribute/googleplay/promote/device-art.html">Device Art
+      "{@docRoot}distribute/promote/device-art.html">Device Art
       Generator</a>&mdash;Drag and drop tool that lets you instantly create production-
       ready art showing your app running on a tablet device. 
     </li>
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 0cadbd2..9c5961c 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -29,7 +29,7 @@
 <h2>See Also</h2>
 
 <ol class="toc">
-<li><a href="{@docRoot}google/play-services/gcm/gs.html">Getting Started</a></li>
+<li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li>
 <li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" target="_android">CCS and User Notifications Signup Form</a></li>
 </ol>
 
diff --git a/docs/html/google/gcm/notifications.jd b/docs/html/google/gcm/notifications.jd
index df171cf..5171850 100644
--- a/docs/html/google/gcm/notifications.jd
+++ b/docs/html/google/gcm/notifications.jd
@@ -29,7 +29,7 @@
 <h2>See Also</h2>
 
 <ol class="toc">
-<li><a href="{@docRoot}google/play-services/gcm/gs.html">Getting Started</a></li>
+<li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li>
 <li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" target="_android">CCS and User Notifications Signup Form</a></li>
 </ol>
 
diff --git a/docs/html/google/play-services/gcm.jd b/docs/html/google/play-services/gcm.jd
deleted file mode 100644
index a9da73f..0000000
--- a/docs/html/google/play-services/gcm.jd
+++ /dev/null
@@ -1,69 +0,0 @@
-page.title=GCM Extensions
-page.tags="cloud","push","messaging"
-header.hide=1
-@jd:body
-
-<div class="landing-banner">
-        
-<div class="col-6">
-  <img src="" alt="">
-</div>
-<div class="col-6">
-
-  <h1 itemprop="name" style="margin-bottom:0;">GCM Extensions for Android</h1>
-   <p itemprop="description">
-GCM extension APIs make it easier to take advantage of enhanced messaging capabilities in your apps, and they can help you simplify your implementation of Google Cloud Messaging.</p>
-
-<p>You can use GCM extensions in any new or existing GCM implementation to build powerful multi-device messaging and presence features for your users.</p>
-
-</div>
-</div>
-
-
-<div class="landing-docs">
-  <div class="col-6 normal-links">
-    <h3 style="clear:left">Key Developer Features</h3>
-
-    <h4>Faster, easier GCM setup</h4>
-    <p>Streamlined registration makes it simple and fast to add GCM support to your Android app. <a href="{@docRoot}google/play-services/gcm/gs.html">Learn more &raquo;</a></p>
-
-
-    <h4>Bidirectional messaging over XMPP</h4>
-    <p>GCM's Cloud Connection Service (CCS) lets you communicate with Android devices over a persistent XMPP connection. Communication is asynchronous and bidirectional, and you can use the service in tandem with existing GCM APIs. You can use <a href="https://services.google.com/fb/forms/gcm/">this form</a> to sign up for CCS. <a href="{@docRoot}google/gcm/ccs.html">Learn more &raquo;</a></p>
-
-   <!-- <p>To get started, sign up using <a href="https://services.google.com/fb/forms/gcm/">this form</a> and then learn how to <a href="{@docRoot}google/gcm/ccs.html">send XMPP messages</a>.</p> -->
-    
-    <h4>Seamless multi-device messaging</h4>
-    <p>New user notifications let you send a single message simultaneously to all of a user's Android devices. </p>
-
-    <p>GCM lets you map all of a user's multiple devices to a single notification key, which you can then reference as the target for messages that you are sending to that user. 
-    <a href="{@docRoot}google/gcm/notifications.html">Learn more &raquo;</a></p>
-    </a>
-    
-
-  </div>
-
-
-  <div class="col-6 normal-links">
-    <h3 style="clear:left">Getting Started</h3>
-    <h4>1. Get the Google Play services SDK</h4>
-    <p>The GCM Extension APIs are part of the Google Play services platform. To use the APIs, <a href="{@docRoot}google/play-services/setup.html">set up
-      the Google Play services SDK</a>. 
-    </p>
-            
-    <h4>2. Create a Google APIs project</h4>
-    
-    <p>To use GCM, you need to set up a Google APIs project and get an application key. If you are already using GCM, you can use your existing project and key. <a href="{@docRoot}google/play-services/gcm/gs.html#google_apis">Learn more &raquo;</a></p>
-
-    <h4>3. Set up GCM in your app</h4>
-    
-    <p>To send and receive messages over GCM, you need to update the manifest and add code to register with GCM and handle messages. See <a href="{@docRoot}google/play-services/gcm/gs.html#manifest">Get Started</a> for details.
-    </p>
-    
-    <h4>4. Integrate GCM with your backend servers</h4>
-
-    <p>A complete GCM implementation requires a server-side implementation, in addition to the client implementation in your app. For complete information, make sure to read the <a href="{@docRoot}google/gcm/index.html">Google Cloud Messaging documentation</a>. 
-
-  </div>
-
-</div>
diff --git a/docs/html/google/play/billing/v2/billing_subscriptions.jd b/docs/html/google/play/billing/v2/billing_subscriptions.jd
index db18a53..9c86e20 100644
--- a/docs/html/google/play/billing/v2/billing_subscriptions.jd
+++ b/docs/html/google/play/billing/v2/billing_subscriptions.jd
@@ -11,12 +11,12 @@
         <li><a href="#model">Application Model</a></li>
         <li><a href="#token">Purchase Token</a></li>
         <li><a href="#version">Checking the In-app Billing API Version</a></li>
-        <li><a href="purchase">Purchasing a Subscription</a></li>
+        <li><a href="#purchase">Purchasing a Subscription</a></li>
         <li><a href="#restore">Restoring Transactions</a></li>
         <li><a href="#validity">Checking Subscription Validity</a></li>
         <li><a href="#viewstatus">Letting Users Cancel or View Status</a></li>
         <li><a href="#purchase-state-changes">Recurring Billing and Changes in Purchase State</a></li>
-        <li><a href="modifying">Modifying Your App for Subscriptions</a></li>
+        <li><a href="#modifying">Modifying Your App for Subscriptions</a></li>
    </ol>
 </div>
 </div>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index db08c3e..2a31374 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -146,9 +146,6 @@
           <li><a href="<?cs var:toroot ?>guide/topics/ui/layout/grid.html">
               <span class="en">Grid Layout</span>
             </a></li>
-          <li><a href="<?cs var:toroot ?>guide/topics/ui/layout/tabs.html">
-              <span class="en">Tab Layout</span>
-            </a></li>
        -->
           <li><a href="<?cs var:toroot ?>guide/topics/ui/layout/listview.html">
               <span class="en">List View</span>
diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd
index 10ab61f..3173ff1 100644
--- a/docs/html/guide/topics/ui/actionbar.jd
+++ b/docs/html/guide/topics/ui/actionbar.jd
@@ -1,5 +1,5 @@
 page.title=Action Bar
-page.tags="actionbar","menu"
+page.tags="actionbar","menu","tabs"
 
 @jd:body
 
diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd
index 6398646..6586c2f 100644
--- a/docs/html/guide/topics/ui/declaring-layout.jd
+++ b/docs/html/guide/topics/ui/declaring-layout.jd
@@ -359,23 +359,6 @@
   <p>Enables you to specify the location of child objects relative to each other (child A to
 the left of child B) or to the parent (aligned to the top of the parent).</p>
 </div>
-      
-<!--
-<div class="layout">
-  <h4><a href="layout/tabs.html">Tabs</a></h4>
-  <a href="layout/tabs.html"><img src="{@docRoot}images/ui/tabs-small.png" alt="" /></a>
-  <p>Provides a tab selection list that monitors clicks and enables the application to change
-the screen whenever a tab is clicked.</p>
-</div>
-      
-<div class="layout first">
-  <h4><a href="layout/grid.html">Table Layout</a></h4>
-  <a href="layout/table.html"><img src="{@docRoot}images/ui/gridlayout-small.png" alt="" /></a>
-  <p>A tabular layout with an arbitrary number of rows and columns, each cell holding the
-widget of your choice. The rows resize to fit the largest column. The cell borders are not
-visible.</p>
-</div>
--->
 
 <div class="layout">
   <h4><a href="{@docRoot}guide/webapps/webview.html">Web View</a></h4>
diff --git a/docs/html/guide/topics/ui/layout/tabs.jd b/docs/html/guide/topics/ui/layout/tabs.jd
deleted file mode 100644
index 62663de..0000000
--- a/docs/html/guide/topics/ui/layout/tabs.jd
+++ /dev/null
@@ -1,219 +0,0 @@
-page.title=Tabbed
-parent.title=Layouts
-parent.link=layout-objects.html
-@jd:body
-<div id="qv-wrapper">
-<div id="qv">
-<h2>In this document</h2>
-  <ol>
-    <li><a href="#example">Example</a></li>
-  </ol>
-  <h2>Key classes</h2>
-  <ol>
-<li>{@link android.widget.TabWidget}</li>
-<li>{@link android.widget.TabHost}</li>
-<li>{@link android.widget.TabHost.TabSpec}</li>
-<li>{@link android.widget.FrameLayout}</li>
-  </ol>
-</div>
-</div>
-<p>To create a tabbed UI, you need to use a {@link android.widget.TabHost} and a {@link
-android.widget.TabWidget}. The {@link android.widget.TabHost} must be the root node for the layout,
-which contains both the {@link android.widget.TabWidget} for displaying the tabs and a {@link
-android.widget.FrameLayout} for displaying the tab content.</p>
-
-<img src="{@docRoot}images/ui/tabs.png" alt="" />
-
-<p>You can implement your tab content in one of two ways: use the tabs to swap
-{@link android.view.View}s within the same {@link android.app.Activity}, or use the tabs to change
-between entirely separate activities. Which method you want for your application will depend on your
-demands, but if each tab provides a distinct user activity, then it probably makes sense to use
-a separate {@link android.app.Activity} for each tab, so that you can better manage the application
-in discrete groups, rather than one massive application and layout.</p>
-<h2 id="example">Example</h2>
-<p>In this tutorial, you'll create a tabbed UI that uses a separate {@link
-android.app.Activity} for each tab.</p>
-
-<ol>
-  <li>Start a new project named <em>HelloTabWidget</em>.</li>
-  <li>First, create three separate {@link android.app.Activity} classes in your project:
-<code>ArtistsActivity</code>, <code>AlbumsActivity</code>, and <code>SongsActivity</code>. These
-will each represent a separate tab. For now, make each one display a simple message using a {@link
-android.widget.TextView}. For example:
-<pre>
-public class ArtistsActivity extends Activity {
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        TextView textview = new TextView(this);
-        textview.setText("This is the Artists tab");
-        setContentView(textview);
-    }
-}
-</pre>
-  <p>Notice that this doesn't use a layout file. Just create a {@link
-android.widget.TextView}, give it some text and set that as the content. Duplicate this for
-each of the three activities, and add the corresponding <code>&lt;activity/&gt;</code> tags to the Android Manifest file.</p>
-
-  <li>You need an icon for each of your tabs. For each icon, you should create two versions: one
-for when the tab is selected and one for when it is unselected. The
-general design recommendation is for the selected icon to be a dark color (grey), and the
-unselected icon to be a light color (white). (See the <a
-href="{@docRoot}guide/practices/ui_guidelines/icon_design.html#tabstructure">Icon Design
-Guidelines</a>.) For example:
-  <p>
-  <img src="images/ic_tab_artists_white.png" title="unselected tab icon"  alt="" />
-  <img src="images/ic_tab_artists_grey.png" title="selected tab icon" alt="" />
-  </p>
-  <p>For this tutorial, you can copy these images and use them for all three tabs. (When you
-create tabs in your own application, you should create customized tab icons.)</p>
-  <p>Now create a <a
-href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">state-list drawable</a>
-that specifies which image to use for each tab state:</p>
-  <ol>
-    <li>Save the icon images in your project <code>res/drawable/</code> directory.</li>
-    <li>Create a new XML file in <code>res/drawable/</code>
-named <code>ic_tab_artists.xml</code> and insert the following:
-<pre>
-&lt;?xml version="1.0" encoding="utf-8"?>
-&lt;selector xmlns:android="http://schemas.android.com/apk/res/android">
-    &lt;!-- When selected, use grey -->
-    &lt;item android:drawable="@drawable/ic_tab_artists_grey"
-          android:state_selected="true" />
-    &lt;!-- When not selected, use white-->
-    &lt;item android:drawable="@drawable/ic_tab_artists_white" />
-&lt;/selector>
-</pre>
-  <p>This is a <a
-href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">state-list drawable</a>,
-which you will apply as the tab image. When the tab state changes, the tab icon will
-automatically switch between the images defined here.</p>
-    </li>
-  </ol>
-  </li>
-
-  <li>Open the <code>res/layout/main.xml</code> file and insert the following:
-  <pre>
-&lt;?xml version="1.0" encoding="utf-8"?>
-&lt;TabHost xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/tabhost"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent">
-    &lt;LinearLayout
-        android:orientation="vertical"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:padding="5dp">
-        &lt;TabWidget
-            android:id="@android:id/tabs"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content" />
-        &lt;FrameLayout
-            android:id="@android:id/tabcontent"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
-            android:padding="5dp" />
-    &lt;/LinearLayout>
-&lt;/TabHost>
-</pre>
-    <p>This is the layout that will display the tabs and provide navigation between each {@link
-    android.app.Activity} created above.</p>
-    <p>The {@link android.widget.TabHost} requires that a {@link android.widget.TabWidget} and a
-{@link android.widget.FrameLayout} both live somewhere within it. To position the {@link
-android.widget.TabWidget} and {@link android.widget.FrameLayout} vertically, a {@link
-android.widget.LinearLayout} is used. The {@link android.widget.FrameLayout} is where the content
-for each tab goes, which is empty now because the {@link android.widget.TabHost} will automatically
-embed each {@link android.app.Activity} within it.</p>
-    <p>Notice that the {@link android.widget.TabWidget} and the {@link android.widget.FrameLayout}
-    elements have the IDs {@code tabs} and {@code tabcontent}, respectively. These names
-    must be used so that the {@link android.widget.TabHost} can retrieve references to each of
-    them. It expects exactly these names.</p>
-  </li>
-
-  <li>Now open <code>HelloTabWidget.java</code> and make it extend {@link
-  android.app.TabActivity}:</p>
-<pre>
-public class HelloTabWidget extends TabActivity {
-</pre>
-  </li>
-  <li>Use the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
-  method:
-<pre>
-public void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    setContentView(R.layout.main);
-
-    Resources res = getResources(); // Resource object to get Drawables
-    TabHost tabHost = getTabHost();  // The activity TabHost
-    TabHost.TabSpec spec;  // Resusable TabSpec for each tab
-    Intent intent;  // Reusable Intent for each tab
-
-    // Create an Intent to launch an Activity for the tab (to be reused)
-    intent = new Intent().setClass(this, ArtistsActivity.class);
-
-    // Initialize a TabSpec for each tab and add it to the TabHost
-    spec = tabHost.newTabSpec("artists").setIndicator("Artists",
-                      res.getDrawable(R.drawable.ic_tab_artists))
-                  .setContent(intent);
-    tabHost.addTab(spec);
-
-    // Do the same for the other tabs
-    intent = new Intent().setClass(this, AlbumsActivity.class);
-    spec = tabHost.newTabSpec("albums").setIndicator("Albums",
-                      res.getDrawable(R.drawable.ic_tab_albums))
-                  .setContent(intent);
-    tabHost.addTab(spec);
-
-    intent = new Intent().setClass(this, SongsActivity.class);
-    spec = tabHost.newTabSpec("songs").setIndicator("Songs",
-                      res.getDrawable(R.drawable.ic_tab_songs))
-                  .setContent(intent);
-    tabHost.addTab(spec);
-
-    tabHost.setCurrentTab(2);
-}
-</pre>
-    <p>This sets up each tab with their text and icon, and assigns each one an {@link
-android.app.Activity}.</p>
-    <p>A reference to the {@link android.widget.TabHost} is first captured with {@link
-android.app.TabActivity#getTabHost()}. Then, for
-each tab, a {@link android.widget.TabHost.TabSpec} is created to define the tab properties. The
-{@link android.widget.TabHost#newTabSpec(String)} method creates a new {@link
-android.widget.TabHost.TabSpec} identified by the given string tag. For each
-{@link android.widget.TabHost.TabSpec}, {@link
-android.widget.TabHost.TabSpec#setIndicator(CharSequence,Drawable)} is called to set the text and
-icon for the tab, and {@link android.widget.TabHost.TabSpec#setContent(Intent)} is called to specify
-the {@link android.content.Intent} to open the appropriate {@link android.app.Activity}. Each
-{@link android.widget.TabHost.TabSpec} is then added to the {@link android.widget.TabHost} by
-calling {@link android.widget.TabHost#addTab(TabHost.TabSpec)}.</p>
-
-    <p>At the very end, {@link
-    android.widget.TabHost#setCurrentTab(int)} opens the tab to be displayed by default, specified
-    by the index position of the tab.</p>
-
-    <p>Notice that not once was the {@link android.widget.TabWidget} object referenced. This is
-    because a {@link android.widget.TabWidget} must always be a child of a {@link
-    android.widget.TabHost}, which is what you use for almost all interaction with the tabs. So when
-    a tab is added to the {@link android.widget.TabHost}, it's automatically added to the child
-    {@link android.widget.TabWidget}.</p>
-  </li>
-
-  <li>Now open the Android Manifest file and add the <code>NoTitleBar</code> theme to the
-<em>HelloTabWidget</em>'s
-  <code>&lt;activity></code> tag. This will remove the default application title from the top
-  of the layout, leaving more space for the tabs, which effectively operate as their own titles.
-  The <code>&lt;activity></code> tag should look like this:
-<pre>
-&lt;activity android:name=".HelloTabWidget" android:label="@string/app_name"
-          android:theme="&#64;android:style/Theme.NoTitleBar">
-</pre>
-  </li>
-
-  <li>Run the application.</li>
-</ol>
-
-
-<p>Your application should look like this (though your icons may be different):</p>
-<img src="images/hello-tabwidget.png" width="150px" />
-
-
diff --git a/docs/html/images/training/basics/actionbar-actions.png b/docs/html/images/training/basics/actionbar-actions.png
new file mode 100644
index 0000000..5195a8c
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-actions.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-basic.png b/docs/html/images/training/basics/actionbar-basic.png
new file mode 100644
index 0000000..ba77659
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-basic.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-overlay@2x.png b/docs/html/images/training/basics/actionbar-overlay@2x.png
new file mode 100644
index 0000000..7014a19
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-overlay@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-custom-tabs@2x.png b/docs/html/images/training/basics/actionbar-theme-custom-tabs@2x.png
new file mode 100644
index 0000000..ea8c93b
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-custom-tabs@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-custom@2x.png b/docs/html/images/training/basics/actionbar-theme-custom@2x.png
new file mode 100644
index 0000000..bb6f1a4
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-custom@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-dark-solid@2x.png b/docs/html/images/training/basics/actionbar-theme-dark-solid@2x.png
new file mode 100644
index 0000000..bd16ffe
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-dark-solid@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-dark@2x.png b/docs/html/images/training/basics/actionbar-theme-dark@2x.png
new file mode 100644
index 0000000..88c3b22
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-dark@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-light-darkactionbar@2x.png b/docs/html/images/training/basics/actionbar-theme-light-darkactionbar@2x.png
new file mode 100644
index 0000000..5509e16
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-light-darkactionbar@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-light-solid@2x.png b/docs/html/images/training/basics/actionbar-theme-light-solid@2x.png
new file mode 100644
index 0000000..810ef46
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-light-solid@2x.png
Binary files differ
diff --git a/docs/html/images/training/basics/actionbar-theme-light@2x.png b/docs/html/images/training/basics/actionbar-theme-light@2x.png
new file mode 100644
index 0000000..a0af43a
--- /dev/null
+++ b/docs/html/images/training/basics/actionbar-theme-light@2x.png
Binary files differ
diff --git a/docs/html/sitemap.txt b/docs/html/sitemap.txt
index 6291a3e..3a416f9 100644
--- a/docs/html/sitemap.txt
+++ b/docs/html/sitemap.txt
@@ -362,7 +362,6 @@
 http://developer.android.com/training/basics/supporting-devices/languages.html
 http://developer.android.com/training/basics/supporting-devices/screens.html
 http://developer.android.com/training/basics/supporting-devices/platforms.html
-http://developer.android.com/training/basics/fragments/support-lib.html
 http://developer.android.com/training/basics/fragments/creating.html
 http://developer.android.com/training/basics/fragments/fragment-ui.html
 http://developer.android.com/training/basics/fragments/communicating.html
diff --git a/docs/html/tools/support-library/setup.jd b/docs/html/tools/support-library/setup.jd
index 6cca897..73d9468 100644
--- a/docs/html/tools/support-library/setup.jd
+++ b/docs/html/tools/support-library/setup.jd
@@ -10,7 +10,7 @@
     <ol>
       <li><a href="#download">Downloading the Support Library</a></li>
       <li><a href="#choosing">Choosing Support Libraries</a></li>
-      <li><a href="add-library">Adding Support Libraries</a>
+      <li><a href="#add-library">Adding Support Libraries</a>
         <ol>
           <li><a href="#libs-without-res">Adding libraries without resources</a></li>
           <li><a href="#libs-with-res">Adding libraries with resources</a></li>
@@ -90,9 +90,9 @@
   each Support Library you want to use.</p>
 
 <p>Some Support Libraries contain resources beyond compiled code classes, such as images or XML
-  files. For example, the <a href="tools/support-library/features.html#v7-appcompat">v7
-  appcompat</a> and <a href="tools/support-library/features.html#v7-gridlayout">v7 gridlayout</a>
-  libraries include resources.</p>
+  files. For example, the <a href="{@docRoot}tools/support-library/features.html#v7-appcompat">v7
+  appcompat</a> and <a href="{@docRoot}tools/support-library/features.html#v7-gridlayout">v7
+  gridlayout</a> libraries include resources.</p>
 
 <p>If you are not sure if a library contains resources, check the
   <a href="{@docRoot}tools/support-library/features.html">Support Library Features</a> page.
@@ -149,7 +149,9 @@
 
 <h3 id="libs-with-res">Adding libraries with resources</h3>
 
-<p>To add a Support Library with resources to your application project:</p>
+<p>To add a Support Library with resources (such as
+  <a href="{@docRoot}tools/support-library/features.html#v7-appcompat">v7
+  appcompat</a> for action bar) to your application project:</p>
 
 <div class="toggle-content closed">
   <p style="margin-top:5px"><a href="#" onclick="return toggleContent(this)">
diff --git a/docs/html/training/basics/actionbar/adding-buttons.jd b/docs/html/training/basics/actionbar/adding-buttons.jd
new file mode 100644
index 0000000..5fb0d59
--- /dev/null
+++ b/docs/html/training/basics/actionbar/adding-buttons.jd
@@ -0,0 +1,210 @@
+page.title=Adding Action Buttons
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#XML">Specify the Actions in XML</a></li>
+  <li><a href="#AddActions">Add the Actions to the Action Bar</a></li>
+  <li><a href="#Respond">Respond to Action Buttons</a></li>
+  <li><a href="#UpNav">Add Up Button for Low-level Activities</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/implementing-navigation/ancestral.html">Providing Up
+  Navigation</a></li>
+  </div>
+</div>
+
+
+
+<p>The action bar allows you to add buttons for the most important action
+items relating to the app's current
+context. Those that appear directly in the action bar with an icon and/or text are known
+as <em>action buttons</em>. Actions that can't fit in the action bar or aren't
+important enough are hidden in the action overflow.</p>
+
+<img src="{@docRoot}images/training/basics/actionbar-actions.png" height="100" alt=""/>
+<p class="img-caption"><strong>Figure 1.</strong> An action bar with an action button
+for Search and the action overflow, which reveals additional actions.</a>
+
+
+<h2 id="XML">Specify the Actions in XML</h2>
+
+<p>All action buttons and other items available in the action overflow are defined
+in an XML <a
+href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. To add
+actions to the action bar, create a new XML file in your project's
+{@code res/menu/} directory.</p>
+
+<p>Add an {@code &lt;item>} element for each item you want to include in the action bar.
+For example:</p>
+
+<p class="code-caption">res/menu/main_activity_actions.xml</p>
+<pre>
+&lt;menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    &lt;!-- Search, should appear as action button -->
+    &lt;item android:id="@+id/action_search"
+          android:icon="@drawable/ic_action_search"
+          android:title="@string/action_search"
+          android:showAsAction="ifRoom" /&gt;
+    &lt;!-- Settings, should always be in the overflow -->
+    &lt;item android:id="@+id/action_settings"
+          android:title="@string/action_settings"
+          android:showAsAction="never" /&gt;
+&lt;/menu&gt;
+</pre>
+
+<div class="sidebox">
+<h3>Download action bar icons</h3>
+<p>To best match the Android <a
+href="{@docRoot}design/style/iconography.html#action-bar">iconography</a> guidelines, you should
+use icons provided in the
+<a href="{@docRoot}design/downloads/index.html#action-bar-icon-pack">Action Bar Icon Pack</a>.</p>
+</div>
+
+<p>This declares that the Search action should appear as an action button when room
+is available in the action bar, but the
+Settings action should always appear in the overflow. (By default, all actions appear in the
+overflow, but it's good practice to explicitly declare your design intentions for each action.)
+
+<p>However, <strong>if your app is using the Support Library</strong> for compatibility on versions
+as low as Android 2.1, the {@code showAsAction} attribute is not available from
+the {@code android:} namespace. Instead this attribute is provided by the Support Library
+and you must define your own XML namespace and use that namespace as the attribute prefix.
+(A custom XML namespace should be based on your app name, but it can be any
+name you want and is only accessible within the scope of the file in which you declare it.)
+For example:</p>
+
+<p class="code-caption">res/menu/main_activity_actions.xml</p>
+<pre>
+&lt;menu xmlns:android="http://schemas.android.com/apk/res/android"
+      <strong>xmlns:yourapp="http://schemas.android.com/apk/res-auto"</strong> >
+    &lt;!-- Search, should appear as action button -->
+    &lt;item android:id="@+id/action_search"
+          android:icon="@drawable/ic_action_search"
+          android:title="@string/action_search"
+          <strong>yourapp:showAsAction="ifRoom"</strong>  /&gt;
+    ...
+&lt;/menu&gt;
+</pre>
+
+
+
+<h2 id="AddActions">Add the Actions to the Action Bar</h2>
+
+<p>To place the menu items into the action bar, implement the
+{@link android.app.Activity#onCreateOptionsMenu onCreateOptionsMenu()} callback
+method in your activity to inflate the menu resource into the given {@link android.view.Menu}
+object. For example:</p>
+
+<pre>
+&#64;Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    // Inflate the menu items for use in the action bar
+    MenuInflater inflater = getMenuInflater();
+    inflater.inflate(R.menu.main_activity_actions, menu);
+    return super.onCreateOptionsMenu(menu);
+}
+</pre>
+
+
+
+<h2 id="Respond">Respond to Action Buttons</h2>
+
+<p>When the user presses one of the action buttons or another item in the action overflow,
+the system calls your activity's {@link android.app.Activity#onOptionsItemSelected
+onOptionsItemSelected()} callback method. In your implementation of this method,
+call {@link android.view.MenuItem#getItemId getItemId()} on the given {@link android.view.MenuItem} to
+determine which item was pressed&mdash;the returned ID matches the value you declared in the
+corresponding {@code &lt;item>} element's {@code android:id} attribute.</p>
+
+<pre>
+&#64;Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle presses on the action bar items
+    switch (item.getItemId()) {
+        case R.id.action_search:
+            openSearch();
+            return true;
+        case R.id.action_settings:
+            openSettings();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+</pre>
+
+
+
+<h2 id="UpNav">Add Up Button for Low-level Activities</h2>
+
+<div class="figure" style="width:240px">
+  <img src="{@docRoot}images/ui/actionbar-up.png" width="240" alt="">
+  <p class="img-caption"><strong>Figure 4.</strong> The <em>Up</em> button in Gmail.</p>
+</div>
+
+<p>All screens in your app that are not the main entrance to your app
+(activities that are not the "home" screen) should
+offer the user a way to navigate to the logical parent screen in the app's hierarchy by pressing
+the <em>Up</em> button in the action bar.</p>
+
+<p>When running on Android 4.1 (API level 16) or higher, or when using {@link
+android.support.v7.app.ActionBarActivity} from the Support Library, performing <em>Up</em>
+navigation simply requires that you declare the parent activity in the manifest file and enable
+the <em>Up</em> button for the action bar.</p>
+
+<p>For example, here's how you can declare an activity's parent in the manifest:</p>
+
+<pre>
+&lt;application ... >
+    ...
+    &lt;!-- The main/home activity (it has no parent activity) -->
+    &lt;activity
+        android:name="com.example.myfirstapp.MainActivity" ...>
+        ...
+    &lt;/activity>
+    &lt;!-- A child of the main activity -->
+    &lt;activity
+        android:name="com.example.myfirstapp.DisplayMessageActivity"
+        android:label="@string/title_activity_display_message"
+        android:parentActivityName="com.example.myfirstapp.MainActivity" >
+        &lt;!-- Parent activity meta-data to support 4.0 and lower -->
+        &lt;meta-data
+            android:name="android.support.PARENT_ACTIVITY"
+            android:value="com.example.myfirstapp.MainActivity" />
+    &lt;/activity>
+&lt;/application>
+</pre>
+
+  <p>Then enable the app icon as the <em>Up</em> button by calling
+{@link android.app.ActionBar#setDisplayHomeAsUpEnabled setDisplayHomeAsUpEnabled()}:</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_displaymessage);
+
+    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+    // If your minSdkVersion is 11 or higher, instead use:
+    // getActionBar().setDisplayHomeAsUpEnabled(true);
+}
+</pre>
+
+<p>Because the system now knows {@code MainActivity} is the parent activity for
+{@code DisplayMessageActivity}, when the user presses the
+<em>Up</em> button, the system navigates to
+the parent activity as appropriate&mdash;you <strong>do not</strong> need to handle the
+<em>Up</em> button's event.</p>
+
+<p>For more information about up navigation, see
+<a href="{@docRoot}training/implementing-navigation/ancestral.html">Providing Up
+  Navigation</a>.
\ No newline at end of file
diff --git a/docs/html/training/basics/actionbar/index.jd b/docs/html/training/basics/actionbar/index.jd
new file mode 100644
index 0000000..f0de758
--- /dev/null
+++ b/docs/html/training/basics/actionbar/index.jd
@@ -0,0 +1,68 @@
+page.title=Adding the Action Bar
+page.tags="actionbar"
+
+trainingnavtop=true
+startpage=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+<ul>
+  <li>Android 2.1 or higher</li>
+</ul>
+
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li>
+  <li><a href="{@docRoot}training/implementing-navigation/index.html">Implementing
+  Effective Navigation</a></li>
+</ul>
+
+</div>
+</div>
+
+<a class="notice-designers wide" href="{@docRoot}design/patterns/actionbar.html">
+  <div>
+    <h3>Design Guide</h3>
+    <p>Action Bar</p>
+  </div>
+</a>
+
+<p>The action bar is one of the most important design elements you can implement for your
+app's activities. It provides several user interface features that make your app immediately
+familiar to users by offering consistency between other Android apps. Key functions include:</p>
+
+<ul>
+  <li>A dedicated space for giving your app an identity and indicating the user's location
+  in the app.</li>
+  <li>Access to important actions in a predictable way (such as Search).</li>
+  <li>Support for navigation and view switching (with tabs or drop-down lists).</li>
+</ul>
+
+<img src="{@docRoot}images/training/basics/actionbar-actions.png" height="100" alt="">
+
+<p>This training class offers a quick guide to the action bar's basics. For more information
+about action bar's various features, see the
+<a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> guide.</p>
+
+
+<h2>Lessons</h2>
+
+<dl>
+  <dt><b><a href="setting-up.html">Setting Up the Action Bar</a></b></dt>
+  <dd>Learn how to add a basic action bar to your activity, whether your app
+  supports only Android 3.0 and higher or also supports versions as low as Android 2.1
+  (by using the Android Support Library).</dd>
+  <dt><b><a href="adding-buttons.html">Adding Action Buttons</a></b></dt>
+  <dd>Learn how to add and respond to user actions in the action bar.</dd>
+  <dt><b><a href="styling.html">Styling the Action Bar</a></b></dt>
+  <dd>Learn how to customize the appearance of your action bar.</dd>
+  <dt><b><a href="overlaying.html">Overlaying the Action Bar</a></b></dt>
+  <dd>Learn how to overlay the action bar in front of your layout, allowing for
+  seamless transitions when hiding the action bar.</dd>
+</dl>
+
diff --git a/docs/html/training/basics/actionbar/overlaying.jd b/docs/html/training/basics/actionbar/overlaying.jd
new file mode 100644
index 0000000..800cd44
--- /dev/null
+++ b/docs/html/training/basics/actionbar/overlaying.jd
@@ -0,0 +1,141 @@
+page.title=Overlaying the Action Bar
+
+trainingnavtop=true
+
+@jd:body
+
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#EnableOverlay">Enable Overlay Mode</a>
+    <ol>
+      <li><a href="#Overlay11">For Android 3.0 and higher only</a></li>
+      <li><a href="#Overlay7">For Android 2.1 and higher</a></li>
+    </ol>
+  </li>
+  <li><a href="#TopMargin">Specify Layout Top-margin</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}guide/topics/ui/themes.html">Styles and Themes</a></li>
+</ul>
+  </div>
+</div>
+
+
+<p>By default, the action bar appears at the top of your activity window,
+slightly reducing the amount of space available for the rest of your activity's layout.
+If, during the course of user interaction, you want to hide and show the action bar, you can do so
+by calling {@link android.app.ActionBar#hide()} and
+{@link android.app.ActionBar#show()} on the {@link android.app.ActionBar}. However,
+this causes your activity to recompute and redraw the layout based on its new size.</p>
+
+
+<div class="figure" style="width:280px">
+  <img src="{@docRoot}images/training/basics/actionbar-overlay@2x.png" width="280" alt="" />
+  <p class="img-caption"><strong>Figure 1.</strong> Gallery's action bar in overlay mode.</p>
+</div>
+
+<p>To avoid resizing your layout when the action bar hides and shows, you can enable <em>overlay
+mode</em> for the action bar. When in overlay mode, your activity layout uses all the space
+available as if the action bar is not there and the system draws the action bar in front of
+your layout. This obscures some of the layout at the top, but now when the action bar hides or
+appears, the system does not need to resize your layout and the transition is seamless.</p>
+
+<p class="note"><strong>Tip:</strong>
+If you want your layout to be partially visible behind the action bar, create a custom
+style for the action bar with a partially transparent background, such as the one shown
+in figure 1. For information about how to define the action bar background, read
+<a href="{@docRoot}training/basics/actionbar/styling.html">Styling the Action Bar</a>.</p>
+
+
+<h2 id="EnableOverlay">Enable Overlay Mode</h2>
+
+<p>To enable overlay mode for the action bar, you need to create a custom theme that
+extends an existing action bar theme and set the {@code android:windowActionBarOverlay} property to
+{@code true}.</p>
+
+
+<h3 id="Overlay11">For Android 3.0 and higher only</h3>
+
+<p>If your
+<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a>
+is set to {@code 11} or higher, your custom theme should use
+{@link android.R.style#Theme_Holo Theme.Holo} theme (or one of its descendants) as your parent
+theme. For example:</p>
+
+<pre>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;android:style/Theme.Holo">
+        &lt;item name="android:windowActionBarOverlay">true&lt;/item>
+    &lt;/style>
+&lt;/resources>
+</pre>
+
+
+<h3 id="Overlay7">For Android 2.1 and higher</h3>
+
+<p>If your app is using the Support Library for compatibility on devices
+running versions lower than Android 3.0, your custom theme should use
+{@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} theme
+(or one of its descendants) as your parent theme. For example:</p>
+
+<pre>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;android:style/Theme.<strong>AppCompat</strong>">
+        &lt;item name="android:windowActionBarOverlay">true&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="windowActionBarOverlay">true&lt;/item>
+    &lt;/style>
+&lt;/resources>
+</pre>
+
+<p>Also notice that this theme includes two definitions for the {@code windowActionBarOverlay}
+style: one with the {@code android:} prefix and one without. The one with the {@code android:}
+prefix is for versions of Android that include the style in the platform and the one
+without the prefix is for older versions that read the style from the Support Library.</p>
+
+
+
+
+
+<h2 id="TopMargin">Specify Layout Top-margin</h2>
+
+<p>When the action bar is in overlay mode, it might obscure some of your layout that should
+remain visible. To ensure that such items remain below the action bar at all times,
+add either margin or padding to the top of the view(s)
+using the height specified by {@link android.R.attr#actionBarSize}. For example:</p>
+
+<pre>
+&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="?android:attr/actionBarSize">
+    ...
+&lt;/RelativeLayout>
+</pre>
+
+<p>If you're using the Support Library for the action bar, you need to remove the
+{@code android:} prefix. For example:</p>
+
+<pre>
+&lt;!-- Support library compatibility -->
+&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="?attr/actionBarSize">
+    ...
+&lt;/RelativeLayout>
+</pre>
+
+<p>In this case, the {@code ?attr/actionBarSize} value without the
+prefix works on all versions, including Android 3.0 and higher.</p>
\ No newline at end of file
diff --git a/docs/html/training/basics/actionbar/setting-up.jd b/docs/html/training/basics/actionbar/setting-up.jd
new file mode 100644
index 0000000..158ce92
--- /dev/null
+++ b/docs/html/training/basics/actionbar/setting-up.jd
@@ -0,0 +1,112 @@
+page.title=Setting Up the Action Bar
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#ApiLevel11">Support Android 3.0 and Above Only</a></li>
+  <li><a href="#ApiLevel7">Support Android 2.1 and Above</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}tools/support-library/setup.html"
+>Setting Up the Support Library</a></li>
+</ul>
+  </div>
+</div>
+
+
+<p>In its most basic form, the action bar displays the title for the activity
+and the app icon on the left. Even in this simple form, the action bar
+is useful for all activities to inform
+users about where they are and to maintain a consistent identity for your app.</p>
+
+<img src="{@docRoot}images/training/basics/actionbar-basic.png" height="100" alt=""/>
+<p class="img-caption"><strong>Figure 1.</strong> An action bar with the app icon and
+activity title.</a>
+
+<p>Setting up a basic action bar requires that your app use an activity theme that enables
+the action bar. How to request such a theme depends on which version of Android is the
+lowest supported by your app. So this
+lesson is divided into two sections depending on which Android
+version is your lowest supported.</p>
+
+
+<h2 id="ApiLevel11">Support Android 3.0 and Above Only</h2>
+
+<p>Beginning with Android 3.0 (API level 11), the action bar is included in all
+activities that use the {@link android.R.style#Theme_Holo Theme.Holo} theme (or one of its
+descendants), which is the default theme when either the <a
+href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> or
+<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a>
+attribute is set to <code>"11"</code> or greater.</p>
+
+<p>So to add the action bar to your activities, simply set either attribute to
+{@code 11} or higher. For example:</p>
+
+<pre>
+&lt;manifest ... &gt;
+    &lt;uses-sdk android:minSdkVersion="11" ... /&gt;
+    ...
+&lt;/manifest&gt;
+</pre>
+
+<p class="note"><strong>Note:</strong> If you've created a custom theme, be sure it uses one
+of the {@link android.R.style#Theme_Holo Theme.Holo} themes as its parent. For details,
+see <a href="{@docRoot}training/basics/actionbar/styling.html">Styling the Action Bar</a>.</p>
+
+<p>Now the {@link android.R.style#Theme_Holo Theme.Holo} theme is applied to your app and
+all activities show the action bar. That's it.</p>
+
+
+
+<h2 id="ApiLevel7">Support Android 2.1 and Above</h2>
+
+<p>Adding the action bar when running on versions older than Android 3.0 (down to Android 2.1)
+requires that you include the Android Support Library in your application.</p>
+
+<p>To get started, read the <a href="{@docRoot}tools/support-library/setup.html"
+>Support Library Setup</a> document and set up the <strong>v7 appcompat</strong>
+library (once you've downloaded the library package, follow the instructions for <a
+href="{@docRoot}tools/support-library/setup.html#libs-with-res">Adding libraries with
+resources</a>).</p>
+
+<p>Once you have the Support Library integrated with your app project:</p>
+
+<ol>
+  <li>Update your activity so that it extends {@link android.support.v7.app.ActionBarActivity}.
+  For example:
+<pre>
+public class MainActivity extends ActionBarActivity { ... }
+</pre>
+  </li>
+  <li>In your manifest file, update either the <a
+  href="{@docRoot}guide/topics/manifest/application-element.html">{@code
+  &lt;application>}</a> element or individual
+  <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;activity>}</a>
+  elements to use one of the {@link android.support.v7.appcompat.R.style#Theme_AppCompat
+  Theme.AppCompat} themes. For example:
+  <pre>&lt;activity android:theme="@style/Theme.AppCompat.Light" ... ></pre>
+  <p class="note"><strong>Note:</strong> If you've created a custom theme, be sure it uses one
+of the {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} themes as
+its parent. For details, see <a href="{@docRoot}training/basics/actionbar/styling.html">Styling
+the Action Bar</a>.</p>
+  </li>
+</ol>
+
+<p>Now your activity includes the action bar when running on Android 2.1 (API level 7) or higher.
+</p>
+
+<p>Remember to properly set your app's API level support in the manifest:</p>
+<pre>
+&lt;manifest ... &gt;
+    &lt;uses-sdk android:minSdkVersion="7"  android:targetSdkVersion="18" /&gt;
+    ...
+&lt;/manifest&gt;
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/basics/actionbar/styling.jd b/docs/html/training/basics/actionbar/styling.jd
new file mode 100644
index 0000000..a1cc10c
--- /dev/null
+++ b/docs/html/training/basics/actionbar/styling.jd
@@ -0,0 +1,448 @@
+page.title=Styling the Action Bar
+
+trainingnavtop=true
+
+@jd:body
+
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#AndroidThemes">Use an Android Theme</a></li>
+  <li><a href="#CustomBackground">Customize the Background</a></li>
+  <li><a href="#CustomText">Customize the Text Color</a></li>
+  <li><a href="#CustomTabs">Customize the Tab Indicator</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}guide/topics/ui/themes.html">Styles and Themes</a></li>
+  <li><a class="external-link" target="_blank"
+  href="http://jgilfelt.github.io/android-actionbarstylegenerator/">Android Action Bar Style
+  Generator</a></li>
+</ul>
+
+  </div>
+</div>
+
+
+
+<p>The action bar provides your users a familiar and predictable way to perform
+actions and navigate your app, but that doesn't mean it needs to look exactly the
+same as it does in other apps. If you want to style the action bar to better fit your product
+brand, you can easily do so using Android's <a href="{@docRoot}guide/topics/ui/themes.html">style
+and theme</a> resources.</p>
+
+<p>Android includes a few built-in activity themes that include "dark" or "light" action bar
+styles. You can also extend these themes to further customize the look for your action bar.</p>
+
+<p class="note" style="clear:left"><strong>Note:</strong> If you are using the Support Library APIs
+for the action bar, then you must use (or override) the {@link
+android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} family of styles (rather
+than the {@link android.R.style#Theme_Holo Theme.Holo} family, available in API level 11 and
+higher). In doing so, each style property that you declare must be declared twice: once using
+the platform's style properties (the
+{@link android.R.attr android:} properties) and once using the
+style properties included in the Support Library (the {@link android.support.v7.appcompat.R.attr
+appcompat.R.attr} properties&mdash;the context for these properties is actually
+<em>your app</em>). See the examples below for details.</p>
+
+
+
+<h2 id="AndroidThemes">Use an Android Theme</h2>
+
+<div class="figure" style="width:340px">
+  <img src="{@docRoot}images/training/basics/actionbar-theme-dark@2x.png" width="340" alt="" />
+</div>
+
+<div class="figure" style="width:340px">
+  <img src="{@docRoot}images/training/basics/actionbar-theme-light-solid@2x.png" width="340" alt="" />
+</div>
+
+<p>Android includes two baseline activity themes that dictate the color for the action bar:
+</p>
+<ul>
+  <li>{@link android.R.style#Theme_Holo Theme.Holo} for a "dark" theme.
+  </li>
+  <li>{@link android.R.style#Theme_Holo_Light Theme.Holo.Light} for a "light" theme.
+  </li>
+</ul>
+
+<p>You can apply these themes to your entire app or to individual activities by
+declaring them in your manifest file with the {@code android:theme} attribute
+for the <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code
+&lt;application>}</a> element or individual
+<a href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;activity>}</a>
+elements.</p>
+
+<p>For example:</p>
+<pre>
+&lt;application android:theme="@android:style/Theme.Holo.Light" ... />
+</pre>
+
+<div class="figure" style="width:340px">
+  <img src="{@docRoot}images/training/basics/actionbar-theme-light-darkactionbar@2x.png" width="340" alt="" />
+</div>
+
+<p>You can also use a dark action bar while the rest of the activity uses the light
+color scheme by declaring the {@link android.R.style#Theme_Holo_Light_DarkActionBar
+Theme.Holo.Light.DarkActionBar} theme.</p>
+
+<p>When using the Support Library, you must instead use the
+{@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} themes:</p>
+<ul>
+  <li>{@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} for the
+  "dark" theme.</li>
+  <li>{@link android.support.v7.appcompat.R.style#Theme_AppCompat_Light Theme.AppCompat.Light}
+  for the "light" theme.</li>
+  <li>{@link android.support.v7.appcompat.R.style#Theme_AppCompat_Light_DarkActionBar
+Theme.AppCompat.Light.DarkActionBar} for the light theme with a dark action bar.
+</ul>
+
+<p>Be sure that you use action bar icons that properly contrast with the color of your action
+bar. To help you, the <a href="{@docRoot}design/downloads/index.html#action-bar-icon-pack">Action
+Bar Icon Pack</a> includes standard action icons for use with both the Holo light and Holo dark
+action bar.</p>
+
+
+
+
+
+<h2 id="CustomBackground">Customize the Background</h2>
+
+<div class="figure" style="width:340px">
+  <img src="{@docRoot}images/training/basics/actionbar-theme-custom@2x.png" width="340" alt="" />
+</div>
+
+<p>To change the action bar background, create a custom theme for your activity that overrides the
+{@link android.R.attr#actionBarStyle} property. This property points to another style
+in which you can override the {@link android.R.attr#background} property to specify
+a drawable resource for the action bar background.</p>
+
+<p>If your app uses <a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">navigation tabs</a>
+or the <a href="{@docRoot}guide/topics/ui/actionbar.html#SplitBar">split
+action bar</a>, then you can also specify the background for these bars using
+the {@link android.R.attr#backgroundStacked} and
+{@link android.R.attr#backgroundSplit} properties, respectively.</p>
+
+<p class="caution"><strong>Caution:</strong> It's important that you declare an appropriate
+parent theme from which your custom theme and style inherit their styles. Without a parent
+style, your action bar will be without many style properties unless you explicitly declare
+them yourself.</p>
+
+
+<h3 id="CustomBackground11">For Android 3.0 and higher only</h3>
+
+<p>When supporting Android 3.0 and higher only, you can define the action bar's
+background like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.Holo.Light.DarkActionBar">
+        &lt;item name="android:actionBarStyle">&#64;style/MyActionBar&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar styles -->
+    &lt;style name="MyActionBar"
+           parent="&#64;style/Widget.Holo.Light.ActionBar.Solid.Inverse">
+        &lt;item name="android:background">&#64;drawable/actionbar_background&lt;/item>
+    &lt;style>
+&lt;/resources>
+</pre>
+
+<p>Then apply your theme to your entire app or individual activities:</p>
+<pre>
+&lt;application android:theme="&#64;style/CustomActionBarTheme" ... />
+</pre>
+
+
+
+<h3 id="CustomBackground7">For Android 2.1 and higher</h3>
+
+<p>When using the Support Library, the same theme as above must instead look like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.<strong>AppCompat</strong>.Light.DarkActionBar">
+        &lt;item name="android:actionBarStyle">&#64;style/MyActionBar&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="actionBarStyle">&#64;style/MyActionBar&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar styles -->
+    &lt;style name="MyActionBar"
+           parent="&#64;style/Widget.<strong>AppCompat</strong>.Light.ActionBar.Solid.Inverse">
+        &lt;item name="android:background">&#64;drawable/actionbar_background&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="background">&#64;drawable/actionbar_background&lt;/item>
+    &lt;style>
+&lt;/resources>
+</pre>
+
+<p>Then apply your theme to your entire app or individual activities:</p>
+<pre>
+&lt;application android:theme="&#64;style/CustomActionBarTheme" ... />
+</pre>
+
+
+
+
+
+
+
+<h2 id="CustomText">Customize the Text Color</h2>
+
+<p>To modify the color of text in the action bar, you need to override separate properties
+for each text element:</p>
+<ul>
+  <li>Action bar title: Create a custom style that specifies the {@code textColor} property and
+  specify that style for the {@link android.R.attr#titleTextStyle} property in your custom
+  {@link android.R.attr#actionBarStyle}.
+    <p class="note"><strong>Note:</strong>
+    The custom style applied to {@link android.R.attr#titleTextStyle} should use
+    {@link android.R.style#TextAppearance_Holo_Widget_ActionBar_Title
+    TextAppearance.Holo.Widget.ActionBar.Title} as the parent style.</p>
+  </li>
+  <li>Action bar tabs: Override {@link android.R.attr#actionBarTabTextStyle} in your
+  activity theme.</li>
+  <li>Action buttons: Override {@link android.R.attr#actionMenuTextColor} in your
+  activity theme.</li>
+</ul>
+
+
+<h3 id="CustomText11">For Android 3.0 and higher only</h3>
+
+<p>When supporting Android 3.0 and higher only, your style XML file might look like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.Holo">
+        &lt;item name="android:actionBarStyle">&#64;style/MyActionBar&lt;/item>
+        &lt;item name="android:actionBarTabTextStyle">&#64;style/MyActionBarTabText&lt;/item>
+        &lt;item name="android:actionMenuTextColor">&#64;color/actionbar_text&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar styles -->
+    &lt;style name="MyActionBar"
+           parent="&#64;style/Widget.Holo.ActionBar">
+        &lt;item name="android:titleTextStyle">&#64;style/MyActionBarTitleText&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar title text -->
+    &lt;style name="MyActionBarTitleText"
+           parent="&#64;style/TextAppearance.Holo.Widget.ActionBar.Title">
+        &lt;item name="android:textColor">&#64;color/actionbar_text&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar tabs text styles -->
+    &lt;style name="MyActionBarTabText"
+           parent="&#64;style/Widget.Holo.ActionBar.TabText">
+        &lt;item name="android:textColor">&#64;color/actionbar_text&lt;/item>
+    &lt;style>
+&lt;/resources>
+</pre>
+
+
+
+
+<h3 id="CustomText7">For Android 2.1 and higher</h3>
+
+<p>When using the Support Library, your style XML file might look like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.<strong>AppCompat</strong>">
+        &lt;item name="android:actionBarStyle">&#64;style/MyActionBar&lt;/item>
+        &lt;item name="android:actionBarTabTextStyle">&#64;style/MyActionBarTabText&lt;/item>
+        &lt;item name="android:actionMenuTextColor">&#64;color/actionbar_text&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="actionBarStyle">&#64;style/MyActionBar&lt;/item>
+        &lt;item name="actionBarTabTextStyle">&#64;style/MyActionBarTabText&lt;/item>
+        &lt;item name="actionMenuTextColor">&#64;color/actionbar_text&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar styles -->
+    &lt;style name="MyActionBar"
+           parent="&#64;style/Widget.<strong>AppCompat</strong>.ActionBar">
+        &lt;item name="android:titleTextStyle">&#64;style/MyActionBarTitleText&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="titleTextStyle">&#64;style/MyActionBarTitleText&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar title text -->
+    &lt;style name="MyActionBarTitleText"
+           parent="&#64;style/TextAppearance.<strong>AppCompat</strong>.Widget.ActionBar.Title">
+        &lt;item name="android:textColor">&#64;color/actionbar_text&lt;/item>
+        &lt;!-- The textColor property is backward compatible with the Support Library -->
+    &lt;style>
+
+    &lt;!-- ActionBar tabs text -->
+    &lt;style name="MyActionBarTabText"
+           parent="&#64;style/Widget.<strong>AppCompat</strong>.ActionBar.TabText">
+        &lt;item name="android:textColor">&#64;color/actionbar_text&lt;/item>
+        &lt;!-- The textColor property is backward compatible with the Support Library -->
+    &lt;style>
+&lt;/resources>
+</pre>
+
+
+
+
+
+
+<h2 id="CustomTabs">Customize the Tab Indicator</h2>
+
+<div class="figure" style="width:340px">
+  <img src="{@docRoot}images/training/basics/actionbar-theme-custom-tabs@2x.png" width="340" alt="" />
+</div>
+
+<p>To change the indicator used for the <a
+href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">navigation tabs</a>,
+create an activity theme that overrides the
+{@link android.R.attr#actionBarTabStyle} property. This property points to another style
+resource in which you override the {@link android.R.attr#background} property that should specify
+a state-list drawable.</p>
+
+<p class="note"><strong>Note:</strong> A state-list drawable is important so that the tab currently
+selected indicates its state with a background different than the other tabs. For more information
+about how to create a drawable resource that handles multiple button states, read the
+<a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">State List</a>
+documentation.</p>
+
+<p>For example, here's a state-list drawable that declares a specific background image
+for several different states of an action bar tab:</p>
+
+<p class="code-caption">res/drawable/actionbar_tab_indicator.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+&lt;!-- STATES WHEN BUTTON IS NOT PRESSED -->
+
+    &lt;!-- Non focused states -->
+    &lt;item android:state_focused="false" android:state_selected="false"
+          android:state_pressed="false"
+          android:drawable="&#64;drawable/tab_unselected" />
+    &lt;item android:state_focused="false" android:state_selected="true"
+          android:state_pressed="false"
+          android:drawable="&#64;drawable/tab_selected" />
+
+    &lt;!-- Focused states (such as when focused with a d-pad or mouse hover) -->
+    &lt;item android:state_focused="true" android:state_selected="false"
+          android:state_pressed="false"
+          android:drawable="&#64;drawable/tab_unselected_focused" />
+    &lt;item android:state_focused="true" android:state_selected="true"
+          android:state_pressed="false"
+          android:drawable="&#64;drawable/tab_selected_focused" />
+
+
+&lt;!-- STATES WHEN BUTTON IS PRESSED -->
+
+    &lt;!-- Non focused states -->
+    &lt;item android:state_focused="false" android:state_selected="false"
+          android:state_pressed="true"
+          android:drawable="&#64;drawable/tab_unselected_pressed" />
+    &lt;item android:state_focused="false" android:state_selected="true"
+        android:state_pressed="true"
+        android:drawable="&#64;drawable/tab_selected_pressed" />
+
+    &lt;!-- Focused states (such as when focused with a d-pad or mouse hover) -->
+    &lt;item android:state_focused="true" android:state_selected="false"
+          android:state_pressed="true"
+          android:drawable="&#64;drawable/tab_unselected_pressed" />
+    &lt;item android:state_focused="true" android:state_selected="true"
+          android:state_pressed="true"
+          android:drawable="&#64;drawable/tab_selected_pressed" />
+&lt;/selector>
+</pre>
+
+
+
+<h3 id="CustomTabs11">For Android 3.0 and higher only</h3>
+
+<p>When supporting Android 3.0 and higher only, your style XML file might look like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.Holo">
+        &lt;item name="android:actionBarTabStyle">&#64;style/MyActionBarTabs&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar tabs styles -->
+    &lt;style name="MyActionBarTabs"
+           parent="&#64;style/Widget.Holo.ActionBar.TabView">
+        &lt;!-- tab indicator -->
+        &lt;item name="android:background">&#64;drawable/actionbar_tab_indicator&lt;/item>
+    &lt;style>
+&lt;/resources>
+</pre>
+
+
+
+<h3 id="CustomTabs7">For Android 2.1 and higher</h3>
+
+<p>When using the Support Library, your style XML file might look like this:</p>
+
+<p class="code-caption">res/values/themes.xml</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;!-- the theme applied to the application or activity -->
+    &lt;style name="CustomActionBarTheme"
+           parent="&#64;style/Theme.<strong>AppCompat</strong>">
+        &lt;item name="android:actionBarTabStyle">&#64;style/MyActionBarTabs&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="actionBarTabStyle">&#64;style/MyActionBarTabs&lt;/item>
+    &lt;style>
+
+    &lt;!-- ActionBar tabs styles -->
+    &lt;style name="MyActionBarTabs"
+           parent="&#64;style/Widget.<strong>AppCompat</strong>.ActionBar.TabView">
+        &lt;!-- tab indicator -->
+        &lt;item name="android:background">&#64;drawable/actionbar_tab_indicator&lt;/item>
+
+        &lt;!-- Support library compatibility -->
+        &lt;item name="background">&#64;drawable/actionbar_tab_indicator&lt;/item>
+    &lt;style>
+&lt;/resources>
+</pre>
+
+<div class="note"><p><strong>More resources</strong></p>
+<ul>
+  <li>See more style properties for the action bar are listed in the <a
+  href="{@docRoot}guide/topics/ui/actionbar.html#Style">Action Bar</a> guide.</li>
+  <li>Learn more about how themes work in the <a
+  href="{@docRoot}guide/topics/ui/themes.html">Styles and Themes</a> guide.</li>
+  <li>For even more complete styling for the action bar,
+try the <a class="external-link" target="_blank"
+  href="www://http.actionbarstylegenerator.com">Android Action Bar Style
+  Generator</a>.</li>
+</ul>
+</div>
\ No newline at end of file
diff --git a/docs/html/training/basics/fragments/creating.jd b/docs/html/training/basics/fragments/creating.jd
index b5df4e1..377adfc 100644
--- a/docs/html/training/basics/fragments/creating.jd
+++ b/docs/html/training/basics/fragments/creating.jd
@@ -6,7 +6,7 @@
 
 <div id="tb-wrapper">
   <div id="tb">
-    
+
     <h2>This lesson teaches you to</h2>
 <ol>
   <li><a href="#Create">Create a Fragment Class</a></li>
@@ -19,7 +19,7 @@
     </ul>
 
 <h2>Try it out</h2>
-    
+
 <div class="download-box">
  <a href="http://developer.android.com/shareables/training/FragmentBasics.zip"
 class="button">Download the sample</a>
@@ -32,21 +32,30 @@
 <p>You can think of a fragment as a modular section of an activity, which has its own lifecycle,
 receives its own input events, and which you can add or remove while the activity is running (sort
 of like a "sub activity" that you can reuse in different activities). This lesson shows how to
-extend the {@link android.support.v4.app.Fragment} class using the Support Library so your app
-remains compatible with devices running system versions as old as Android 1.6.</p>
+extend the {@link android.support.v4.app.Fragment} class using the <a
+href="{@docRoot}tools/support-library/index.html">Support Library</a> so your app
+remains compatible with devices running system versions as low as Android 1.6.</p>
 
-<p class="note"><strong>Note:</strong> If you decide for other reasons that the minimum
+<p class="note"><strong>Note:</strong> If you decide that the minimum
 API level your app requires is 11 or higher, you don't need to use the Support
 Library and can instead use the framework's built in {@link android.app.Fragment} class and related
 APIs. Just be aware that this lesson is focused on using the APIs from the Support Library, which
 use a specific package signature and sometimes slightly different API names than the versions
 included in the platform.</p>
 
+<p>Before you begin this lesson, you must set up your Android project to use the Support Library.
+If you have not used the Support Library before, set up your project to use the <strong>v4</strong>
+library by following the <a href="{@docRoot}tools/support-library/setup.html">Support Library
+Setup</a> document. However, you can also include the <a href=
+"{@docRoot}guide/topics/ui/actionbar.html">action bar</a> in your activities by instead using the
+<strong>v7 appcompat</strong> library, which is compatible with Android 2.1 (API level 7)
+and also includes the {@link android.support.v4.app.Fragment} APIs.</p>
+
 
 
 <h2 id="Create">Create a Fragment Class</h2>
 
-<p>To create a fragment, extend the {@link android.support.v4.app.Fragment} class, then override 
+<p>To create a fragment, extend the {@link android.support.v4.app.Fragment} class, then override
 key lifecycle methods to insert your app logic, similar to the way you would with an {@link
 android.app.Activity} class.</p>
 
@@ -63,7 +72,7 @@
 
 public class ArticleFragment extends Fragment {
     &#64;Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
         Bundle savedInstanceState) {
         // Inflate the layout for this fragment
         return inflater.inflate(R.layout.article_view, container, false);
@@ -82,7 +91,7 @@
 
 
 
-<h2 id="AddInLayout">Add a Fragment to an Activity using XML</h2> 
+<h2 id="AddInLayout">Add a Fragment to an Activity using XML</h2>
 
 <p>While fragments are reusable, modular UI components, each instance of a {@link
 android.support.v4.app.Fragment} class must be associated with a parent {@link
@@ -98,7 +107,7 @@
 screen is considered "large" (specified by the <code>large</code> qualifier in the directory
 name).</p>
 
-<p><code>res/layout-large/news_articles.xml:</code></p>
+<p class="code-caption">res/layout-large/news_articles.xml</p>
 <pre>
 &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
@@ -120,11 +129,11 @@
 &lt;/LinearLayout>
 </pre>
 
-<p class="note"><strong>Tip:</strong> For more information about creating layouts for different
+<p class="note"><strong>Tip:</strong> For more about creating layouts for different
 screen sizes, read <a href="{@docRoot}training/multiscreen/screensizes.html">Supporting Different
 Screen Sizes</a>.</p>
 
-<p>Here's how an activity applies this layout:</p>
+<p>Then apply the layout to your activity:</p>
 
 <pre>
 import android.os.Bundle;
@@ -139,6 +148,12 @@
 }
 </pre>
 
+<p>If you're using the <a href="{@docRoot}tools/support-library/features.html#v7-appcompat">v7
+appcompat library</a>, your activity should instead extend {@link
+android.support.v7.app.ActionBarActivity}, which is a subclass of {@link
+android.support.v4.app.FragmentActivity} (for more information,
+read <a href="{@docRoot}training/basics/actionbar/index.html">Adding the Action Bar</a>).</p>
+
 
 <p class="note"><strong>Note:</strong> When you add a fragment to an activity layout by defining
 the fragment in the layout XML file, you <em>cannot</em> remove the fragment at runtime. If you plan
diff --git a/docs/html/training/basics/fragments/index.jd b/docs/html/training/basics/fragments/index.jd
index 1b82f2c..987decf 100644
--- a/docs/html/training/basics/fragments/index.jd
+++ b/docs/html/training/basics/fragments/index.jd
@@ -57,9 +57,6 @@
 <h2>Lessons</h2>
  
 <dl>
-  <dt><b><a href="support-lib.html">Using the Android Support Library</a></b></dt>
-    <dd>Learn how to use more recent framework APIs in earlier versions of Android by bundling
-the Android Support Library into your app.</dd>
   <dt><b><a href="creating.html">Creating a Fragment</a></b></dt>
     <dd>Learn how to build a fragment and implement basic behaviors within its callback
 methods.</dd>
diff --git a/docs/html/training/basics/intents/sending.jd b/docs/html/training/basics/intents/sending.jd
index 1646b91..aba3896 100644
--- a/docs/html/training/basics/intents/sending.jd
+++ b/docs/html/training/basics/intents/sending.jd
@@ -240,7 +240,7 @@
 
 // Always use string resources for UI text.
 // This says something like "Share this photo with"
-String title = (String) getResources().getText(R.string.chooser_title);
+String title = getResources().getString(R.string.chooser_title);
 // Create and start the chooser
 Intent chooser = Intent.createChooser(intent, title);
 startActivity(chooser);
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 3e3aa14..09fcc4e 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -95,7 +95,7 @@
 finger across the touch screen. Simple dragging is often implemented by overriding 
 {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
 {@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 
-<a href="dragging.jd">Dragging and Scaling</a>.</li>
+<a href="dragging.html">Dragging and Scaling</a>.</li>
 
     <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 
 drags and lifts her finger quickly. After the user lifts her finger, you generally 
diff --git a/docs/html/training/implementing-navigation/index.jd b/docs/html/training/implementing-navigation/index.jd
index 5b65716..24c98f2 100644
--- a/docs/html/training/implementing-navigation/index.jd
+++ b/docs/html/training/implementing-navigation/index.jd
@@ -47,9 +47,9 @@
 
 <p class="note"><strong>Note:</strong> Several elements of this class require the
 <a href="{@docRoot}tools/support-library/index.html">Support Library</a> APIs.
-If you have not used the Support Library before, follow the lesson about <a
-href="{@docRoot}training/basics/fragments/support-lib.html">Using the Support Library</a>
-to get your project set up.</p>
+If you have not used the Support Library before, follow the instructions
+in the <a href="{@docRoot}tools/support-library/setup.html">Support Library Setup</a>
+document.</p>
 
 
 <h2 id="lessons">Lessons</h2>
diff --git a/docs/html/training/implementing-navigation/lateral.jd b/docs/html/training/implementing-navigation/lateral.jd
index 97e0398..bb9d78c 100644
--- a/docs/html/training/implementing-navigation/lateral.jd
+++ b/docs/html/training/implementing-navigation/lateral.jd
@@ -1,5 +1,5 @@
 page.title=Creating Swipe Views with Tabs
-page.tags="viewpager","horizontal","paging","swipe view"
+page.tags="viewpager","horizontal","paging","swipe view","tabs"
 
 trainingnavtop=true
 
diff --git a/docs/html/training/load-data-background/index.jd b/docs/html/training/load-data-background/index.jd
index 221ae57..29108e8 100644
--- a/docs/html/training/load-data-background/index.jd
+++ b/docs/html/training/load-data-background/index.jd
@@ -54,7 +54,8 @@
 </p>
 <p>
     This class describes how to use a {@link android.support.v4.content.CursorLoader} to run a
-    background query. Examples in this class use the {@link android.support.v4 v4 support library}
+    background query. Examples in this class use the <a
+    href="{@docRoot}tools/support-library/features.html#v4">v4 Support Library</a>
     versions of classes, which support platforms starting with Android 1.6.
 </p>
 <h2>Lessons</h2>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index cb57752..c99fc96 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -39,6 +39,35 @@
 
       <li class="nav-section">
         <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>training/basics/actionbar/index.html"
+             description=
+             "The action bar is one of the most important design elements you can implement for your
+app's activities. Although first introduced with API level 11, you can use the Support Library to
+include the action bar on devices running Android 2.1 or higher."
+            >Adding the Action Bar</a>
+        </div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>training/basics/actionbar/setting-up.html">
+            Setting Up the Action Bar
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/basics/actionbar/adding-buttons.html">
+            Adding Action Buttons
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/basics/actionbar/styling.html">
+            Styling the Action Bar
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/basics/actionbar/overlaying.html">
+            Overlaying the Action Bar
+          </a>
+          </li>
+        </ul>
+      </li>
+
+      <li class="nav-section">
+        <div class="nav-section-header">
           <a href="<?cs var:toroot ?>training/basics/activity-lifecycle/index.html"
              description=
              "How Android activities live and die and how to create
@@ -100,10 +129,6 @@
             >Building a Dynamic UI with Fragments</a>
         </div>
         <ul>
-          <li><a href="<?cs var:toroot ?>training/basics/fragments/support-lib.html">
-            Using the Support Library
-          </a>
-          </li>
           <li><a href="<?cs var:toroot ?>training/basics/fragments/creating.html">
             Creating a Fragment
           </a>
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index f74d0ef..a4f75b9 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -28,6 +28,9 @@
     @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
     public final Bitmap mBitmap;
 
+    private TileMode mTileX;
+    private TileMode mTileY;
+
     /**
      * Call this to create a new shader that will draw with a bitmap.
      *
@@ -37,11 +40,23 @@
      */
     public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
         mBitmap = bitmap;
+        mTileX = tileX;
+        mTileY = tileY;
         final int b = bitmap.ni();
         native_instance = nativeCreate(b, tileX.nativeInt, tileY.nativeInt);
         native_shader = nativePostCreate(native_instance, b, tileX.nativeInt, tileY.nativeInt);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final BitmapShader copy = new BitmapShader(mBitmap, mTileX, mTileY);
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
     private static native int nativeCreate(int native_bitmap, int shaderTileModeX,
             int shaderTileModeY);
     private static native int nativePostCreate(int native_shader, int native_bitmap,
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 6a4e89a..f526d29 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -406,18 +406,18 @@
         sColorNameMap.put("yellow", YELLOW);
         sColorNameMap.put("cyan", CYAN);
         sColorNameMap.put("magenta", MAGENTA);
-        sColorNameMap.put("aqua", 0x00FFFF);
-        sColorNameMap.put("fuchsia", 0xFF00FF);
+        sColorNameMap.put("aqua", 0xFF00FFFF);
+        sColorNameMap.put("fuchsia", 0xFFFF00FF);
         sColorNameMap.put("darkgrey", DKGRAY);
         sColorNameMap.put("grey", GRAY);
         sColorNameMap.put("lightgrey", LTGRAY);
-        sColorNameMap.put("lime", 0x00FF00);
-        sColorNameMap.put("maroon", 0x800000);
-        sColorNameMap.put("navy", 0x000080);
-        sColorNameMap.put("olive", 0x808000);
-        sColorNameMap.put("purple", 0x800080);
-        sColorNameMap.put("silver", 0xC0C0C0);
-        sColorNameMap.put("teal", 0x008080);
+        sColorNameMap.put("lime", 0xFF00FF00);
+        sColorNameMap.put("maroon", 0xFF800000);
+        sColorNameMap.put("navy", 0xFF000080);
+        sColorNameMap.put("olive", 0xFF808000);
+        sColorNameMap.put("purple", 0xFF800080);
+        sColorNameMap.put("silver", 0xFFC0C0C0);
+        sColorNameMap.put("teal", 0xFF008080);
 
     }
 }
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 241ab17..de0d3d6 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -16,10 +16,22 @@
 
 package android.graphics;
 
-/** A subclass of shader that returns the coposition of two other shaders, combined by
+/** A subclass of shader that returns the composition of two other shaders, combined by
     an {@link android.graphics.Xfermode} subclass.
 */
 public class ComposeShader extends Shader {
+
+    private static final int TYPE_XFERMODE = 1;
+    private static final int TYPE_PORTERDUFFMODE = 2;
+
+    /**
+     * Type of the ComposeShader: can be either TYPE_XFERMODE or TYPE_PORTERDUFFMODE
+     */
+    private int mType;
+
+    private Xfermode mXferMode;
+    private PorterDuff.Mode mPorterDuffMode;
+
     /**
      * Hold onto the shaders to avoid GC.
      */
@@ -37,8 +49,10 @@
                         is null, then SRC_OVER is assumed.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
+        mType = TYPE_XFERMODE;
         mShaderA = shaderA;
         mShaderB = shaderB;
+        mXferMode = mode;
         native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
                 (mode != null) ? mode.native_instance : 0);
         if (mode instanceof PorterDuffXfermode) {
@@ -59,14 +73,37 @@
         @param mode     The PorterDuff mode that combines the colors from the two shaders.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
+        mType = TYPE_PORTERDUFFMODE;
         mShaderA = shaderA;
         mShaderB = shaderB;
+        mPorterDuffMode = mode;
         native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance,
                 mode.nativeInt);
         native_shader = nativePostCreate2(native_instance, shaderA.native_shader,
                 shaderB.native_shader, mode.nativeInt);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final ComposeShader copy;
+        switch (mType) {
+            case TYPE_XFERMODE:
+                copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mXferMode);
+                break;
+            case TYPE_PORTERDUFFMODE:
+                copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "ComposeShader should be created with either Xfermode or PorterDuffMode");
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
     private static native int nativeCreate1(int native_shaderA, int native_shaderB,
             int native_mode);
     private static native int nativeCreate2(int native_shaderA, int native_shaderB,
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 96a71e3..54cdcab 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -17,6 +17,27 @@
 package android.graphics;
 
 public class LinearGradient extends Shader {
+
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
+
+    /**
+     * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_START_AND_COLOR_END.
+     */
+    private int mType;
+
+    private float mX0;
+    private float mY0;
+    private float mX1;
+    private float mY1;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mColor0;
+    private int mColor1;
+
+    private TileMode mTileMode;
+
 	/**	Create a shader that draws a linear gradient along a line.
         @param x0           The x-coordinate for the start of the gradient line
         @param y0           The y-coordinate for the start of the gradient line
@@ -36,6 +57,14 @@
         if (positions != null && colors.length != positions.length) {
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mX0 = x0;
+        mY0 = y0;
+        mX1 = x1;
+        mY1 = y1;
+        mColors = colors;
+        mPositions = positions;
+        mTileMode = tile;
         native_instance = nativeCreate1(x0, y0, x1, y1, colors, positions, tile.nativeInt);
         native_shader = nativePostCreate1(native_instance, x0, y0, x1, y1, colors, positions,
                 tile.nativeInt);
@@ -52,12 +81,42 @@
 	*/
 	public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
             TileMode tile) {
+        mType = TYPE_COLOR_START_AND_COLOR_END;
+        mX0 = x0;
+        mY0 = y0;
+        mX1 = x1;
+        mY1 = y1;
+        mColor0 = color0;
+        mColor1 = color1;
+        mTileMode = tile;
         native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt);
         native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1,
                 tile.nativeInt);
     }
 
-	private native int nativeCreate1(float x0, float y0, float x1, float y1,
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final LinearGradient copy;
+        switch (mType) {
+            case TYPE_COLORS_AND_POSITIONS:
+                copy = new LinearGradient(mX0, mY0, mX1, mY1, mColors.clone(), mPositions.clone(),
+                        mTileMode);
+                break;
+            case TYPE_COLOR_START_AND_COLOR_END:
+                copy = new LinearGradient(mX0, mY0, mX1, mY1, mColor0, mColor1, mTileMode);
+                break;
+            default:
+                throw new IllegalArgumentException("LinearGradient should be created with either " +
+                        "colors and positions or start color and end color");
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private native int nativeCreate1(float x0, float y0, float x1, float y1,
             int colors[], float positions[], int tileMode);
 	private native int nativeCreate2(float x0, float y0, float x1, float y1,
             int color0, int color1, int tileMode);
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1485d8d..331cf6e 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -105,9 +105,17 @@
     public static final int SUBPIXEL_TEXT_FLAG  = 0x80;
     /** bit mask for the flag enabling device kerning for text */
     public static final int DEV_KERN_TEXT_FLAG  = 0x100;
+    /** @hide bit mask for the flag enabling subpixel glyph rendering for text */
+    public static final int LCD_RENDER_TEXT_FLAG = 0x200;
+    /** bit mask for the flag enabling embedded bitmap strikes for text */
+    public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;
+    /** @hide bit mask for the flag forcing freetype's autohinter on for text */
+    public static final int AUTO_HINTING_TEXT_FLAG = 0x800;
+    /** @hide bit mask for the flag enabling vertical rendering for text */
+    public static final int VERTICAL_TEXT_FLAG = 0x1000;
 
     // we use this when we first create a paint
-    static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG;
+    static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG;
 
     /**
      * Option for {@link #setHinting}: disable hinting.
@@ -421,7 +429,9 @@
         mMaskFilter = paint.mMaskFilter;
         mPathEffect = paint.mPathEffect;
         mRasterizer = paint.mRasterizer;
-        mShader = paint.mShader;
+        if (paint.mShader != null) {
+            mShader = paint.mShader.copy();
+        }
         mTypeface = paint.mTypeface;
         mXfermode = paint.mXfermode;
 
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index 897762c..23244d8 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -18,6 +18,25 @@
 
 public class RadialGradient extends Shader {
 
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_CENTER_AND_COLOR_EDGE = 2;
+
+    /**
+     * Type of the RadialGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_CENTER_AND_COLOR_EDGE.
+     */
+    private int mType;
+
+    private float mX;
+    private float mY;
+    private float mRadius;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mColor0;
+    private int mColor1;
+
+    private TileMode mTileMode;
+
 	/**	Create a shader that draws a radial gradient given the center and radius.
         @param x        The x-coordinate of the center of the radius
         @param y        The y-coordinate of the center of the radius
@@ -39,6 +58,13 @@
         if (positions != null && colors.length != positions.length) {
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mX = x;
+        mY = y;
+        mRadius = radius;
+        mColors = colors;
+        mPositions = positions;
+        mTileMode = tile;
         native_instance = nativeCreate1(x, y, radius, colors, positions, tile.nativeInt);
         native_shader = nativePostCreate1(native_instance, x, y, radius, colors, positions,
                 tile.nativeInt);
@@ -57,12 +83,41 @@
         if (radius <= 0) {
             throw new IllegalArgumentException("radius must be > 0");
         }
+        mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE;
+        mX = x;
+        mY = y;
+        mRadius = radius;
+        mColor0 = color0;
+        mColor1 = color1;
+        mTileMode = tile;
         native_instance = nativeCreate2(x, y, radius, color0, color1, tile.nativeInt);
         native_shader = nativePostCreate2(native_instance, x, y, radius, color0, color1,
                 tile.nativeInt);
     }
 
-	private static native int nativeCreate1(float x, float y, float radius,
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final RadialGradient copy;
+        switch (mType) {
+            case TYPE_COLORS_AND_POSITIONS:
+                copy = new RadialGradient(mX, mY, mRadius, mColors.clone(), mPositions.clone(),
+                        mTileMode);
+                break;
+            case TYPE_COLOR_CENTER_AND_COLOR_EDGE:
+                copy = new RadialGradient(mX, mY, mRadius, mColor0, mColor1, mTileMode);
+                break;
+            default:
+                throw new IllegalArgumentException("RadialGradient should be created with either " +
+                        "colors and positions or center color and edge color");
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private static native int nativeCreate1(float x, float y, float radius,
             int colors[], float positions[], int tileMode);
 	private static native int nativeCreate2(float x, float y, float radius,
             int color0, int color1, int tileMode);
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 43758e7..afc68d8 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -90,6 +90,28 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    protected Shader copy() {
+        final Shader copy = new Shader();
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    /**
+     * @hide
+     */
+    protected void copyLocalMatrix(Shader dest) {
+        if (mLocalMatrix != null) {
+            final Matrix lm = new Matrix();
+            getLocalMatrix(lm);
+            dest.setLocalMatrix(lm);
+        } else {
+            dest.setLocalMatrix(null);
+        }
+    }
+
     private static native void nativeDestructor(int native_shader, int native_skiaShader);
     private static native void nativeSetLocalMatrix(int native_shader,
             int native_skiaShader, int matrix_instance);
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 2afdd4d..3010927 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -18,6 +18,22 @@
 
 public class SweepGradient extends Shader {
 
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
+
+    /**
+     * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_START_AND_COLOR_END.
+     */
+    private int mType;
+
+    private float mCx;
+    private float mCy;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mColor0;
+    private int mColor1;
+
     /**
      * A subclass of Shader that draws a sweep gradient around a center point.
      *
@@ -41,6 +57,11 @@
             throw new IllegalArgumentException(
                         "color and position arrays must be of equal length");
         }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mCx = cx;
+        mCy = cy;
+        mColors = colors;
+        mPositions = positions;
         native_instance = nativeCreate1(cx, cy, colors, positions);
         native_shader = nativePostCreate1(native_instance, cx, cy, colors, positions);
     }
@@ -54,10 +75,36 @@
      * @param color1   The color to use at the end of the sweep
      */
     public SweepGradient(float cx, float cy, int color0, int color1) {
+        mType = TYPE_COLOR_START_AND_COLOR_END;
+        mCx = cx;
+        mCy = cy;
+        mColor0 = color0;
+        mColor1 = color1;
         native_instance = nativeCreate2(cx, cy, color0, color1);
         native_shader = nativePostCreate2(native_instance, cx, cy, color0, color1);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final SweepGradient copy;
+        switch (mType) {
+            case TYPE_COLORS_AND_POSITIONS:
+                copy = new SweepGradient(mCx, mCy, mColors.clone(), mPositions.clone());
+                break;
+            case TYPE_COLOR_START_AND_COLOR_END:
+                copy = new SweepGradient(mCx, mCy, mColor0, mColor1);
+                break;
+            default:
+                throw new IllegalArgumentException("SweepGradient should be created with either " +
+                        "colors and positions or start color and end color");
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
     private static native int nativeCreate1(float x, float y, int colors[], float positions[]);
     private static native int nativeCreate2(float x, float y, int color0, int color1);
 
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 8689261..5ceab36 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -23,12 +23,14 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
 import android.view.Gravity;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -72,7 +74,10 @@
      // These are scaled to match the target density.
     private int mBitmapWidth;
     private int mBitmapHeight;
-    
+
+    // Mirroring matrix for using with Shaders
+    private Matrix mMirrorMatrix;
+
     /**
      * Create an empty drawable, not dealing with density.
      * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
@@ -399,14 +404,51 @@
     }
 
     @Override
+    public void setAutoMirrored(boolean mirrored) {
+        if (mBitmapState.mAutoMirrored != mirrored) {
+            mBitmapState.mAutoMirrored = mirrored;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public final boolean isAutoMirrored() {
+        return mBitmapState.mAutoMirrored;
+    }
+
+    @Override
     public int getChangingConfigurations() {
         return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
     }
-    
+
+    private boolean needMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
+    private void updateMirrorMatrix(float dx) {
+        if (mMirrorMatrix == null) {
+            mMirrorMatrix = new Matrix();
+        }
+        mMirrorMatrix.setTranslate(dx, 0);
+        mMirrorMatrix.preScale(-1.0f, 1.0f);
+    }
+
     @Override
     protected void onBoundsChange(Rect bounds) {
         super.onBoundsChange(bounds);
         mApplyGravity = true;
+        Shader shader = mBitmapState.mPaint.getShader();
+        if (shader != null) {
+            if (needMirroring()) {
+                updateMirrorMatrix(bounds.right - bounds.left);
+                shader.setLocalMatrix(mMirrorMatrix);
+            } else {
+                if (mMirrorMatrix != null) {
+                    mMirrorMatrix = null;
+                    shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+                }
+            }
+        }
     }
 
     @Override
@@ -430,6 +472,7 @@
             }
 
             Shader shader = state.mPaint.getShader();
+            final boolean needMirroring = needMirroring();
             if (shader == null) {
                 if (mApplyGravity) {
                     final int layoutDirection = getLayoutDirection();
@@ -437,12 +480,31 @@
                             getBounds(), mDstRect, layoutDirection);
                     mApplyGravity = false;
                 }
+                if (needMirroring) {
+                    canvas.save();
+                    // Mirror the bitmap
+                    canvas.translate(mDstRect.right - mDstRect.left, 0);
+                    canvas.scale(-1.0f, 1.0f);
+                }
                 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);
+                if (needMirroring) {
+                    canvas.restore();
+                }
             } else {
                 if (mApplyGravity) {
                     copyBounds(mDstRect);
                     mApplyGravity = false;
                 }
+                if (needMirroring) {
+                    // Mirror the bitmap
+                    updateMirrorMatrix(mDstRect.right - mDstRect.left);
+                    shader.setLocalMatrix(mMirrorMatrix);
+                } else {
+                    if (mMirrorMatrix != null) {
+                        mMirrorMatrix = null;
+                        shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+                    }
+                }
                 canvas.drawRect(mDstRect, state.mPaint);
             }
         }
@@ -505,6 +567,8 @@
         setTargetDensity(r.getDisplayMetrics());
         setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap,
                 bitmap.hasMipMap()));
+        setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored,
+                false));
 
         final Paint paint = mBitmapState.mPaint;
         paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias,
@@ -567,6 +631,7 @@
         Shader.TileMode mTileModeY = null;
         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
         boolean mRebuildShader;
+        boolean mAutoMirrored;
 
         BitmapState(Bitmap bitmap) {
             mBitmap = bitmap;
@@ -581,6 +646,7 @@
             mTargetDensity = bitmapState.mTargetDensity;
             mPaint = new Paint(bitmapState.mPaint);
             mRebuildShader = bitmapState.mRebuildShader;
+            mAutoMirrored = bitmapState.mAutoMirrored;
         }
 
         @Override
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index c8fce9e..8135716 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -572,6 +572,25 @@
     }
 
     /**
+     * Set whether this Drawable is automatically mirrored when its layout direction is RTL
+     * (right-to left). See {@link android.util.LayoutDirection}.
+     *
+     * @param mirrored Set to true if the Drawable should be mirrored, false if not.
+     */
+    public void setAutoMirrored(boolean mirrored) {
+    }
+
+    /**
+     * Tells if this Drawable will be automatically mirrored  when its layout direction is RTL
+     * right-to-left. See {@link android.util.LayoutDirection}.
+     *
+     * @return boolean Returns true if this Drawable will be automatically mirrored.
+     */
+    public boolean isAutoMirrored() {
+        return false;
+    }
+
+    /**
      * Return the opacity/transparency of this Drawable.  The returned value is
      * one of the abstract format constants in
      * {@link android.graphics.PixelFormat}:
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index f9cd4e2..e350e8d 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -173,6 +173,19 @@
     }
 
     @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mDrawableContainerState.mAutoMirrored = mirrored;
+        if (mCurrDrawable != null) {
+            mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mDrawableContainerState.mAutoMirrored;
+    }
+
+    @Override
     public void jumpToCurrentState() {
         boolean changed = false;
         if (mLastDrawable != null) {
@@ -334,6 +347,7 @@
                 d.setLevel(getLevel());
                 d.setBounds(getBounds());
                 d.setLayoutDirection(getLayoutDirection());
+                d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
             }
         } else {
             mCurrDrawable = null;
@@ -471,6 +485,8 @@
         int mEnterFadeDuration;
         int mExitFadeDuration;
 
+        boolean mAutoMirrored;
+
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
@@ -490,6 +506,7 @@
                 mLayoutDirection = orig.mLayoutDirection;
                 mEnterFadeDuration = orig.mEnterFadeDuration;
                 mExitFadeDuration = orig.mExitFadeDuration;
+                mAutoMirrored = orig.mAutoMirrored;
 
                 // Cloning the following values may require creating futures.
                 mConstantPadding = orig.getConstantPadding();
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 206897b..81cc11b 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -119,6 +119,9 @@
         mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity,
                 PixelFormat.UNKNOWN);
 
+        setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored,
+                false));
+
         a.recycle();
 
         final int innerDepth = parser.getDepth() + 1;
@@ -200,6 +203,7 @@
         st.mChildren[i] = childDrawable;
         childDrawable.mId = id;
         childDrawable.mDrawable = layer;
+        childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
         childDrawable.mInsetL = left;
         childDrawable.mInsetT = top;
         childDrawable.mInsetR = right;
@@ -448,6 +452,21 @@
     }
 
     @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mLayerState.mAutoMirrored = mirrored;
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNum;
+        for (int i=0; i<N; i++) {
+            array[i].mDrawable.setAutoMirrored(mirrored);
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mLayerState.mAutoMirrored;
+    }
+
+    @Override
     public boolean isStateful() {
         return mLayerState.isStateful();
     }
@@ -630,6 +649,8 @@
         private boolean mCheckedConstantState;
         private boolean mCanConstantState;
 
+        private boolean mAutoMirrored;
+
         LayerState(LayerState orig, LayerDrawable owner, Resources res) {
             if (orig != null) {
                 final ChildDrawable[] origChildDrawable = orig.mChildren;
@@ -663,6 +684,7 @@
                 mHaveStateful = orig.mHaveStateful;
                 mStateful = orig.mStateful;
                 mCheckedConstantState = mCanConstantState = true;
+                mAutoMirrored = orig.mAutoMirrored;
             } else {
                 mNum = 0;
                 mChildren = null;
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 86e4bad..720494b 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -30,6 +30,7 @@
 import android.graphics.Region;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
 import android.util.TypedValue;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -132,6 +133,7 @@
             // lazy allocation of a paint
             setDither(state.mDither);
         }
+        setAutoMirrored(state.mAutoMirrored);
         if (mNinePatch != null) {
             computeBitmapSize();
         }
@@ -216,7 +218,19 @@
 
     @Override
     public void draw(Canvas canvas) {
-        mNinePatch.draw(canvas, getBounds(), mPaint);
+        final Rect bounds = getBounds();
+        final boolean needMirroring = isAutoMirrored() &&
+                getLayoutDirection() == LayoutDirection.RTL;
+        if (needMirroring) {
+            canvas.save();
+            // Mirror the 9patch
+            canvas.translate(bounds.right - bounds.left, 0);
+            canvas.scale(-1.0f, 1.0f);
+        }
+        mNinePatch.draw(canvas, bounds, mPaint);
+        if (needMirroring) {
+            canvas.restore();
+        }
     }
 
     @Override
@@ -279,6 +293,16 @@
     }
 
     @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mNinePatchState.mAutoMirrored = mirrored;
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mNinePatchState.mAutoMirrored;
+    }
+
+    @Override
     public void setFilterBitmap(boolean filter) {
         getPaint().setFilterBitmap(filter);
         invalidateSelf();
@@ -328,8 +352,11 @@
                     ": <nine-patch> requires a valid 9-patch source image");
         }
 
+        final boolean automirrored = a.getBoolean(
+                com.android.internal.R.styleable.NinePatchDrawable_autoMirrored, false);
+
         setNinePatchState(new NinePatchState(new NinePatch(bitmap, bitmap.getNinePatchChunk()),
-                padding, opticalInsets, dither), r);
+                padding, opticalInsets, dither, automirrored), r);
         mNinePatchState.mTargetDensity = mTargetDensity;
 
         a.recycle();
@@ -407,20 +434,23 @@
         final boolean mDither;
         int mChangingConfigurations;
         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+        boolean mAutoMirrored;
 
         NinePatchState(NinePatch ninePatch, Rect padding) {
-            this(ninePatch, padding, new Rect(), DEFAULT_DITHER);
+            this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false);
         }
 
         NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) {
-            this(ninePatch, padding, opticalInsets, DEFAULT_DITHER);
+            this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
         }
 
-        NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither) {
+        NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither,
+                       boolean autoMirror) {
             mNinePatch = ninePatch;
             mPadding = rect;
             mOpticalInsets = Insets.of(opticalInsets);
             mDither = dither;
+            mAutoMirrored = autoMirror;
         }
 
         // Copy constructor
@@ -434,6 +464,7 @@
             mDither = state.mDither;
             mChangingConfigurations = state.mChangingConfigurations;
             mTargetDensity = state.mTargetDensity;
+            mAutoMirrored = state.mAutoMirrored;
         }
 
         @Override
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index e3c7bc5..48d66b7 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -132,6 +132,9 @@
         setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,
                                DEFAULT_DITHER));
 
+        setAutoMirrored(a.getBoolean(
+                com.android.internal.R.styleable.StateListDrawable_autoMirrored, false));
+
         a.recycle();
 
         int type;
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index a305fc3..97afa59 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -479,7 +479,7 @@
     const uint32_t*             mEntries;
     const uint32_t*             mEntryStyles;
     const void*                 mStrings;
-    char16_t**                  mCache;
+    char16_t mutable**          mCache;
     uint32_t                    mStringPoolSize;    // number of uint16_t
     const uint32_t*             mStyles;
     uint32_t                    mStylePoolSize;    // number of uint32_t
@@ -678,11 +678,15 @@
     // Returns -1 if no namespace, -2 if idx out of range.
     int32_t getAttributeNamespaceID(size_t idx) const;
     const uint16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
-    
+
     int32_t getAttributeNameID(size_t idx) const;
     const uint16_t* getAttributeName(size_t idx, size_t* outLen) const;
     uint32_t getAttributeNameResID(size_t idx) const;
-    
+
+    // These will work only if the underlying string pool is UTF-8.
+    const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
+    const char* getAttributeName8(size_t idx, size_t* outLen) const;
+
     int32_t getAttributeValueStringID(size_t idx) const;
     const uint16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
     
@@ -1294,12 +1298,14 @@
         const char16_t* package;
         size_t packageLen;
         const char16_t* type;
+        const char* type8;
         size_t typeLen;
         const char16_t* name;
+        const char* name8;
         size_t nameLen;
     };
 
-    bool getResourceName(uint32_t resID, resource_name* outName) const;
+    bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
 
     /**
      * Retrieve the value of a resource.  If the resource is found, returns a
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 1128e02..1cc3563 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -35,7 +35,7 @@
 #define INT32_MAX ((int32_t)(2147483647))
 #endif
 
-#define POOL_NOISY(x) //x
+#define STRING_POOL_NOISY(x) //x
 #define XML_NOISY(x) //x
 #define TABLE_NOISY(x) //x
 #define TABLE_GETENTRY(x) //x
@@ -378,7 +378,6 @@
         size_t charSize;
         if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
             charSize = sizeof(uint8_t);
-            mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
         } else {
             charSize = sizeof(char16_t);
         }
@@ -593,6 +592,23 @@
                 if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
                     AutoMutex lock(mDecodeLock);
 
+                    if (mCache == NULL) {
+#ifndef HAVE_ANDROID_OS
+                        STRING_POOL_NOISY(ALOGI("CREATING STRING CACHE OF %d bytes",
+                                mHeader->stringCount*sizeof(char16_t**)));
+#else
+                        // We do not want to be in this case when actually running Android.
+                        ALOGW("CREATING STRING CACHE OF %d bytes",
+                                mHeader->stringCount*sizeof(char16_t**));
+#endif
+                        mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
+                        if (mCache == NULL) {
+                            ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
+                                    (int)(mHeader->stringCount*sizeof(char16_t**)));
+                            return NULL;
+                        }
+                    }
+
                     if (mCache[idx] != NULL) {
                         return mCache[idx];
                     }
@@ -612,6 +628,7 @@
                         return NULL;
                     }
 
+                    STRING_POOL_NOISY(ALOGI("Caching UTF8 string: %s", u8str));
                     utf8_to_utf16(u8str, u8len, u16str);
                     mCache[idx] = u16str;
                     return u16str;
@@ -633,20 +650,20 @@
 const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
 {
     if (mError == NO_ERROR && idx < mHeader->stringCount) {
-        const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
-        const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
+        if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) == 0) {
+            return NULL;
+        }
+        const uint32_t off = mEntries[idx]/sizeof(char);
         if (off < (mStringPoolSize-1)) {
-            if (isUTF8) {
-                const uint8_t* strings = (uint8_t*)mStrings;
-                const uint8_t* str = strings+off;
-                *outLen = decodeLength(&str);
-                size_t encLen = decodeLength(&str);
-                if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
-                    return (const char*)str;
-                } else {
-                    ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
-                            (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
-                }
+            const uint8_t* strings = (uint8_t*)mStrings;
+            const uint8_t* str = strings+off;
+            *outLen = decodeLength(&str);
+            size_t encLen = decodeLength(&str);
+            if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+                return (const char*)str;
+            } else {
+                ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+                        (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
             }
         } else {
             ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
@@ -695,45 +712,104 @@
 
     size_t len;
 
-    // TODO optimize searching for UTF-8 strings taking into account
-    // the cache fill to determine when to convert the searched-for
-    // string key to UTF-8.
+    if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
+        STRING_POOL_NOISY(ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string()));
 
-    if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
-        // Do a binary search for the string...
-        ssize_t l = 0;
-        ssize_t h = mHeader->stringCount-1;
+        // The string pool contains UTF 8 strings; we don't want to cause
+        // temporary UTF-16 strings to be created as we search.
+        if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+            // Do a binary search for the string...  this is a little tricky,
+            // because the strings are sorted with strzcmp16().  So to match
+            // the ordering, we need to convert strings in the pool to UTF-16.
+            // But we don't want to hit the cache, so instead we will have a
+            // local temporary allocation for the conversions.
+            char16_t* convBuffer = (char16_t*)malloc(strLen+4);
+            ssize_t l = 0;
+            ssize_t h = mHeader->stringCount-1;
 
-        ssize_t mid;
-        while (l <= h) {
-            mid = l + (h - l)/2;
-            const char16_t* s = stringAt(mid, &len);
-            int c = s ? strzcmp16(s, len, str, strLen) : -1;
-            POOL_NOISY(printf("Looking for %s, at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
-                         String8(str).string(),
-                         String8(s).string(),
-                         c, (int)l, (int)mid, (int)h));
-            if (c == 0) {
-                return mid;
-            } else if (c < 0) {
-                l = mid + 1;
-            } else {
-                h = mid - 1;
+            ssize_t mid;
+            while (l <= h) {
+                mid = l + (h - l)/2;
+                const uint8_t* s = (const uint8_t*)string8At(mid, &len);
+                int c;
+                if (s != NULL) {
+                    char16_t* end = utf8_to_utf16_n(s, len, convBuffer, strLen+3);
+                    *end = 0;
+                    c = strzcmp16(convBuffer, end-convBuffer, str, strLen);
+                } else {
+                    c = -1;
+                }
+                STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+                             (const char*)s, c, (int)l, (int)mid, (int)h));
+                if (c == 0) {
+                    STRING_POOL_NOISY(ALOGI("MATCH!"));
+                    free(convBuffer);
+                    return mid;
+                } else if (c < 0) {
+                    l = mid + 1;
+                } else {
+                    h = mid - 1;
+                }
+            }
+            free(convBuffer);
+        } else {
+            // It is unusual to get the ID from an unsorted string block...
+            // most often this happens because we want to get IDs for style
+            // span tags; since those always appear at the end of the string
+            // block, start searching at the back.
+            String8 str8(str, strLen);
+            const size_t str8Len = str8.size();
+            for (int i=mHeader->stringCount-1; i>=0; i--) {
+                const char* s = string8At(i, &len);
+                STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+                             String8(s).string(),
+                             i));
+                if (s && str8Len == len && memcmp(s, str8.string(), str8Len) == 0) {
+                    STRING_POOL_NOISY(ALOGI("MATCH!"));
+                    return i;
+                }
             }
         }
+
     } else {
-        // It is unusual to get the ID from an unsorted string block...
-        // most often this happens because we want to get IDs for style
-        // span tags; since those always appear at the end of the string
-        // block, start searching at the back.
-        for (int i=mHeader->stringCount-1; i>=0; i--) {
-            const char16_t* s = stringAt(i, &len);
-            POOL_NOISY(printf("Looking for %s, at %s, i=%d\n",
-                         String8(str, strLen).string(),
-                         String8(s).string(),
-                         i));
-            if (s && strzcmp16(s, len, str, strLen) == 0) {
-                return i;
+        STRING_POOL_NOISY(ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string()));
+
+        if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+            // Do a binary search for the string...
+            ssize_t l = 0;
+            ssize_t h = mHeader->stringCount-1;
+
+            ssize_t mid;
+            while (l <= h) {
+                mid = l + (h - l)/2;
+                const char16_t* s = stringAt(mid, &len);
+                int c = s ? strzcmp16(s, len, str, strLen) : -1;
+                STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+                             String8(s).string(),
+                             c, (int)l, (int)mid, (int)h));
+                if (c == 0) {
+                    STRING_POOL_NOISY(ALOGI("MATCH!"));
+                    return mid;
+                } else if (c < 0) {
+                    l = mid + 1;
+                } else {
+                    h = mid - 1;
+                }
+            }
+        } else {
+            // It is unusual to get the ID from an unsorted string block...
+            // most often this happens because we want to get IDs for style
+            // span tags; since those always appear at the end of the string
+            // block, start searching at the back.
+            for (int i=mHeader->stringCount-1; i>=0; i--) {
+                const char16_t* s = stringAt(i, &len);
+                STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+                             String8(s).string(),
+                             i));
+                if (s && strLen == len && strzcmp16(s, len, str, strLen) == 0) {
+                    STRING_POOL_NOISY(ALOGI("MATCH!"));
+                    return i;
+                }
             }
         }
     }
@@ -936,6 +1012,14 @@
     return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
 }
 
+const char* ResXMLParser::getAttributeNamespace8(size_t idx, size_t* outLen) const
+{
+    int32_t id = getAttributeNamespaceID(idx);
+    //printf("attribute namespace=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+    return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
 int32_t ResXMLParser::getAttributeNameID(size_t idx) const
 {
     if (mEventCode == START_TAG) {
@@ -959,6 +1043,14 @@
     return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
 }
 
+const char* ResXMLParser::getAttributeName8(size_t idx, size_t* outLen) const
+{
+    int32_t id = getAttributeNameID(idx);
+    //printf("attribute name=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+    return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
 uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const
 {
     int32_t id = getAttributeNameID(idx);
@@ -1048,22 +1140,67 @@
                                        const char16_t* attr, size_t attrLen) const
 {
     if (mEventCode == START_TAG) {
+        if (attr == NULL) {
+            return NAME_NOT_FOUND;
+        }
         const size_t N = getAttributeCount();
-        for (size_t i=0; i<N; i++) {
-            size_t curNsLen, curAttrLen;
-            const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
-            const char16_t* curAttr = getAttributeName(i, &curAttrLen);
-            //printf("%d: ns=%p attr=%p curNs=%p curAttr=%p\n",
-            //       i, ns, attr, curNs, curAttr);
-            //printf(" --> attr=%s, curAttr=%s\n",
-            //       String8(attr).string(), String8(curAttr).string());
-            if (attr && curAttr && (strzcmp16(attr, attrLen, curAttr, curAttrLen) == 0)) {
-                if (ns == NULL) {
-                    if (curNs == NULL) return i;
-                } else if (curNs != NULL) {
-                    //printf(" --> ns=%s, curNs=%s\n",
-                    //       String8(ns).string(), String8(curNs).string());
-                    if (strzcmp16(ns, nsLen, curNs, curNsLen) == 0) return i;
+        if (mTree.mStrings.isUTF8()) {
+            String8 ns8, attr8;
+            if (ns != NULL) {
+                ns8 = String8(ns, nsLen);
+            }
+            attr8 = String8(attr, attrLen);
+            STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF8 %s (%d) / %s (%d)", ns8.string(), nsLen,
+                    attr8.string(), attrLen));
+            for (size_t i=0; i<N; i++) {
+                size_t curNsLen = 0, curAttrLen = 0;
+                const char* curNs = getAttributeNamespace8(i, &curNsLen);
+                const char* curAttr = getAttributeName8(i, &curAttrLen);
+                STRING_POOL_NOISY(ALOGI("  curNs=%s (%d), curAttr=%s (%d)", curNs, curNsLen,
+                        curAttr, curAttrLen));
+                if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+                        && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+                    if (ns == NULL) {
+                        if (curNs == NULL) {
+                            STRING_POOL_NOISY(ALOGI("  FOUND!"));
+                            return i;
+                        }
+                    } else if (curNs != NULL) {
+                        //printf(" --> ns=%s, curNs=%s\n",
+                        //       String8(ns).string(), String8(curNs).string());
+                        if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+                            STRING_POOL_NOISY(ALOGI("  FOUND!"));
+                            return i;
+                        }
+                    }
+                }
+            }
+        } else {
+            STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF16 %s (%d) / %s (%d)",
+                    String8(ns, nsLen).string(), nsLen,
+                    String8(attr, attrLen).string(), attrLen));
+            for (size_t i=0; i<N; i++) {
+                size_t curNsLen = 0, curAttrLen = 0;
+                const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
+                const char16_t* curAttr = getAttributeName(i, &curAttrLen);
+                STRING_POOL_NOISY(ALOGI("  curNs=%s (%d), curAttr=%s (%d)",
+                        String8(curNs, curNsLen).string(), curNsLen,
+                        String8(curAttr, curAttrLen).string(), curAttrLen));
+                if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+                        && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) {
+                    if (ns == NULL) {
+                        if (curNs == NULL) {
+                            STRING_POOL_NOISY(ALOGI("  FOUND!"));
+                            return i;
+                        }
+                    } else if (curNs != NULL) {
+                        //printf(" --> ns=%s, curNs=%s\n",
+                        //       String8(ns).string(), String8(curNs).string());
+                        if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) {
+                            STRING_POOL_NOISY(ALOGI("  FOUND!"));
+                            return i;
+                        }
+                    }
                 }
             }
         }
@@ -2940,7 +3077,7 @@
     mHeaders.clear();
 }
 
-bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
+bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
 {
     if (mError != NO_ERROR) {
         return false;
@@ -2980,13 +3117,28 @@
 
         outName->package = grp->name.string();
         outName->packageLen = grp->name.size();
-        outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
-        outName->name = grp->basePackage->keyStrings.stringAt(
-            dtohl(entry->key.index), &outName->nameLen);
-
-        // If we have a bad index for some reason, we should abort.
-        if (outName->type == NULL || outName->name == NULL) {
-            return false;
+        if (allowUtf8) {
+            outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
+            outName->name8 = grp->basePackage->keyStrings.string8At(
+                dtohl(entry->key.index), &outName->nameLen);
+        } else {
+            outName->type8 = NULL;
+            outName->name8 = NULL;
+        }
+        if (outName->type8 == NULL) {
+            outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
+            // If we have a bad index for some reason, we should abort.
+            if (outName->type == NULL) {
+                return false;
+            }
+        }
+        if (outName->name8 == NULL) {
+            outName->name = grp->basePackage->keyStrings.stringAt(
+                dtohl(entry->key.index), &outName->nameLen);
+            // If we have a bad index for some reason, we should abort.
+            if (outName->name == NULL) {
+                return false;
+            }
         }
 
         return true;
@@ -4485,7 +4637,7 @@
             while (cnt > 0) {
                 if (!Res_INTERNALID(bag->map.name.ident)) {
                     //printf("Trying attr #%08x\n", bag->map.name.ident);
-                    if (getResourceName(bag->map.name.ident, &rname)) {
+                    if (getResourceName(bag->map.name.ident, false, &rname)) {
                         #if 0
                         printf("Matching %s against %s (0x%08x)\n",
                                String8(s, len).string(),
@@ -4538,7 +4690,7 @@
                 for (i=0; i<cnt; i++, bagi++) {
                     if (!Res_INTERNALID(bagi->map.name.ident)) {
                         //printf("Trying attr #%08x\n", bagi->map.name.ident);
-                        if (getResourceName(bagi->map.name.ident, &rname)) {
+                        if (getResourceName(bagi->map.name.ident, false, &rname)) {
                             #if 0
                             printf("Matching %s against %s (0x%08x)\n",
                                    String8(start,pos-start).string(),
@@ -5216,7 +5368,7 @@
                 | (0x00ff0000 & ((typeIndex+1)<<16))
                 | (0x0000ffff & (entryIndex));
             resource_name resName;
-            if (!this->getResourceName(resID, &resName)) {
+            if (!this->getResourceName(resID, true, &resName)) {
                 ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
                 // add dummy value, or trimming leading/trailing zeroes later will fail
                 vector.push(0);
@@ -5483,12 +5635,23 @@
                                     | (0x00ff0000 & ((typeIndex+1)<<16))
                                     | (0x0000ffff & (entryIndex));
                         resource_name resName;
-                        if (this->getResourceName(resID, &resName)) {
+                        if (this->getResourceName(resID, true, &resName)) {
+                            String8 type8;
+                            String8 name8;
+                            if (resName.type8 != NULL) {
+                                type8 = String8(resName.type8, resName.typeLen);
+                            } else {
+                                type8 = String8(resName.type, resName.typeLen);
+                            }
+                            if (resName.name8 != NULL) {
+                                name8 = String8(resName.name8, resName.nameLen);
+                            } else {
+                                name8 = String8(resName.name, resName.nameLen);
+                            }
                             printf("      spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
                                 resID,
                                 CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                                CHAR16_TO_CSTR(resName.type, resName.typeLen),
-                                CHAR16_TO_CSTR(resName.name, resName.nameLen),
+                                type8.string(), name8.string(),
                                 dtohl(typeConfigs->typeSpecFlags[entryIndex]));
                         } else {
                             printf("      INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
@@ -5531,11 +5694,22 @@
                                     | (0x00ff0000 & ((typeIndex+1)<<16))
                                     | (0x0000ffff & (entryIndex));
                         resource_name resName;
-                        if (this->getResourceName(resID, &resName)) {
+                        if (this->getResourceName(resID, true, &resName)) {
+                            String8 type8;
+                            String8 name8;
+                            if (resName.type8 != NULL) {
+                                type8 = String8(resName.type8, resName.typeLen);
+                            } else {
+                                type8 = String8(resName.type, resName.typeLen);
+                            }
+                            if (resName.name8 != NULL) {
+                                name8 = String8(resName.name8, resName.nameLen);
+                            } else {
+                                name8 = String8(resName.name, resName.nameLen);
+                            }
                             printf("        resource 0x%08x %s:%s/%s: ", resID,
                                     CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                                    CHAR16_TO_CSTR(resName.type, resName.typeLen),
-                                    CHAR16_TO_CSTR(resName.name, resName.nameLen));
+                                    type8.string(), name8.string());
                         } else {
                             printf("        INVALID RESOURCE 0x%08x: ", resID);
                         }
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 6ac637e..df966e1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -256,8 +256,12 @@
     log.appendFormat("  PatchCache           %8d / %8d\n",
             patchCache.getSize(), patchCache.getMaxSize());
     for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
-        const uint32_t size = fontRenderer->getFontRendererSize(i);
-        log.appendFormat("  FontRenderer %d       %8d / %8d\n", i, size, size);
+        const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA);
+        const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA);
+        log.appendFormat("  FontRenderer %d A8    %8d / %8d\n", i, sizeA8, sizeA8);
+        log.appendFormat("  FontRenderer %d RGBA  %8d / %8d\n", i, sizeRGBA, sizeRGBA);
+        log.appendFormat("  FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA,
+                sizeA8 + sizeRGBA);
     }
     log.appendFormat("Other:\n");
     log.appendFormat("  FboCache             %8d / %8d\n",
@@ -272,7 +276,8 @@
     total += dropShadowCache.getSize();
     total += patchCache.getSize();
     for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
-        total += fontRenderer->getFontRendererSize(i);
+        total += fontRenderer->getFontRendererSize(i, GL_ALPHA);
+        total += fontRenderer->getFontRendererSize(i, GL_RGBA);
     }
 
     log.appendFormat("Total memory usage:\n");
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 5ac2511..be0800f 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -594,7 +594,6 @@
 
 private:
     SkRegion* mRegion;
-    SkRegion::Op mOp;
 };
 
 class ResetShaderOp : public StateOp {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 79a7a93..1b2f651 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,7 +21,6 @@
 
 #include <cutils/properties.h>
 
-#include <utils/Functor.h>
 #include <utils/Log.h>
 
 #ifdef ANDROID_ENABLE_RENDERSCRIPT
@@ -35,6 +34,7 @@
 #include "Debug.h"
 #include "Extensions.h"
 #include "FontRenderer.h"
+#include "OpenGLRenderer.h"
 #include "PixelBuffer.h"
 #include "Rect.h"
 
@@ -45,6 +45,52 @@
 #define RS_MIN_INPUT_CUTOFF 10000
 
 ///////////////////////////////////////////////////////////////////////////////
+// TextSetupFunctor
+///////////////////////////////////////////////////////////////////////////////
+status_t TextSetupFunctor::operator ()(int what, void* data) {
+    Data* typedData = reinterpret_cast<Data*>(data);
+    GLenum glyphFormat = typedData ? typedData->glyphFormat : GL_ALPHA;
+
+    renderer->setupDraw();
+    renderer->setupDrawTextGamma(paint);
+    renderer->setupDrawDirtyRegionsDisabled();
+    renderer->setupDrawWithTexture(glyphFormat == GL_ALPHA);
+    switch (glyphFormat) {
+        case GL_ALPHA: {
+            renderer->setupDrawAlpha8Color(paint->getColor(), alpha);
+            break;
+        }
+        case GL_RGBA: {
+            float floatAlpha = alpha / 255.0f;
+            renderer->setupDrawColor(floatAlpha, floatAlpha, floatAlpha, floatAlpha);
+            break;
+        }
+        default: {
+#if DEBUG_FONT_RENDERER
+            ALOGD("TextSetupFunctor: called with unknown glyph format %x", glyphFormat);
+#endif
+            break;
+        }
+    }
+    renderer->setupDrawColorFilter();
+    renderer->setupDrawShader();
+    renderer->setupDrawBlending(true, mode);
+    renderer->setupDrawProgram();
+    renderer->setupDrawModelView(x, y, x, y, pureTranslate, true);
+    // Calling setupDrawTexture with the name 0 will enable the
+    // uv attributes and increase the texture unit count
+    // texture binding will be performed by the font renderer as
+    // needed
+    renderer->setupDrawTexture(0);
+    renderer->setupDrawPureColorUniforms();
+    renderer->setupDrawColorFilterUniforms();
+    renderer->setupDrawShaderUniforms(pureTranslate);
+    renderer->setupDrawTextGammaUniforms();
+
+    return NO_ERROR;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // FontRenderer
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -103,11 +149,16 @@
     sLogFontRendererCreate = false;
 }
 
-FontRenderer::~FontRenderer() {
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        delete mCacheTextures[i];
+void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) {
+    for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+        delete cacheTextures[i];
     }
-    mCacheTextures.clear();
+    cacheTextures.clear();
+}
+
+FontRenderer::~FontRenderer() {
+    clearCacheTextures(mACacheTextures);
+    clearCacheTextures(mRGBACacheTextures);
 
     LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
     while (it.next()) {
@@ -124,15 +175,19 @@
         it.value()->invalidateTextureCache();
     }
 
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        mCacheTextures[i]->init();
+    for (uint32_t i = 0; i < mACacheTextures.size(); i++) {
+        mACacheTextures[i]->init();
+    }
+
+    for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
+        mRGBACacheTextures[i]->init();
     }
 }
 
-void FontRenderer::flushLargeCaches() {
+void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
     // Start from 1; don't deallocate smallest/default texture
-    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
-        CacheTexture* cacheTexture = mCacheTextures[i];
+    for (uint32_t i = 1; i < cacheTextures.size(); i++) {
+        CacheTexture* cacheTexture = cacheTextures[i];
         if (cacheTexture->getPixelBuffer()) {
             cacheTexture->init();
             LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
@@ -144,11 +199,16 @@
     }
 }
 
-CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
-        uint32_t* startX, uint32_t* startY) {
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
-            return mCacheTextures[i];
+void FontRenderer::flushLargeCaches() {
+    flushLargeCaches(mACacheTextures);
+    flushLargeCaches(mRGBACacheTextures);
+}
+
+CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures,
+        const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) {
+    for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+        if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) {
+            return cacheTextures[i];
         }
     }
     // Could not fit glyph into current cache textures
@@ -169,9 +229,26 @@
 
     cachedGlyph->mIsValid = false;
 
+    // choose an appropriate cache texture list for this glyph format
+    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
+    Vector<CacheTexture*>* cacheTextures = NULL;
+    switch (format) {
+        case SkMask::kA8_Format:
+            cacheTextures = &mACacheTextures;
+            break;
+        case SkMask::kARGB32_Format:
+            cacheTextures = &mRGBACacheTextures;
+            break;
+        default:
+#if DEBUG_FONT_RENDERER
+            ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format);
+#endif
+        return;
+    }
+
     // If the glyph is too tall, don't cache it
     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
-                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
+                (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) {
         ALOGE("Font size too large to fit in cache. width, height = %i, %i",
                 (int) glyph.fWidth, (int) glyph.fHeight);
         return;
@@ -181,14 +258,14 @@
     uint32_t startX = 0;
     uint32_t startY = 0;
 
-    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+    CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
 
     if (!cacheTexture) {
         if (!precaching) {
             // If the new glyph didn't fit and we are not just trying to precache it,
             // clear out the cache and try again
             flushAllAndInvalidate();
-            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+            cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
         }
 
         if (!cacheTexture) {
@@ -216,24 +293,21 @@
         cacheTexture->allocateMesh();
     }
 
-    // Tells us whether the glyphs is B&W (1 bit per pixel)
-    // or anti-aliased (8 bits per pixel)
-    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
-
     uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map();
-    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
+    int srcStride = glyph.rowBytes();
 
     // Copy the glyph image, taking the mask format into account
-    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
-    int stride = glyph.rowBytes();
-
-    uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
-    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
-
     switch (format) {
         case SkMask::kA8_Format: {
+            uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+            uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
+                    - TEXTURE_BORDER_SIZE;
+            // write leading border line
+            memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+            // write glyph data
             if (mGammaTable) {
-                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
                     row = cacheY * cacheWidth;
                     cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
                     for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
@@ -243,21 +317,55 @@
                     cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
                 }
             } else {
-                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
                     row = cacheY * cacheWidth;
                     memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
                     cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
                     cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
                 }
             }
+            // write trailing border line
+            row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+            memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+            break;
+        }
+        case SkMask::kARGB32_Format: {
+            // prep data lengths
+            const size_t formatSize = PixelBuffer::formatSize(GL_RGBA);
+            const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE;
+            size_t rowSize = formatSize * glyph.fWidth;
+            // prep advances
+            size_t dstStride = formatSize * cacheWidth;
+            // prep indices
+            // - we actually start one row early, and then increment before first copy
+            uint8_t* src = &bitmapBuffer[0 - srcStride];
+            uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)];
+            uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)];
+            uint8_t* dstL = dst - borderSize;
+            uint8_t* dstR = dst + rowSize;
+            // write leading border line
+            memset(dstL, 0, rowSize + 2 * borderSize);
+            // write glyph data
+            while (dst < dstEnd) {
+                memset(dstL += dstStride, 0, borderSize); // leading border column
+                memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data
+                memset(dstR += dstStride, 0, borderSize); // trailing border column
+            }
+            // write trailing border line
+            memset(dstL, 0, rowSize + 2 * borderSize);
             break;
         }
         case SkMask::kBW_Format: {
+            uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+            uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
+                    - TEXTURE_BORDER_SIZE;
             static const uint8_t COLORS[2] = { 0, 255 };
-
+            // write leading border line
+            memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+            // write glyph data
             for (cacheY = startY; cacheY < endY; cacheY++) {
                 cacheX = startX;
-                int rowBytes = stride;
+                int rowBytes = srcStride;
                 uint8_t* buffer = bitmapBuffer;
 
                 row = cacheY * cacheWidth;
@@ -270,23 +378,24 @@
                 }
                 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
 
-                bitmapBuffer += stride;
+                bitmapBuffer += srcStride;
             }
+            // write trailing border line
+            row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+            memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
             break;
         }
         default:
-            ALOGW("Unkown glyph format: 0x%x", format);
+            ALOGW("Unknown glyph format: 0x%x", format);
             break;
     }
 
-    row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
-    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
-
     cachedGlyph->mIsValid = true;
 }
 
-CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
-    CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads);
+CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format,
+        bool allocate) {
+    CacheTexture* cacheTexture = new CacheTexture(width, height, format, gMaxNumberOfQuads);
 
     if (allocate) {
         Caches::getInstance().activeTexture(0);
@@ -298,17 +407,23 @@
 }
 
 void FontRenderer::initTextTexture() {
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        delete mCacheTextures[i];
-    }
-    mCacheTextures.clear();
+    clearCacheTextures(mACacheTextures);
+    clearCacheTextures(mRGBACacheTextures);
 
     mUploadTexture = false;
-    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
-    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
-    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
-    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
-    mCurrentCacheTexture = mCacheTextures[0];
+    mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+            GL_ALPHA, true));
+    mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+            GL_ALPHA, false));
+    mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+            GL_ALPHA, false));
+    mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight,
+            GL_ALPHA, false));
+    mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+            GL_RGBA, false));
+    mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+            GL_RGBA, false));
+    mCurrentCacheTexture = mACacheTextures[0];
 }
 
 // We don't want to allocate anything unless we actually draw text
@@ -322,6 +437,24 @@
     mInitialized = true;
 }
 
+void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures,
+        bool& resetPixelStore, GLuint& lastTextureId) {
+    for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+        CacheTexture* cacheTexture = cacheTextures[i];
+        if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) {
+            if (cacheTexture->getTextureId() != lastTextureId) {
+                lastTextureId = cacheTexture->getTextureId();
+                caches.activeTexture(0);
+                caches.bindTexture(lastTextureId);
+            }
+
+            if (cacheTexture->upload()) {
+                resetPixelStore = true;
+            }
+        }
+    }
+}
+
 void FontRenderer::checkTextureUpdate() {
     if (!mUploadTexture) {
         return;
@@ -334,25 +467,8 @@
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
     // Iterate over all the cache textures and see which ones need to be updated
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        CacheTexture* cacheTexture = mCacheTextures[i];
-        if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) {
-            if (cacheTexture->getTextureId() != lastTextureId) {
-                lastTextureId = cacheTexture->getTextureId();
-                caches.activeTexture(0);
-                caches.bindTexture(lastTextureId);
-            }
-
-            if (cacheTexture->upload()) {
-                resetPixelStore = true;
-            }
-
-#if DEBUG_FONT_RENDERER
-            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
-                    i, x, y, width, height);
-#endif
-        }
-    }
+    checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId);
+    checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId);
 
     // Unbind any PBO we might have used to update textures
     caches.unbindPixelBuffer();
@@ -366,18 +482,18 @@
     mUploadTexture = false;
 }
 
-void FontRenderer::issueDrawCommand() {
+void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
+    Caches& caches = Caches::getInstance();
     bool first = true;
     bool force = false;
-
-    GLuint lastId = 0;
-    Caches& caches = Caches::getInstance();
-
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        CacheTexture* texture = mCacheTextures[i];
+    for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+        CacheTexture* texture = cacheTextures[i];
         if (texture->canDraw()) {
             if (first) {
-                if (mFunctor) (*mFunctor)(0, NULL);
+                if (mFunctor) {
+                    TextSetupFunctor::Data functorData(texture->getFormat());
+                    (*mFunctor)(0, &functorData);
+                }
 
                 checkTextureUpdate();
                 caches.bindIndicesBuffer();
@@ -407,6 +523,11 @@
             texture->resetMesh();
         }
     }
+}
+
+void FontRenderer::issueDrawCommand() {
+    issueDrawCommand(mACacheTextures);
+    issueDrawCommand(mRGBACacheTextures);
 
     mDrawn = true;
 }
@@ -582,13 +703,13 @@
 
 bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
         uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
-        float hOffset, float vOffset, Rect* bounds) {
+        float hOffset, float vOffset, Rect* bounds, Functor* functor) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
     }
 
-    initRender(clip, bounds, NULL);
+    initRender(clip, bounds, functor);
     mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
     finishRender();
 
@@ -646,10 +767,10 @@
     delete[] scratch;
 }
 
-uint32_t FontRenderer::getCacheSize() const {
+static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) {
     uint32_t size = 0;
-    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
-        CacheTexture* cacheTexture = mCacheTextures[i];
+    for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+        CacheTexture* cacheTexture = cacheTextures[i];
         if (cacheTexture && cacheTexture->getPixelBuffer()) {
             size += cacheTexture->getPixelBuffer()->getSize();
         }
@@ -657,5 +778,19 @@
     return size;
 }
 
+uint32_t FontRenderer::getCacheSize(GLenum format) const {
+    switch (format) {
+        case GL_ALPHA: {
+            return calculateCacheSize(mACacheTextures);
+        }
+        case GL_RGBA: {
+            return calculateCacheSize(mRGBACacheTextures);
+        }
+        default: {
+            return 0;
+        }
+    }
+}
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index c1072ed..aca47b4 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_HWUI_FONT_RENDERER_H
 #define ANDROID_HWUI_FONT_RENDERER_H
 
+#include <utils/Functor.h>
 #include <utils/LruCache.h>
 #include <utils/Vector.h>
 #include <utils/StrongPointer.h>
@@ -46,8 +47,40 @@
 namespace android {
 namespace uirenderer {
 
+class OpenGLRenderer;
+
 ///////////////////////////////////////////////////////////////////////////////
-// Renderer
+// TextSetupFunctor
+///////////////////////////////////////////////////////////////////////////////
+class TextSetupFunctor: public Functor {
+public:
+    struct Data {
+        Data(GLenum glyphFormat) : glyphFormat(glyphFormat) {
+        }
+
+        GLenum glyphFormat;
+    };
+
+    TextSetupFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+            int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
+            renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
+            alpha(alpha), mode(mode), paint(paint) {
+    }
+    ~TextSetupFunctor() { }
+
+    status_t operator ()(int what, void* data);
+
+    OpenGLRenderer* renderer;
+    float x;
+    float y;
+    bool pureTranslate;
+    int alpha;
+    SkXfermode::Mode mode;
+    SkPaint* paint;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// FontRenderer
 ///////////////////////////////////////////////////////////////////////////////
 
 class FontRenderer {
@@ -55,6 +88,7 @@
     FontRenderer();
     ~FontRenderer();
 
+    void flushLargeCaches(Vector<CacheTexture*>& cacheTextures);
     void flushLargeCaches();
 
     void setGammaTable(const uint8_t* gammaTable) {
@@ -73,7 +107,8 @@
 
     // bounds is an out parameter
     bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
-            uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
+            uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds,
+            Functor* functor);
 
     struct DropShadow {
         DropShadow() { };
@@ -100,7 +135,7 @@
         mLinearFiltering = linearFiltering;
     }
 
-    uint32_t getCacheSize() const;
+    uint32_t getCacheSize(GLenum format) const;
 
 private:
     friend class Font;
@@ -110,10 +145,11 @@
     void allocateTextureMemory(CacheTexture* cacheTexture);
     void deallocateTextureMemory(CacheTexture* cacheTexture);
     void initTextTexture();
-    CacheTexture* createCacheTexture(int width, int height, bool allocate);
+    CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate);
     void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
             uint32_t *retOriginX, uint32_t *retOriginY, bool precaching);
-    CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY);
+    CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph,
+            uint32_t* startX, uint32_t* startY);
 
     void flushAllAndInvalidate();
 
@@ -121,6 +157,7 @@
     void initRender(const Rect* clip, Rect* bounds, Functor* functor);
     void finishRender();
 
+    void issueDrawCommand(Vector<CacheTexture*>& cacheTextures);
     void issueDrawCommand();
     void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
             float x2, float y2, float u2, float v2,
@@ -148,7 +185,8 @@
     uint32_t mLargeCacheWidth;
     uint32_t mLargeCacheHeight;
 
-    Vector<CacheTexture*> mCacheTextures;
+    Vector<CacheTexture*> mACacheTextures;
+    Vector<CacheTexture*> mRGBACacheTextures;
 
     Font* mCurrentFont;
     LruCache<Font::FontDescription, Font*> mActiveFonts;
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index bbfa66d..46cfd04 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -35,7 +35,7 @@
     virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0;
 
     virtual uint32_t getFontRendererCount() const = 0;
-    virtual uint32_t getFontRendererSize(uint32_t fontRenderer) const = 0;
+    virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0;
 
     virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0;
     virtual void setupProgram(ProgramDescription& description, Program* program) const = 0;
@@ -81,8 +81,8 @@
         return 1;
     }
 
-    uint32_t getFontRendererSize(uint32_t fontRenderer) const {
-        return mRenderer ? mRenderer->getCacheSize() : 0;
+    uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
+        return mRenderer ? mRenderer->getCacheSize(format) : 0;
     }
 
     void describe(ProgramDescription& description, const SkPaint* paint) const;
@@ -128,8 +128,8 @@
         return 1;
     }
 
-    uint32_t getFontRendererSize(uint32_t fontRenderer) const {
-        return mRenderer ? mRenderer->getCacheSize() : 0;
+    uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
+        return mRenderer ? mRenderer->getCacheSize(format) : 0;
     }
 
     void describe(ProgramDescription& description, const SkPaint* paint) const {
@@ -162,13 +162,13 @@
         return kGammaCount;
     }
 
-    uint32_t getFontRendererSize(uint32_t fontRenderer) const {
+    uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
         if (fontRenderer >= kGammaCount) return 0;
 
         FontRenderer* renderer = mRenderers[fontRenderer];
         if (!renderer) return 0;
 
-        return renderer->getCacheSize();
+        return renderer->getCacheSize(format);
     }
 
     void describe(ProgramDescription& description, const SkPaint* paint) const {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bc00ce8..36be9a7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1668,7 +1668,7 @@
     SkPath path;
     path.addRect(left, top, right, bottom);
 
-    return clipPath(&path, op);
+    return OpenGLRenderer::clipPath(&path, op);
 }
 
 bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
@@ -1679,11 +1679,15 @@
     path->transform(transform, &transformed);
 
     SkRegion clip;
-    if (!mSnapshot->clipRegion->isEmpty()) {
-        clip.setRegion(*mSnapshot->clipRegion);
+    if (!mSnapshot->previous->clipRegion->isEmpty()) {
+        clip.setRegion(*mSnapshot->previous->clipRegion);
     } else {
-        Rect* bounds = mSnapshot->clipRect;
-        clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+        if (mSnapshot->previous == mFirstSnapshot) {
+            clip.setRect(0, 0, mWidth, mHeight);
+        } else {
+            Rect* bounds = mSnapshot->previous->clipRect;
+            clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+        }
     }
 
     SkRegion region;
@@ -2825,48 +2829,6 @@
     return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
 }
 
-class TextSetupFunctor: public Functor {
-public:
-    TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate,
-            int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
-            renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
-            alpha(alpha), mode(mode), paint(paint) {
-    }
-    ~TextSetupFunctor() { }
-
-    status_t operator ()(int what, void* data) {
-        renderer.setupDraw();
-        renderer.setupDrawTextGamma(paint);
-        renderer.setupDrawDirtyRegionsDisabled();
-        renderer.setupDrawWithTexture(true);
-        renderer.setupDrawAlpha8Color(paint->getColor(), alpha);
-        renderer.setupDrawColorFilter();
-        renderer.setupDrawShader();
-        renderer.setupDrawBlending(true, mode);
-        renderer.setupDrawProgram();
-        renderer.setupDrawModelView(x, y, x, y, pureTranslate, true);
-        // Calling setupDrawTexture with the name 0 will enable the
-        // uv attributes and increase the texture unit count
-        // texture binding will be performed by the font renderer as
-        // needed
-        renderer.setupDrawTexture(0);
-        renderer.setupDrawPureColorUniforms();
-        renderer.setupDrawColorFilterUniforms();
-        renderer.setupDrawShaderUniforms(pureTranslate);
-        renderer.setupDrawTextGammaUniforms();
-
-        return NO_ERROR;
-    }
-
-    OpenGLRenderer& renderer;
-    float x;
-    float y;
-    bool pureTranslate;
-    int alpha;
-    SkXfermode::Mode mode;
-    SkPaint* paint;
-};
-
 status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
         const float* positions, SkPaint* paint) {
     if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
@@ -2912,7 +2874,7 @@
 
     const bool hasActiveLayer = hasLayer();
 
-    TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
+    TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
     if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
             positions, hasActiveLayer ? &bounds : NULL, &functor)) {
         if (hasActiveLayer) {
@@ -3003,7 +2965,7 @@
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
-    TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
+    TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
 
     // don't call issuedrawcommand, do it at end of batch
     bool forceFinish = (drawOpMode != kDrawOpMode_Defer);
@@ -3045,26 +3007,7 @@
     int alpha;
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
-
-    setupDraw();
-    setupDrawTextGamma(paint);
-    setupDrawDirtyRegionsDisabled();
-    setupDrawWithTexture(true);
-    setupDrawAlpha8Color(paint->getColor(), alpha);
-    setupDrawColorFilter();
-    setupDrawShader();
-    setupDrawBlending(true, mode);
-    setupDrawProgram();
-    setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
-    // Calling setupDrawTexture with the name 0 will enable the
-    // uv attributes and increase the texture unit count
-    // texture binding will be performed by the font renderer as
-    // needed
-    setupDrawTexture(0);
-    setupDrawPureColorUniforms();
-    setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(false);
-    setupDrawTextGammaUniforms();
+    TextSetupFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
 
     const Rect* clip = &mSnapshot->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
@@ -3072,7 +3015,7 @@
     const bool hasActiveLayer = hasLayer();
 
     if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
-            hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
+            hOffset, vOffset, hasActiveLayer ? &bounds : NULL, &functor)) {
         if (hasActiveLayer) {
             currentTransform().mapRect(bounds);
             dirtyLayerUnchecked(bounds, getRegion());
diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h
index 32d5417..9725a61 100644
--- a/libs/hwui/PixelBuffer.h
+++ b/libs/hwui/PixelBuffer.h
@@ -112,13 +112,25 @@
     virtual uint8_t* getMappedPointer() const = 0;
 
     /**
-     * Upload the specified rectangle of this pixe buffer as a
+     * Upload the specified rectangle of this pixel buffer as a
      * GL_TEXTURE_2D texture. Calling this method will trigger
      * an unmap() if necessary.
      */
     virtual void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) = 0;
 
     /**
+     * Upload the specified rectangle of this pixel buffer as a
+     * GL_TEXTURE_2D texture. Calling this method will trigger
+     * an unmap() if necessary.
+     *
+     * This is a convenience function provided to save callers the
+     * trouble of computing the offset parameter.
+     */
+    void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+        upload(x, y, width, height, getOffset(x, y));
+    }
+
+    /**
      * Returns the width of the render buffer in pixels.
      */
     uint32_t getWidth() const {
@@ -140,6 +152,13 @@
     }
 
     /**
+     * Returns the offset of a pixel in this pixel buffer, in bytes.
+     */
+    uint32_t getOffset(uint32_t x, uint32_t y) const {
+        return (y * mWidth + x) * formatSize(mFormat);
+    }
+
+    /**
      * Returns the number of bytes per pixel in the specified format.
      *
      * Supported formats:
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index ba2e6f2..2764523 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "Debug.h"
 #include "Extensions.h"
 #include "Properties.h"
 #include "Stencil.h"
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 5f15724..55503ce 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -108,8 +108,8 @@
 // CacheTexture
 ///////////////////////////////////////////////////////////////////////////////
 
-CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
-            mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
+CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) :
+            mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format),
             mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
             mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
             mCaches(Caches::getInstance()) {
@@ -182,7 +182,7 @@
 
 void CacheTexture::allocateTexture() {
     if (!mTexture) {
-        mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight);
+        mTexture = PixelBuffer::create(mFormat, mWidth, mHeight);
     }
 
     if (!mTextureId) {
@@ -191,8 +191,8 @@
         mCaches.bindTexture(mTextureId);
         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
         // Initialize texture dimensions
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
-                GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+        glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+                mFormat, GL_UNSIGNED_BYTE, 0);
 
         const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
@@ -217,8 +217,7 @@
         glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
     }
 
-    mTexture->upload(x, y, width, height, y * mWidth + x);
-
+    mTexture->upload(x, y, width, height);
     setDirty(false);
 
     return mHasES3;
@@ -232,6 +231,30 @@
 }
 
 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
+    switch (glyph.fMaskFormat) {
+        case SkMask::kA8_Format:
+            if (mFormat != GL_ALPHA) {
+#if DEBUG_FONT_RENDERER
+                ALOGD("fitBitmap: kA8_Format glyph cannot fit into texture format %x", mFormat);
+#endif
+                return false;
+            }
+            break;
+        case SkMask::kARGB32_Format:
+            if (mFormat != GL_RGBA) {
+#if DEBUG_FONT_RENDERER
+                ALOGD("fitBitmap: kARGB32_Format glyph cannot fit into texture format %x", mFormat);
+#endif
+                return false;
+            }
+            break;
+        default:
+#if DEBUG_FONT_RENDERER
+            ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
+#endif
+            return false;
+    }
+
     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
         return false;
     }
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 208b1ff..028b611 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -24,6 +24,7 @@
 #include <utils/Log.h>
 
 #include "FontUtil.h"
+#include "../PixelBuffer.h"
 #include "../Rect.h"
 #include "../Vertex.h"
 
@@ -31,7 +32,6 @@
 namespace uirenderer {
 
 class Caches;
-class PixelBuffer;
 
 /**
  * CacheBlock is a node in a linked list of current free space areas in a CacheTexture.
@@ -74,7 +74,7 @@
 
 class CacheTexture {
 public:
-    CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount);
+    CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount);
     ~CacheTexture();
 
     void reset();
@@ -100,6 +100,14 @@
         return mHeight;
     }
 
+    inline GLenum getFormat() const {
+        return mFormat;
+    }
+
+    inline uint32_t getOffset(uint16_t x, uint16_t y) const {
+        return (y * mWidth + x) * PixelBuffer::formatSize(mFormat);
+    }
+
     inline const Rect* getDirtyRect() const {
         return &mDirtyRect;
     }
@@ -173,6 +181,7 @@
     GLuint mTextureId;
     uint16_t mWidth;
     uint16_t mHeight;
+    GLenum mFormat;
     bool mLinearFiltering;
     bool mDirty;
     uint16_t mNumGlyphs;
diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h
index f758666..cdcb23c 100644
--- a/libs/hwui/font/FontUtil.h
+++ b/libs/hwui/font/FontUtil.h
@@ -31,6 +31,9 @@
 #define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512
 
 #define TEXTURE_BORDER_SIZE 1
+#if TEXTURE_BORDER_SIZE != 1
+# error TEXTURE_BORDER_SIZE other than 1 is not currently supported
+#endif
 
 #define CACHE_BLOCK_ROUNDING_SIZE 4
 
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 5ebba93..ee748d39 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -147,6 +147,7 @@
     private int mNumUpdates = Integer.MAX_VALUE;  // no expiry
     private float mSmallestDisplacement = 0.0f;    // meters
     private WorkSource mWorkSource = new WorkSource();
+    private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
 
     private String mProvider = LocationManager.FUSED_PROVIDER;  // for deprecated APIs that explicitly request a provider
 
@@ -236,6 +237,7 @@
         mSmallestDisplacement = src.mSmallestDisplacement;
         mProvider = src.mProvider;
         mWorkSource = src.mWorkSource;
+        mHideFromAppOps = src.mHideFromAppOps;
     }
 
     /**
@@ -506,6 +508,16 @@
         return mWorkSource;
     }
 
+    /** @hide */
+    public void setHideFromAppOps(boolean hideFromAppOps) {
+        mHideFromAppOps = hideFromAppOps;
+    }
+
+    /** @hide */
+    public boolean getHideFromAppOps() {
+        return mHideFromAppOps;
+    }
+
     private static void checkInterval(long millis) {
         if (millis < 0) {
             throw new IllegalArgumentException("invalid interval: " + millis);
@@ -549,6 +561,7 @@
             request.setExpireAt(in.readLong());
             request.setNumUpdates(in.readInt());
             request.setSmallestDisplacement(in.readFloat());
+            request.setHideFromAppOps(in.readInt() != 0);
             String provider = in.readString();
             if (provider != null) request.setProvider(provider);
             WorkSource workSource = in.readParcelable(WorkSource.class.getClassLoader());
@@ -574,6 +587,7 @@
         parcel.writeLong(mExpireAt);
         parcel.writeInt(mNumUpdates);
         parcel.writeFloat(mSmallestDisplacement);
+        parcel.writeInt(mHideFromAppOps ? 1 : 0);
         parcel.writeString(mProvider);
         parcel.writeParcelable(mWorkSource, 0);
     }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bcd0398..14cdbb7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1555,6 +1555,22 @@
 
     /**
      * @hide
+     * Checks whether the current audio focus is exclusive.
+     * @return true if the top of the audio focus stack requested focus
+     *     with {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}
+     */
+    public boolean isAudioFocusExclusive() {
+        IAudioService service = getService();
+        try {
+            return service.getCurrentAudioFocus() == AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in isAudioFocusExclusive()", e);
+            return false;
+        }
+    }
+
+    /**
+     * @hide
      * If the stream is active locally or remotely, adjust its volume according to the enforced
      * priority rules.
      * Note: only AudioManager.STREAM_MUSIC is supported at the moment
@@ -1771,6 +1787,12 @@
     }
 
     /**
+     * @hide
+     * Used to indicate no audio focus has been gained or lost.
+     */
+    public static final int AUDIOFOCUS_NONE = 0;
+
+    /**
      * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
      * @see OnAudioFocusChangeListener#onAudioFocusChange(int)
      * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int)
@@ -1795,6 +1817,15 @@
      */
     public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
     /**
+     * Used to indicate a temporary request of audio focus, anticipated to last a short
+     * amount of time, during which no other applications, or system components, should play
+     * anything. Examples of exclusive and transient audio focus requests are voice
+     * memo recording and speech recognition, during which the system shouldn't play any
+     * notifications, and media playback should have paused.
+     * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int)
+     */
+    public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
+    /**
      * Used to indicate a loss of audio focus of unknown duration.
      * @see OnAudioFocusChangeListener#onAudioFocusChange(int)
      */
@@ -1958,14 +1989,17 @@
      *      for the playback of driving directions, or notifications sounds.
      *      Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for
      *      the previous focus owner to keep playing if it ducks its audio output.
+     *      Alternatively use {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} for a temporary request
+     *      that benefits from the system not playing disruptive sounds like notifications, for
+     *      usecases such as voice memo recording, or speech recognition.
      *      Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
      *      as the playback of a song or a video.
      *  @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
      */
     public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
         int status = AUDIOFOCUS_REQUEST_FAILED;
-        if ((durationHint < AUDIOFOCUS_GAIN) || (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK))
-        {
+        if ((durationHint < AUDIOFOCUS_GAIN) ||
+                (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
             Log.e(TAG, "Invalid duration hint, audio focus request denied");
             return status;
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index c178ae4..290866e 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -4198,6 +4198,10 @@
         mMediaFocusControl.unregisterAudioFocusClient(clientId);
     }
 
+    public int getCurrentAudioFocus() {
+        return mMediaFocusControl.getCurrentAudioFocus();
+    }
+
     //==========================================================================================
     // Device orientation
     //==========================================================================================
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 6b34b56..9a39994 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -30,16 +30,12 @@
  */
 class FocusRequester {
 
-    /**
-     * Used to indicate no audio focus has been gained or lost.
-     */
-    private static final int AUDIOFOCUS_NONE = 0;
-
     // on purpose not using this classe's name, as it will only be used from MediaFocusControl
     private static final String TAG = "MediaFocusControl";
+    private static final boolean DEBUG = false;
 
     private AudioFocusDeathHandler mDeathHandler;
-    private final IAudioFocusDispatcher mFocusDispatcher;
+    private final IAudioFocusDispatcher mFocusDispatcher; // may be null
     private final IBinder mSourceRef;
     private final String mClientId;
     private final String mPackageName;
@@ -49,7 +45,7 @@
      */
     private final int mFocusGainRequest;
     /**
-     * the audio focus loss received my mFocusDispatcher, is MediaFocusControl.AUDIOFOCUS_NONE if
+     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
      *  it never lost focus.
      */
     private int mFocusLossReceived;
@@ -69,14 +65,10 @@
         mPackageName = pn;
         mCallingUid = uid;
         mFocusGainRequest = focusRequest;
-        mFocusLossReceived = AUDIOFOCUS_NONE;
+        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
     }
 
 
-    boolean canDispatchFocus() {
-        return (mFocusDispatcher != null);
-    }
-
     boolean hasSameClient(String otherClient) {
         try {
             return mClientId.compareTo(otherClient) == 0;
@@ -113,7 +105,7 @@
 
     private static String focusChangeToString(int focus) {
         switch(focus) {
-            case AUDIOFOCUS_NONE:
+            case AudioManager.AUDIOFOCUS_NONE:
                 return "none";
             case AudioManager.AUDIOFOCUS_GAIN:
                 return "GAIN";
@@ -121,6 +113,8 @@
                 return "GAIN_TRANSIENT";
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                 return "GAIN_TRANSIENT_MAY_DUCK";
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+                return "GAIN_TRANSIENT_EXCLUSIVE";
             case AudioManager.AUDIOFOCUS_LOSS:
                 return "LOSS";
             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
@@ -181,23 +175,24 @@
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                     case AudioManager.AUDIOFOCUS_LOSS:
-                    case AUDIOFOCUS_NONE:
+                    case AudioManager.AUDIOFOCUS_NONE:
                         return AudioManager.AUDIOFOCUS_LOSS;
                 }
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                 switch(mFocusLossReceived) {
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                    case AUDIOFOCUS_NONE:
+                    case AudioManager.AUDIOFOCUS_NONE:
                         return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                     case AudioManager.AUDIOFOCUS_LOSS:
                         return AudioManager.AUDIOFOCUS_LOSS;
                 }
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                 switch(mFocusLossReceived) {
-                    case AUDIOFOCUS_NONE:
+                    case AudioManager.AUDIOFOCUS_NONE:
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                        return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                         return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                     case AudioManager.AUDIOFOCUS_LOSS:
@@ -205,26 +200,25 @@
                 }
             default:
                 Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
-                        return AUDIOFOCUS_NONE;
+                        return AudioManager.AUDIOFOCUS_NONE;
         }
     }
 
     void handleExternalFocusGain(int focusGain) {
-        try {
-            int focusLoss = focusLossForGainRequest(focusGain);
-            if (focusLoss != mFocusLossReceived) {
-                mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
-                mFocusLossReceived = focusLoss;
-            }
-        } catch (android.os.RemoteException e) {
-            Log.e(TAG, "Failure to signal loss of focus: ", e);
-        }
+        int focusLoss = focusLossForGainRequest(focusGain);
+        handleFocusLoss(focusLoss);
     }
 
     void handleFocusGain(int focusGain) {
         try {
-            mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
-            mFocusLossReceived = AUDIOFOCUS_NONE;
+            if (mFocusDispatcher != null) {
+                if (DEBUG) {
+                    Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+                        + mClientId);
+                }
+                mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
+            }
+            mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
         } catch (android.os.RemoteException e) {
             Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
         }
@@ -232,9 +226,16 @@
 
     void handleFocusLoss(int focusLoss) {
         try {
-            mFocusDispatcher.dispatchAudioFocusChange(
-                    focusLoss, mClientId);
-            mFocusLossReceived = focusLoss;
+            if (focusLoss != mFocusLossReceived) {
+                if (mFocusDispatcher != null) {
+                    if (DEBUG) {
+                        Log.v(TAG, "dispatching " + focusChangeToString(focusLoss) + " to "
+                            + mClientId);
+                    }
+                    mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
+                }
+                mFocusLossReceived = focusLoss;
+            }
         } catch (android.os.RemoteException e) {
             Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
         }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4a1646b..744e32a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -119,6 +119,8 @@
     
     void unregisterAudioFocusClient(String clientId);
 
+    int getCurrentAudioFocus();
+
     oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
     void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
 
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index ca57b92..ba2c63c 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -265,7 +265,7 @@
      */
     protected void discardAudioFocusOwner() {
         synchronized(mAudioFocusLock) {
-            if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
+            if (!mFocusStack.empty()) {
                 // notify the current focus owner it lost focus after removing it from stack
                 final FocusRequester exFocusOwner = mFocusStack.pop();
                 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
@@ -280,13 +280,26 @@
 
     private void notifyTopOfAudioFocusStack() {
         // notify the top of the stack it gained focus
-        if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
+        if (!mFocusStack.empty()) {
             if (canReassignAudioFocus()) {
                 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
             }
         }
     }
 
+    /**
+     * Focus is requested, propagate the associated loss throughout the stack.
+     * @param focusGain the new focus gain that will later be added at the top of the stack
+     */
+    private void propagateFocusLossFromGain_syncAf(int focusGain) {
+        // going through the audio focus stack to signal new focus, traversing order doesn't
+        // matter as all entries respond to the same external focus gain
+        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+        while(stackIterator.hasNext()) {
+            stackIterator.next().handleExternalFocusGain(focusGain);
+        }
+    }
+
     private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
 
     /**
@@ -411,14 +424,20 @@
         }
     }
 
+    protected int getCurrentAudioFocus() {
+        synchronized(mAudioFocusLock) {
+            if (mFocusStack.empty()) {
+                return AudioManager.AUDIOFOCUS_NONE;
+            } else {
+                return mFocusStack.peek().getGainRequest();
+            }
+        }
+    }
 
     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int)  */
     protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName) {
         Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId);
-        // the main stream type for the audio focus request is currently not used. It may
-        // potentially be used to handle multiple stream type-dependent audio focuses.
-
         // we need a valid binder callback for clients
         if (!cb.pingBinder()) {
             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -462,14 +481,14 @@
                 fr.release();
             }
 
-            // notify current top of stack it is losing focus
-            if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
-                mFocusStack.peek().handleExternalFocusGain(focusChangeHint);
-            }
-
             // focus requester might already be somewhere below in the stack, remove it
             removeFocusStackEntry(clientId, false /* signal */);
 
+            // propagate the focus change through the stack
+            if (!mFocusStack.empty()) {
+                propagateFocusLossFromGain_syncAf(focusChangeHint);
+            }
+
             // push focus requester at the top of the audio focus stack
             mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb,
                     clientId, afdh, callingPackageName, Binder.getCallingUid()));
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 1e45807..853353d 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_PACKAGE_NAME := DocumentsUI
 LOCAL_CERTIFICATE := platform
 
diff --git a/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..224cc4f
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 0000000..ff7b1de
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 0000000..cc661e3
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
index f96d459..eb6d803 100644
--- a/packages/DocumentsUI/res/layout/activity.xml
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -14,20 +14,34 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drawer_layout"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:layout_height="match_parent">
 
-    <FrameLayout
-        android:id="@+id/directory"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1" />
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <FrameLayout
-        android:id="@+id/save"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        <FrameLayout
+            android:id="@+id/directory"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1" />
 
-</LinearLayout>
+        <FrameLayout
+            android:id="@+id/save"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/roots_list"
+        android:layout_width="300dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:background="#fff" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml
deleted file mode 100644
index 6ec7566..0000000
--- a/packages/DocumentsUI/res/layout/item_backend.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingBottom="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/chip"
-        android:foreground="?android:attr/selectableItemBackground"
-        android:duplicateParentState="true">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="8dp"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@android:id/icon"
-                android:layout_width="24dip"
-                android:layout_height="24dip"
-                android:layout_marginEnd="8dp"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
-
-            <TextView
-                android:id="@android:id/text1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:singleLine="true"
-                android:ellipsize="marquee"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewStart" />
-
-        </LinearLayout>
-
-    </FrameLayout>
-
-</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
new file mode 100644
index 0000000..e9cf3aa
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -0,0 +1,60 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:gravity="center_vertical"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_rowSpan="2"
+        android:layout_marginEnd="8dip"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textAlignment="viewStart" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textAlignment="viewStart" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml
new file mode 100644
index 0000000..fe6c14d
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_title.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAlignment="viewStart" />
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index bf7c161..a0d03b2 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -19,5 +19,12 @@
         android:id="@+id/menu_create_dir"
         android:title="@string/menu_create_dir"
         android:icon="@drawable/ic_menu_create_dir"
-        android:showAsAction="always" />
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/menu_search"
+        android:title="@string/menu_search"
+        android:icon="@drawable/ic_menu_search"
+        android:showAsAction="always|collapseActionView"
+        android:actionViewClass="android.widget.SearchView"
+        android:imeOptions="actionSearch" />
 </menu>
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
index c1fa228..12d0324 100644
--- a/packages/DocumentsUI/res/menu/directory.xml
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -25,9 +25,4 @@
         android:title="@string/menu_list"
         android:icon="@drawable/ic_menu_list"
         android:showAsAction="ifRoom" />
-    <item
-        android:id="@+id/menu_sort"
-        android:title="@string/menu_sort"
-        android:icon="@drawable/ic_menu_sort"
-        android:showAsAction="ifRoom" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 6ae2d12..665f3b1 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -17,20 +17,26 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label">Documents</string>
 
-    <string name="title_open">Open</string>
-    <string name="title_save">Save</string>
+    <string name="title_open">Open from</string>
+    <string name="title_save">Save to</string>
 
     <string name="menu_create_dir">Create folder</string>
     <string name="menu_grid">Grid view</string>
     <string name="menu_list">List view</string>
     <string name="menu_sort">Sort by</string>
+    <string name="menu_search">Search</string>
 
     <string name="menu_open">Open</string>
     <string name="menu_save">Save</string>
 
     <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
 
-    <string name="sort_name">Name</string>
-    <string name="sort_date">Date modified</string>
+    <string name="sort_name">By name</string>
+    <string name="sort_date">By date modified</string>
+
+    <string name="drawer_open">Show roots</string>
+    <string name="drawer_close">Hide roots</string>
+
+    <string name="save_error">Failed to save document</string>
 
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
deleted file mode 100644
index fc13487..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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 android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.RootColumns;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.google.android.collect.Lists;
-
-import java.util.List;
-
-/**
- * Display all known storage roots.
- */
-public class BackendFragment extends Fragment {
-
-    // TODO: cluster backends by type
-
-    private GridView mGridView;
-    private BackendAdapter mAdapter;
-
-    public static void show(FragmentManager fm) {
-        final BackendFragment fragment = new BackendFragment();
-
-        final FragmentTransaction ft = fm.beginTransaction();
-        ft.replace(R.id.directory, fragment);
-        ft.setBreadCrumbTitle("TOP");
-        ft.commitAllowingStateLoss();
-    }
-
-    public static class Root {
-        public int rootType;
-        public Uri uri;
-        public Drawable icon;
-        public String title;
-        public String summary;
-
-        public static Root fromCursor(Context context, ProviderInfo info, Cursor cursor) {
-            final Root root = new Root();
-
-            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
-            root.uri = DocumentsContract.buildDocumentUri(
-                    info.authority, cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
-
-            final PackageManager pm = context.getPackageManager();
-            final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
-            if (icon != 0) {
-                try {
-                    root.icon = pm.getResourcesForApplication(info.applicationInfo)
-                            .getDrawable(icon);
-                } catch (NotFoundException e) {
-                    throw new RuntimeException(e);
-                } catch (NameNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                root.icon = info.loadIcon(pm);
-            }
-
-            root.title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
-            if (root.title == null) {
-                root.title = info.loadLabel(pm).toString();
-            }
-
-            root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
-
-            return root;
-        }
-    }
-
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        final Context context = inflater.getContext();
-
-        // Gather roots from known storage providers
-        final List<ProviderInfo> providers = context.getPackageManager()
-                .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
-        final List<Root> roots = Lists.newArrayList();
-        for (ProviderInfo info : providers) {
-            if (info.metaData != null
-                    && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
-                // TODO: populate roots on background thread, and cache results
-                final Uri uri = DocumentsContract.buildRootsUri(info.authority);
-                final Cursor cursor = context.getContentResolver()
-                        .query(uri, null, null, null, null);
-                try {
-                    while (cursor.moveToNext()) {
-                        roots.add(Root.fromCursor(context, info, cursor));
-                    }
-                } finally {
-                    cursor.close();
-                }
-            }
-        }
-
-        final View view = inflater.inflate(R.layout.fragment_backend, container, false);
-
-        mGridView = (GridView) view.findViewById(R.id.grid);
-        mGridView.setOnItemClickListener(mItemListener);
-
-        mAdapter = new BackendAdapter(context, roots);
-        mGridView.setAdapter(mAdapter);
-        mGridView.setNumColumns(GridView.AUTO_FIT);
-
-        return view;
-    }
-
-    private OnItemClickListener mItemListener = new OnItemClickListener() {
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final Root root = mAdapter.getItem(position);
-            ((DocumentsActivity) getActivity()).onRootPicked(root);
-        }
-    };
-
-    public static class BackendAdapter extends ArrayAdapter<Root> {
-        public BackendAdapter(Context context, List<Root> list) {
-            super(context, android.R.layout.simple_list_item_1, list);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_backend, parent, false);
-            }
-
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
-            final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-
-            final PackageManager pm = parent.getContext().getPackageManager();
-            final Root root = getItem(position);
-            icon.setImageDrawable(root.icon);
-            text1.setText(root.title);
-
-            return convertView;
-        }
-    }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 531eaf3..1f22613 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,17 +16,12 @@
 
 package com.android.documentsui;
 
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Context;
 import android.content.CursorLoader;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
@@ -65,8 +60,6 @@
 
     // TODO: show storage backend in item views when requested
 
-    private static final String TAG_SORT = "sort";
-
     private ListView mListView;
     private GridView mGridView;
 
@@ -81,7 +74,8 @@
 
     private static final int LOADER_DOCUMENTS = 2;
 
-    public static void show(FragmentManager fm, Uri uri, String displayName) {
+    public static void show(
+            FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
         final Bundle args = new Bundle();
         args.putParcelable(EXTRA_URI, uri);
 
@@ -90,7 +84,9 @@
 
         final FragmentTransaction ft = fm.beginTransaction();
         ft.replace(R.id.directory, fragment);
-        ft.addToBackStack(displayName);
+        if (addToBackStack) {
+            ft.addToBackStack(displayName);
+        }
         ft.setBreadCrumbTitle(displayName);
         ft.commitAllowingStateLoss();
     }
@@ -136,7 +132,13 @@
                     sortOrder = null;
                 }
 
-                final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
+                final Uri contentsUri;
+                if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
+                    contentsUri = uri;
+                } else {
+                    contentsUri = DocumentsContract.buildContentsUri(uri);
+                }
+
                 return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
             }
 
@@ -198,9 +200,6 @@
             updateMode();
             getFragmentManager().invalidateOptionsMenu();
             return true;
-        } else if (id == R.id.menu_sort) {
-            SortFragment.show(this);
-            return true;
         } else {
             return super.onOptionsItemSelected(item);
         }
@@ -238,7 +237,7 @@
         }
     }
 
-    private void updateSortBy() {
+    public void updateSortBy() {
         getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
     }
 
@@ -343,12 +342,15 @@
             final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
             final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
 
+            final Uri uri = getArguments().getParcelable(EXTRA_URI);
+            final String authority = uri.getAuthority();
+
             if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
-                final Uri uri = getArguments().getParcelable(EXTRA_URI);
-                final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
+                final Uri childUri = DocumentsContract.buildDocumentUri(authority, guid);
                 icon.setImageURI(childUri);
             } else {
-                icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
+                icon.setImageDrawable(
+                        DocumentsActivity.resolveDocumentIcon(context, authority, mimeType));
             }
 
             title.setText(displayName);
@@ -358,38 +360,6 @@
         }
     }
 
-    public static class SortFragment extends DialogFragment {
-        public static void show(DirectoryFragment parent) {
-            if (!parent.isAdded()) return;
-
-            final SortFragment dialog = new SortFragment();
-            dialog.setTargetFragment(parent, 0);
-            dialog.show(parent.getFragmentManager(), TAG_SORT);
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final Context context = getActivity();
-            final DisplayState state = getDisplayState(this);
-
-            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-            builder.setTitle(R.string.menu_sort);
-            builder.setSingleChoiceItems(new CharSequence[] {
-                    getText(R.string.sort_name),
-                    getText(R.string.sort_date),
-            }, state.sortBy, new OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    state.sortBy = which;
-                    ((DirectoryFragment) getTargetFragment()).updateSortBy();
-                    dismiss();
-                }
-            });
-
-            return builder.create();
-        }
-    }
-
     private static int getDocumentFlags(Context context, Uri uri) {
         final Cursor cursor = context.getContentResolver().query(uri, new String[] {
                 DocumentColumns.FLAGS }, null, null, null);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index e61cea6..dcd02d2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -25,7 +25,6 @@
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
 import android.app.FragmentManager.OnBackStackChangedListener;
 import android.content.ClipData;
 import android.content.ContentResolver;
@@ -35,26 +34,57 @@
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.RootColumns;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v4.widget.DrawerLayout.DrawerListener;
+import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Xml;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
 import android.widget.TextView;
+import android.widget.Toast;
 
-import com.android.documentsui.BackendFragment.Root;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 
 public class DocumentsActivity extends Activity {
@@ -63,18 +93,32 @@
     // TODO: fragment to show recently opened documents
     // TODO: pull actionbar icon from current backend
 
+    private static final String TAG_CREATE_DIRECTORY = "create_directory";
+
     private static final int ACTION_OPEN = 1;
     private static final int ACTION_CREATE = 2;
 
     private int mAction;
     private String[] mAcceptMimes;
 
+    private SearchView mSearchView;
+
+    private DrawerLayout mDrawerLayout;
+    private ActionBarDrawerToggle mDrawerToggle;
+
+    private static HashMap<String, DocumentsProviderInfo> sProviders = Maps.newHashMap();
+    private static ArrayList<Root> sRoots = Lists.newArrayList();
+
+    private RootsAdapter mRootsAdapter;
+    private ListView mRootsList;
+
     private final DisplayState mDisplayState = new DisplayState();
 
-    private boolean mIgnoreNextNavigation;
+    private Root mCurrentRoot;
 
     private Uri mCurrentDir;
     private boolean mCurrentSupportsCreate;
+    private boolean mCurrentSupportsSearch;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -106,40 +150,91 @@
         setContentView(R.layout.activity);
 
         getFragmentManager().addOnBackStackChangedListener(mStackListener);
-        BackendFragment.show(getFragmentManager());
-
-        updateActionBar();
 
         if (mAction == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
         }
+
+        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+        mRootsAdapter = new RootsAdapter(this, sRoots);
+        mRootsList = (ListView) findViewById(R.id.roots_list);
+        mRootsList.setAdapter(mRootsAdapter);
+        mRootsList.setOnItemClickListener(mRootsListener);
+
+        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
+                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
+
+        mDrawerLayout.setDrawerListener(mDrawerListener);
+        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+        mDrawerLayout.openDrawer(mRootsList);
+
+        updateActionBar();
+        updateRoots();
+    }
+
+    private DrawerListener mDrawerListener = new DrawerListener() {
+        @Override
+        public void onDrawerSlide(View drawerView, float slideOffset) {
+            mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
+        }
+
+        @Override
+        public void onDrawerOpened(View drawerView) {
+            mDrawerToggle.onDrawerOpened(drawerView);
+            updateActionBar();
+        }
+
+        @Override
+        public void onDrawerClosed(View drawerView) {
+            mDrawerToggle.onDrawerClosed(drawerView);
+            updateActionBar();
+        }
+
+        @Override
+        public void onDrawerStateChanged(int newState) {
+            mDrawerToggle.onDrawerStateChanged(newState);
+        }
+    };
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        mDrawerToggle.syncState();
     }
 
     public void updateActionBar() {
         final FragmentManager fm = getFragmentManager();
         final ActionBar actionBar = getActionBar();
 
-        if (fm.getBackStackEntryCount() > 0) {
-            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
-            actionBar.setDisplayShowHomeEnabled(true);
-            actionBar.setDisplayHomeAsUpEnabled(true);
-            actionBar.setTitle(null);
-            actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener);
-            actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1);
-            mIgnoreNextNavigation = true;
+        actionBar.setDisplayShowHomeEnabled(true);
+        actionBar.setDisplayHomeAsUpEnabled(true);
 
-        } else {
+        if (mDrawerLayout.isDrawerOpen(mRootsList)) {
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
-            actionBar.setDisplayShowHomeEnabled(false);
-            actionBar.setDisplayHomeAsUpEnabled(false);
+            actionBar.setIcon(new ColorDrawable());
 
             if (mAction == ACTION_OPEN) {
                 actionBar.setTitle(R.string.title_open);
             } else if (mAction == ACTION_CREATE) {
                 actionBar.setTitle(R.string.title_save);
             }
+
+        } else {
+            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+            if (mCurrentRoot != null) {
+                actionBar.setIcon(mCurrentRoot.icon);
+            }
+            actionBar.setTitle(null);
+            actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener);
+
+            if (fm.getBackStackEntryCount() > 0) {
+                mDrawerToggle.setDrawerIndicatorEnabled(false);
+            } else {
+                mDrawerToggle.setDrawerIndicatorEnabled(true);
+            }
         }
     }
 
@@ -147,6 +242,25 @@
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
         getMenuInflater().inflate(R.menu.activity, menu);
+
+        final MenuItem searchMenu = menu.findItem(R.id.menu_search);
+        mSearchView = (SearchView) searchMenu.getActionView();
+        mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String query) {
+                // TODO: clear existing directory stack?
+                final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query);
+                DirectoryFragment.show(getFragmentManager(), searchUri, query, true);
+                mSearchView.setIconified(true);
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String newText) {
+                return false;
+            }
+        });
+
         return true;
     }
 
@@ -158,17 +272,29 @@
         createDir.setVisible(mAction == ACTION_CREATE);
         createDir.setEnabled(mCurrentSupportsCreate);
 
+        // TODO: close any search in-progress when hiding
+        final MenuItem search = menu.findItem(R.id.menu_search);
+        search.setVisible(mCurrentSupportsSearch);
+
         return true;
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
+        if (mDrawerToggle.onOptionsItemSelected(item)) {
+            return true;
+        }
+
         final int id = item.getItemId();
         if (id == android.R.id.home) {
             getFragmentManager().popBackStack();
             updateActionBar();
+            return true;
         } else if (id == R.id.menu_create_dir) {
             CreateDirectoryFragment.show(getFragmentManager());
+            return true;
+        } else if (id == R.id.menu_search) {
+            return false;
         }
         return super.onOptionsItemSelected(item);
     }
@@ -180,46 +306,73 @@
         }
     };
 
-    private BaseAdapter mStackAdapter = new BaseAdapter() {
+    // TODO: support additional sort orders
+    private BaseAdapter mSortAdapter = new BaseAdapter() {
         @Override
         public int getCount() {
-            return getFragmentManager().getBackStackEntryCount();
+            return 2;
         }
 
         @Override
         public Object getItem(int position) {
-            return getFragmentManager().getBackStackEntryAt(position);
+            switch (position) {
+                case 0:
+                    return getText(R.string.sort_name);
+                case 1:
+                    return getText(R.string.sort_date);
+                default:
+                    return null;
+            }
         }
 
         @Override
         public long getItemId(int position) {
-            return getFragmentManager().getBackStackEntryAt(position).getId();
+            return position;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             if (convertView == null) {
                 convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_title, parent, false);
+            }
+
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+            final FragmentManager fm = getFragmentManager();
+            final int count = fm.getBackStackEntryCount();
+            if (count > 0) {
+                title.setText(fm.getBackStackEntryAt(count - 1).getBreadCrumbTitle());
+            } else if (mCurrentRoot != null) {
+                title.setText(mCurrentRoot.title);
+            } else {
+                title.setText(null);
+            }
+
+            summary.setText((String) getItem(position));
+
+            return convertView;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
                         .inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
             }
 
-            final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position);
             final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-            text1.setText(entry.getBreadCrumbTitle());
+            text1.setText((String) getItem(position));
 
             return convertView;
         }
     };
 
-    private OnNavigationListener mNavigationListener = new OnNavigationListener() {
+    private OnNavigationListener mSortListener = new OnNavigationListener() {
         @Override
         public boolean onNavigationItemSelected(int itemPosition, long itemId) {
-            if (mIgnoreNextNavigation) {
-                mIgnoreNextNavigation = false;
-                return false;
-            }
-
-            getFragmentManager().popBackStack((int) itemId, 0);
+            // TODO: request updated sort order
             return true;
         }
     };
@@ -231,6 +384,7 @@
     public void onDirectoryChanged(Uri uri, int flags) {
         mCurrentDir = uri;
         mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+        mCurrentSupportsSearch = (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0;
 
         if (mAction == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
@@ -240,15 +394,11 @@
         invalidateOptionsMenu();
     }
 
-    public void onRootPicked(Root root) {
-        DirectoryFragment.show(getFragmentManager(), root.uri, root.title);
-    }
-
     public void onDocumentPicked(Document doc) {
         final FragmentManager fm = getFragmentManager();
         if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
             // Nested directory picked, recurse using new fragment
-            DirectoryFragment.show(fm, doc.uri, doc.displayName);
+            DirectoryFragment.show(fm, doc.uri, doc.displayName, true);
         } else if (mAction == ACTION_OPEN) {
             // Explicit file picked, return
             onFinished(doc.uri);
@@ -272,9 +422,13 @@
         values.put(DocumentColumns.MIME_TYPE, mimeType);
         values.put(DocumentColumns.DISPLAY_NAME, displayName);
 
-        // TODO: handle errors from remote side
         final Uri uri = getContentResolver().insert(mCurrentDir, values);
-        onFinished(uri);
+        if (uri != null) {
+            onFinished(uri);
+        } else {
+            // TODO: ask for overwrite confirmation
+            Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
+        }
     }
 
     private void onFinished(Uri... uris) {
@@ -313,6 +467,72 @@
         public static final int SORT_BY_DATE = 1;
     }
 
+    public static class Root {
+        public DocumentsProviderInfo info;
+        public int rootType;
+        public Uri uri;
+        public Drawable icon;
+        public String title;
+        public String summary;
+
+        public static Root fromInfo(Context context, DocumentsProviderInfo info) {
+            final Root root = new Root();
+            final PackageManager pm = context.getPackageManager();
+
+            root.info = info;
+            root.rootType = DocumentsContract.ROOT_TYPE_SERVICE;
+            root.uri = DocumentsContract.buildDocumentUri(
+                    info.providerInfo.authority, DocumentsContract.ROOT_GUID);
+            root.icon = info.providerInfo.loadIcon(pm);
+            root.title = info.providerInfo.loadLabel(pm).toString();
+            root.summary = null;
+
+            return root;
+        }
+
+        public static Root fromCursor(
+                Context context, DocumentsProviderInfo info, Cursor cursor) {
+            final Root root = fromInfo(context, info);
+
+            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
+            root.uri = DocumentsContract.buildDocumentUri(info.providerInfo.authority,
+                    cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
+
+            final PackageManager pm = context.getPackageManager();
+            final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
+            if (icon != 0) {
+                try {
+                    root.icon = pm.getResourcesForApplication(info.providerInfo.applicationInfo)
+                            .getDrawable(icon);
+                } catch (NotFoundException e) {
+                    throw new RuntimeException(e);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            final String title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
+            if (title != null) {
+                root.title = title;
+            }
+
+            root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
+
+            return root;
+        }
+    }
+
+    public static class DocumentsProviderInfo {
+        public ProviderInfo providerInfo;
+        public boolean customRoots;
+        public List<Icon> customIcons;
+    }
+
+    public static class Icon {
+        public String mimeType;
+        public Drawable icon;
+    }
+
     public static class Document {
         public Uri uri;
         public String mimeType;
@@ -367,8 +587,17 @@
         }
     }
 
-    public static Drawable resolveDocumentIcon(Context context, String mimeType) {
-        // TODO: allow backends to provide custom MIME icons
+    public static Drawable resolveDocumentIcon(Context context, String authority, String mimeType) {
+        // Custom icons take precedence
+        final DocumentsProviderInfo info = sProviders.get(authority);
+        if (info != null) {
+            for (Icon icon : info.customIcons) {
+                if (mimeMatches(icon.mimeType, mimeType)) {
+                    return icon.icon;
+                }
+            }
+        }
+
         if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
             return context.getResources().getDrawable(R.drawable.ic_dir);
         } else {
@@ -376,17 +605,155 @@
             final Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setType(mimeType);
 
-            final ResolveInfo info = pm.resolveActivity(
+            final ResolveInfo activityInfo = pm.resolveActivity(
                     intent, PackageManager.MATCH_DEFAULT_ONLY);
-            if (info != null) {
-                return info.loadIcon(pm);
+            if (activityInfo != null) {
+                return activityInfo.loadIcon(pm);
             } else {
                 return null;
             }
         }
     }
 
-    private static final String TAG_CREATE_DIRECTORY = "create_directory";
+    private static final String TAG_DOCUMENTS_PROVIDER = "documents-provider";
+    private static final String TAG_ICON = "icon";
+
+    /**
+     * Gather roots from all known storage providers.
+     */
+    private void updateRoots() {
+        sProviders.clear();
+        sRoots.clear();
+
+        final PackageManager pm = getPackageManager();
+        final List<ProviderInfo> providers = pm.queryContentProviders(
+                null, -1, PackageManager.GET_META_DATA);
+        for (ProviderInfo providerInfo : providers) {
+            if (providerInfo.metaData != null && providerInfo.metaData.containsKey(
+                    DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
+                final DocumentsProviderInfo info = parseInfo(this, providerInfo);
+                if (info == null) {
+                    Log.w(TAG, "Missing info for " + providerInfo);
+                    continue;
+                }
+
+                sProviders.put(info.providerInfo.authority, info);
+
+                if (info.customRoots) {
+                    // TODO: populate roots on background thread, and cache results
+                    final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority);
+                    final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+                    try {
+                        while (cursor.moveToNext()) {
+                            sRoots.add(Root.fromCursor(this, info, cursor));
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                } else if (info != null) {
+                    sRoots.add(Root.fromInfo(this, info));
+                }
+            }
+        }
+    }
+
+    private static DocumentsProviderInfo parseInfo(Context context, ProviderInfo providerInfo) {
+        final DocumentsProviderInfo info = new DocumentsProviderInfo();
+        info.providerInfo = providerInfo;
+        info.customIcons = Lists.newArrayList();
+
+        final PackageManager pm = context.getPackageManager();
+        final Resources res;
+        try {
+            res = pm.getResourcesForApplication(providerInfo.applicationInfo);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Failed to find resources for " + providerInfo, e);
+            return null;
+        }
+
+        XmlResourceParser parser = null;
+        try {
+            parser = providerInfo.loadXmlMetaData(
+                    pm, DocumentsContract.META_DATA_DOCUMENT_PROVIDER);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type = 0;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                final String tag = parser.getName();
+                if (type == XmlPullParser.START_TAG && TAG_DOCUMENTS_PROVIDER.equals(tag)) {
+                    final TypedArray a = res.obtainAttributes(
+                            attrs, com.android.internal.R.styleable.DocumentsProviderInfo);
+                    info.customRoots = a.getBoolean(
+                            com.android.internal.R.styleable.DocumentsProviderInfo_customRoots,
+                            false);
+                    a.recycle();
+
+                } else if (type == XmlPullParser.START_TAG && TAG_ICON.equals(tag)) {
+                    final TypedArray a = res.obtainAttributes(
+                            attrs, com.android.internal.R.styleable.Icon);
+                    final Icon icon = new Icon();
+                    icon.mimeType = a.getString(com.android.internal.R.styleable.Icon_mimeType);
+                    icon.icon = a.getDrawable(com.android.internal.R.styleable.Icon_icon);
+                    info.customIcons.add(icon);
+                    a.recycle();
+                }
+            }
+        } catch (IOException e){
+            Log.w(TAG, "Failed to parse metadata", e);
+            return null;
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Failed to parse metadata", e);
+            return null;
+        } finally {
+            IoUtils.closeQuietly(parser);
+        }
+
+        return info;
+    }
+
+    private OnItemClickListener mRootsListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            // Clear entire backstack and start in new root
+            final FragmentManager fm = getFragmentManager();
+            while (fm.getBackStackEntryCount() > 0) {
+                fm.popBackStackImmediate();
+            }
+
+            mCurrentRoot = mRootsAdapter.getItem(position);
+            DirectoryFragment.show(
+                    getFragmentManager(), mCurrentRoot.uri, mCurrentRoot.title, false);
+
+            mDrawerLayout.closeDrawers();
+        }
+    };
+
+    public static class RootsAdapter extends ArrayAdapter<Root> {
+        public RootsAdapter(Context context, List<Root> list) {
+            super(context, android.R.layout.simple_list_item_1, list);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_root, parent, false);
+            }
+
+            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+            final Root root = getItem(position);
+            icon.setImageDrawable(root.icon);
+            title.setText(root.title);
+
+            summary.setText(root.summary);
+            summary.setVisibility(root.summary != null ? View.VISIBLE : View.GONE);
+
+            return convertView;
+        }
+    }
 
     public static class CreateDirectoryFragment extends DialogFragment {
         public static void show(FragmentManager fm) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
index a2a4f7c..cdc399d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -66,7 +66,7 @@
 
         final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
         icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
-                context, getArguments().getString(EXTRA_MIME_TYPE)));
+                context, null, getArguments().getString(EXTRA_MIME_TYPE)));
 
         mDisplayName = (EditText) view.findViewById(android.R.id.title);
         mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME));
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 37dc5b1..afdb6bb 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -13,7 +13,7 @@
             android:permission="android.permission.MANAGE_DOCUMENTS">
             <meta-data
                 android:name="android.content.DOCUMENT_PROVIDER"
-                android:value="true" />
+                android:resource="@xml/document_provider" />
         </provider>
     </application>
 </manifest>
diff --git a/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png b/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png
new file mode 100644
index 0000000..961a9bb
--- /dev/null
+++ b/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png
Binary files differ
diff --git a/packages/ExternalStorageProvider/res/xml/document_provider.xml b/packages/ExternalStorageProvider/res/xml/document_provider.xml
new file mode 100644
index 0000000..929a273
--- /dev/null
+++ b/packages/ExternalStorageProvider/res/xml/document_provider.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<documents-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:customRoots="true">
+
+    <icon android:mimeType="application/pdf" android:icon="@drawable/ic_pdf" />
+    <icon android:mimeType="text/*" android:icon="@drawable/ic_pdf" />
+</documents-provider>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity.xml b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
index 32bc15a..a4105ea 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
@@ -34,12 +34,13 @@
             android:layout_height="wrap_content"
             android:layout_gravity="fill_horizontal"
             android:layout_marginLeft="32dip"
+            android:layout_marginTop="32dip"
             android:layout_marginRight="32dip"
             android:layout_marginBottom="12dip"
             android:layout_row="0"
             android:layout_column="0"
             android:layout_columnSpan="2"
-            android:minHeight="?android:attr/listPreferredItemHeightSmall">
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Spinner>
 
         <!-- Copies -->
@@ -57,7 +58,8 @@
             android:layout_gravity="bottom"
             android:inputType="numberDecimal"
             android:selectAllOnFocus="true"
-            android:minWidth="150dip">
+            android:minWidth="150dip"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </view>
 
         <TextView
@@ -86,7 +88,8 @@
             android:layout_marginBottom="12dip"
             android:layout_row="2"
             android:layout_column="1"
-            android:minWidth="150dip">
+            android:minWidth="150dip"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Spinner>
 
         <TextView
@@ -114,7 +117,8 @@
             android:layout_marginBottom="12dip"
             android:layout_row="4"
             android:layout_column="0"
-            android:minWidth="150dip">
+            android:minWidth="150dip"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Spinner>
 
         <TextView
@@ -142,7 +146,8 @@
             android:layout_marginBottom="12dip"
             android:layout_row="4"
             android:layout_column="1"
-            android:minWidth="150dip">
+            android:minWidth="150dip"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Spinner>
 
         <TextView
@@ -169,7 +174,8 @@
             android:layout_marginRight="12dip"
             android:layout_row="6"
             android:layout_column="0"
-            android:minWidth="150dip">
+            android:minWidth="150dip"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Spinner>
 
         <view
@@ -186,10 +192,12 @@
             android:minWidth="150dip"
             android:hint="@string/pages_range_example"
             android:inputType="textNoSuggestions"
-            android:visibility="gone">
+            android:visibility="gone"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </view>
 
         <TextView
+            android:id="@+id/page_range_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="32dip"
@@ -231,7 +239,8 @@
             android:layout_columnSpan="2"
             android:text="@string/print_preview"
             android:gravity="left|center_vertical"
-            android:background="?android:attr/selectableItemBackground">
+            android:background="?android:attr/selectableItemBackground"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Button>
 
         <ImageView
@@ -269,7 +278,8 @@
             android:layout_columnSpan="2"
             android:padding="0dip"
             android:text="@string/print_button"
-            android:background="?android:attr/selectableItemBackground">
+            android:background="?android:attr/selectableItemBackground"
+            android:minHeight="?android:attr/listPreferredItemHeight">
         </Button>
 
     </GridLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 27540d7..1762693 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
     <string name="app_label">Print Spooler</string>
@@ -38,7 +38,7 @@
     <string name="label_orientation">ORIENTATION</string>
 
     <!-- Label of the page selection widget. [CHAR LIMIT=20] -->
-    <string name="label_pages">PAGES</string>
+    <string name="label_pages">PAGES (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
 
     <!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=15] -->
     <string name="pages_range_example">e.g. 1&#8211;5, 8</string>
@@ -52,6 +52,9 @@
     <!-- Title of the message that the printing application crashed. [CHAR LIMIT=50] -->
     <string name="printing_app_crashed">Printing app crashed</string>
 
+    <!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
+    <string name="page_count_unknown">unknown</string>
+
     <!-- Color mode labels. -->
     <string-array name="color_mode_labels">
         <!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 1e1cc24..d61fd2c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -24,9 +24,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -34,24 +32,27 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.UserHandle;
+import android.print.ILayoutResultCallback;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrinterDiscoveryObserver;
+import android.print.IWriteResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
 import android.print.PrintAttributes.MediaSize;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentAdapter;
 import android.print.PrintDocumentInfo;
 import android.print.PrintJobInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.text.Editable;
 import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Choreographer;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -64,10 +65,13 @@
 import android.widget.EditText;
 import android.widget.Spinner;
 import android.widget.TextView;
+import android.widget.Toast;
 
-import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -76,15 +80,31 @@
  */
 public class PrintJobConfigActivity extends Activity {
 
-    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "PrintJobConfigActivity";
 
-    private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
 
-    public static final String EXTRA_PRINTABLE = "printable";
-    public static final String EXTRA_APP_ID = "appId";
-    public static final String EXTRA_ATTRIBUTES = "attributes";
+    private static final boolean LIVE_PREVIEW_SUPPORTED = false;
+
+    public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter";
+    public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
     public static final String EXTRA_PRINT_JOB_ID = "printJobId";
 
+    private static final int CONTROLLER_STATE_INITIALIZED = 1;
+    private static final int CONTROLLER_STATE_STARTED = 2;
+    private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
+    private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
+    private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
+    private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
+    private static final int CONTROLLER_STATE_FINISHED = 7;
+    private static final int CONTROLLER_STATE_FAILED = 8;
+    private static final int CONTROLLER_STATE_CANCELLED = 9;
+
+    private static final int EDITOR_STATE_INITIALIZED = 1;
+    private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
+    private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3;
+    private static final int EDITOR_STATE_CANCELLED = 4;
+
     private static final int MIN_COPIES = 1;
 
     private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
@@ -95,31 +115,12 @@
     private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
             "([0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*[,]?[\\s]*)+");
 
-    private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
-
-    private Handler mHandler;
-
-    private Editor mEditor;
-
-    private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
-
-    private int mAppId;
-    private int mPrintJobId;
+    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
 
     private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().create();
     private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().create();
     private final PrintAttributes mTempPrintAttributes = new PrintAttributes.Builder().create();
 
-    private RemotePrintDocumentAdapter mRemotePrintAdapter;
-
-    private boolean mPrintConfirmed;
-
-    private boolean mStarted;
-
-    private IBinder mIPrintDocumentAdapter;
-
-    private PrintDocumentInfo mPrintDocumentInfo;
-
     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
         @Override
         public void binderDied() {
@@ -127,369 +128,492 @@
         }
     };
 
-    @Override
-    protected void onDestroy() {
-        mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
-        super.onDestroy();
-    }
+    private PrintSpooler mSpooler;
+    private Editor mEditor;
+    private Document mDocument;
+    private PrintController mController;
+    private PrinterDiscoveryObserver mPrinterDiscoveryObserver;
+
+    private int mPrintJobId;
+
+    private IBinder mIPrintDocumentAdapter;
 
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
         setContentView(R.layout.print_job_config_activity);
 
-        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
-                | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
 
-        mHandler = new MyHandler(Looper.getMainLooper());
+        Bundle extras = getIntent().getExtras();
+
+        mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
+        if (mPrintJobId < 0) {
+            throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
+        }
+
+        mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER);
+        if (mIPrintDocumentAdapter == null) {
+            throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
+        }
+
+        PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_PRINT_ATTRIBUTES);
+        if (attributes != null) {
+            mCurrPrintAttributes.copyFrom(attributes);
+        }
+
+        mSpooler = PrintSpooler.getInstance(this);
         mEditor = new Editor();
+        mDocument = new Document();
+        mController = new PrintController(new RemotePrintDocumentAdapter(
+                IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
+                mSpooler.generateFileForPrintJob(mPrintJobId)));
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
-        notifyPrintableStartIfNeeded();
+        try {
+            mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException re) {
+            finish();
+            return;
+        }
+        mController.initialize();
+        mEditor.initialize();
+        mPrinterDiscoveryObserver = new PrinterDiscoveryObserver(mEditor, getMainLooper());
+        mSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
     }
 
     @Override
     protected void onPause() {
-        super.onPause();
-        mPrintSpooler.stopPrinterDiscovery();
-        notifyPrintableFinishIfNeeded();
-    }
-
-    private void notifyPrintableStartIfNeeded() {
-        if (mEditor.getCurrentPrinter() == null
-                || mStarted) {
-            return;
-        }
-        mStarted = true;
-        mRemotePrintAdapter.start();
-    }
-
-    private void updatePrintableContentIfNeeded() {
-        if (!mStarted) {
-            return;
-        }
-
-        mPrintSpooler.setPrintJobAttributes(mPrintJobId, mCurrPrintAttributes);
-
-        mRemotePrintAdapter.cancel();
-        mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FINISHED);
-        mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FAILED);
-
-        // TODO: Implement setting the print preview attribute
-        mRemotePrintAdapter.layout(mOldPrintAttributes,
-                mCurrPrintAttributes, new LayoutResultCallback() {
-            @Override
-            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
-                        0, info).sendToTarget();
-            }
-
-            @Override
-            public void onLayoutFailed(CharSequence error) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
-            }
-        }, new Bundle());
-    }
-
-    private void handleOnLayoutFinished(PrintDocumentInfo info, boolean changed) {
-        mPrintDocumentInfo = info;
-
-        mEditor.updateUiIfNeeded();
-
-        // TODO: Handle the case of unchanged content
-        mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info);
-
-        // TODO: Implement page selector.
-        final List<PageRange> pages = new ArrayList<PageRange>();
-        pages.add(PageRange.ALL_PAGES);
-
-        mRemotePrintAdapter.write(pages, new WriteResultCallback() {
-            @Override
-            public void onWriteFinished(List<PageRange> pages) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
-            }
-
-            @Override
-            public void onWriteFailed(CharSequence error) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
-            }
-        });
-    }
-
-    private void handleOnLayoutFailed(CharSequence error) {
-        Log.e(LOG_TAG, "Error during layout: " + error);
-        finishActivity(Activity.RESULT_CANCELED);
-    }
-
-    private void handleOnWriteFinished(List<PageRange> pages) {
-        // TODO: Now we have to allow the preview button
-        mEditor.updatePrintPreview(mRemotePrintAdapter.getFile());
-    }
-
-    private void handleOnWriteFailed(CharSequence error) {
-        Log.e(LOG_TAG, "Error write layout: " + error);
-        finishActivity(Activity.RESULT_CANCELED);
-    }
-
-    private void notifyPrintableFinishIfNeeded() {
-        if (!mStarted) {
-            return;
-        }
-
-        if (!mPrintConfirmed) {
-            mRemotePrintAdapter.cancel();
-        }
-        mRemotePrintAdapter.finish();
-
-        PrinterInfo printer = mEditor.getCurrentPrinter();
-        // If canceled or no printer, nothing to do.
-        if (!mPrintConfirmed || printer == null) {
-            // Update the print job's status.
-            mPrintSpooler.setPrintJobState(mPrintJobId,
+        mSpooler.stopPrinterDiscovery();
+        mPrinterDiscoveryObserver.destroy();
+        mPrinterDiscoveryObserver = null;
+        if (mController.isCancelled() || mController.isFailed()) {
+            mSpooler.setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_CANCELED);
-            return;
-        }
-
-        // Update the print job's printer.
-        mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printer.getId());
-
-        // Update the print job's status.
-        mPrintSpooler.setPrintJobState(mPrintJobId,
-                PrintJobInfo.STATE_QUEUED);
-
-        if (DEBUG) {
-            if (mPrintConfirmed) {
-                File file = mRemotePrintAdapter.getFile();
-                if (file.exists()) {
-                    new ViewSpooledFileAsyncTask(file).executeOnExecutor(
-                          AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        } else if (mController.hasStarted()) {
+            mController.finish();
+            if (mEditor.isPrintConfirmed()) {
+                if (mController.isFinished()) {
+                    mSpooler.setPrintJobState(mPrintJobId,
+                            PrintJobInfo.STATE_QUEUED);
+                } else {
+                    mSpooler.setPrintJobState(mPrintJobId,
+                            PrintJobInfo.STATE_CANCELED);
                 }
             }
         }
+        mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
+        super.onPause();
     }
 
-    private boolean hasPdfViewer() {
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setType("application/pdf");
-        return !getPackageManager().queryIntentActivities(intent,
-                PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
-    }
-
-    // Caution: Use this only for debugging
-    private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
-
-        private final File mFile;
-
-        public ViewSpooledFileAsyncTask(File file) {
-            mFile = file;
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed()
+                && getWindow().shouldCloseOnTouch(this, event)) {
+            if (!mController.isWorking()) {
+                PrintJobConfigActivity.this.finish();
+            }
+            mEditor.cancel();
+            return true;
         }
-
-        @Override
-        protected Void doInBackground(Void... params) {
-            mFile.setExecutable(true, false);
-            mFile.setWritable(true, false);
-            mFile.setReadable(true, false);
-
-            final long identity = Binder.clearCallingIdentity();
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivityAsUser(intent, null, UserHandle.CURRENT);
-            Binder.restoreCallingIdentity(identity);
-            return null;
-        }
+        return super.onTouchEvent(event);
     }
 
-    private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
-        private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
-        private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            event.startTracking();
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+                && !event.isCanceled()) {
+            if (!mController.isWorking()) {
+                PrintJobConfigActivity.this.finish();
+            }
+            mEditor.cancel();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private boolean printAttributesChanged() {
+        return !mOldPrintAttributes.equals(mCurrPrintAttributes);
+    }
+
+    private class PrintController {
+        private final AtomicInteger mRequestCounter = new AtomicInteger();
+
+        private final RemotePrintDocumentAdapter mRemotePrintAdapter;
 
         private final Handler mHandler;
 
-        @SuppressWarnings("unchecked")
-        public PrintDiscoveryObserver(Looper looper) {
-            mHandler = new Handler(looper, null, true) {
-                @Override
-                public void handleMessage(Message message) {
-                    switch (message.what) {
-                        case MESSAGE_ADD_DICOVERED_PRINTERS: {
-                            List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
-                            mEditor.addPrinters(printers);
-                        } break;
-                        case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
-                            List<PrinterId> printerIds = (List<PrinterId>) message.obj;
-                            mEditor.removePrinters(printerIds);
-                        } break;
-                    }
+        private int mControllerState = CONTROLLER_STATE_INITIALIZED;
+
+        private PageRange[] mRequestedPages;
+
+        private Bundle mMetadata = new Bundle();
+
+        private final ILayoutResultCallback mILayoutResultCallback =
+                new ILayoutResultCallback.Stub() {
+            @Override
+            public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
+                if (mRequestCounter.get() == sequence) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
+                            0, info).sendToTarget();
                 }
-            };
-        }
-
-        @Override
-        public void addDiscoveredPrinters(List<PrinterInfo> printers) {
-            mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
-        }
-
-        @Override
-        public void removeDiscoveredPrinters(List<PrinterId> printers) {
-            mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
-        }
-    }
-
-    private final class SpinnerItem<T> {
-        final T value;
-        CharSequence label;
-
-        public SpinnerItem(T value, CharSequence label) {
-            this.value = value;
-            this.label = label;
-        }
-
-        public String toString() {
-            return label.toString();
-        }
-    }
-
-    /**
-     * An instance of this class class is intended to be the first focusable
-     * in a layout to which the system automatically gives focus. It performs
-     * some voodoo to avoid the first tap on it to start an edit mode, rather
-     * to bring up the IME, i.e. to get the behavior as if the view was not
-     * focused.
-     */
-    public static final class CustomEditText extends EditText {
-        private boolean mClickedBeforeFocus;
-
-        public CustomEditText(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        @Override
-        public boolean performClick() {
-            super.performClick();
-            if (isFocused() && !mClickedBeforeFocus) {
-                clearFocus();
-                requestFocus();
             }
-            mClickedBeforeFocus = true;
-            return true;
-        }
 
-        @Override
-        public void setError(CharSequence error, Drawable icon) {
-            setCompoundDrawables(null, null, icon, null);
-        }
-
-        protected void onFocusChanged(boolean gainFocus, int direction,
-                Rect previouslyFocusedRect) {
-            if (!gainFocus) {
-                mClickedBeforeFocus = false;
+            @Override
+            public void onLayoutFailed(CharSequence error, int sequence) {
+                if (mRequestCounter.get() == sequence) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
+                }
             }
-            super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        }
-    }
+        };
 
-    private final class MyHandler extends Handler {
-        public static final int MSG_ON_LAYOUT_FINISHED = 1;
-        public static final int MSG_ON_LAYOUT_FAILED = 2;
-        public static final int MSG_ON_WRITE_FINISHED = 3;
-        public static final int MSG_ON_WRITE_FAILED = 4;
+        private IWriteResultCallback mIWriteResultCallback = new IWriteResultCallback.Stub() {
+            @Override
+            public void onWriteFinished(PageRange[] pages, int sequence) {
+                if (mRequestCounter.get() == sequence) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
+                }
+            }
 
-        public MyHandler(Looper looper) {
-            super(looper, null, false);
+            @Override
+            public void onWriteFailed(CharSequence error, int sequence) {
+                if (mRequestCounter.get() == sequence) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
+                }
+            }
+        };
+
+        public PrintController(RemotePrintDocumentAdapter adapter) {
+            mRemotePrintAdapter = adapter;
+            mHandler = new MyHandler(Looper.getMainLooper());
         }
 
-        @Override
-        @SuppressWarnings("unchecked")
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_ON_LAYOUT_FINISHED: {
-                    PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
-                    final boolean changed = (message.arg1 == 1);
-                    handleOnLayoutFinished(info, changed);
-                } break;
+        public void initialize() {
+            mControllerState = CONTROLLER_STATE_INITIALIZED;
+        }
 
-                case MSG_ON_LAYOUT_FAILED: {
-                    CharSequence error = (CharSequence) message.obj;
-                    handleOnLayoutFailed(error);
-                } break;
+        public void cancel() {
+            mControllerState = CONTROLLER_STATE_CANCELLED;
+        }
 
-                case MSG_ON_WRITE_FINISHED: {
-                    List<PageRange> pages = (List<PageRange>) message.obj;
-                    handleOnWriteFinished(pages);
-                } break;
+        public boolean isCancelled() {
+            return (mControllerState == CONTROLLER_STATE_CANCELLED);
+        }
 
-                case MSG_ON_WRITE_FAILED: {
-                    CharSequence error = (CharSequence) message.obj;
-                    handleOnWriteFailed(error);
-                } break;
+        public boolean isFinished() {
+            return (mControllerState == CONTROLLER_STATE_FINISHED);
+        }
+
+        public boolean isFailed() {
+            return (mControllerState == CONTROLLER_STATE_FAILED);
+        }
+
+        public boolean hasStarted() {
+            return mControllerState >= CONTROLLER_STATE_STARTED;
+        }
+
+        public boolean hasPerformedLayout() {
+            return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
+        }
+
+        public boolean isWorking() {
+            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
+                    || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
+        }
+
+        public void start() {
+            mControllerState = CONTROLLER_STATE_STARTED;
+            mRemotePrintAdapter.start();
+        }
+
+        public void update() {
+            if (!printAttributesChanged()) {
+                // If the attributes changes, then we do not do a layout but may
+                // have to ask the app to write some pages. Hence, pretend layout
+                // completed and nothing changed, so we handle writing as usual.
+                handleOnLayoutFinished(mDocument.info, false);
+            } else {
+                mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes);
+
+                mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
+                        !mEditor.isPrintConfirmed());
+
+                mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
+
+                mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
+                        mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
+
+                mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
+            }
+        }
+
+        public void finish() {
+            mControllerState = CONTROLLER_STATE_FINISHED;
+            mRemotePrintAdapter.finish();
+        }
+
+        private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) {
+            if (isCancelled()) {
+                if (mEditor.isDone()) {
+                    PrintJobConfigActivity.this.finish();
+                }
+                return;
+            }
+
+            mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
+
+            // If the info changed, we update the document and the print job,
+            // and update the UI since the the page range selection may have
+            // become invalid.
+            final boolean infoChanged = !info.equals(mDocument.info);
+            if (infoChanged) {
+                mDocument.info = info;
+                mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info);
+                mEditor.updateUi();
+            }
+
+            // If the document info or the layout changed, then
+            // drop the pages since we have to fetch them again.
+            if (infoChanged || layoutChanged) {
+                mDocument.pages = null;
+            }
+
+            // No pages means that the user selected an invalid range while we
+            // were doing a layout or the layout returned a document info for
+            // which the selected range is invalid. In such a case we do not
+            // write anything and wait for the user to fix the range which will
+            // trigger an update.
+            mRequestedPages = mEditor.getRequestedPages();
+            if (mRequestedPages == null) {
+                if (mEditor.isDone()) {
+                    PrintJobConfigActivity.this.finish();
+                }
+                return;
+            }
+
+            // If the info and the layout did not change and we already have
+            // the requested pages, then nothing else to do.
+            if (!infoChanged && !layoutChanged
+                    && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+                if (mEditor.isDone()) {
+                    PrintJobConfigActivity.this.finish();
+                }
+                return;
+            }
+
+            // If we do not support live preview and the current layout is
+            // not for preview purposes, i.e. the user did not poke the
+            // preview button, then just skip the write.
+            if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed()
+                    && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) {
+                if (mEditor.isDone()) {
+                    PrintJobConfigActivity.this.finish();
+                }
+                return;
+            }
+
+            // Request a write of the pages of interest.
+            mControllerState = CONTROLLER_STATE_WRITE_STARTED;
+            mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback,
+                    mRequestCounter.incrementAndGet());
+        }
+
+        private void handleOnLayoutFailed(CharSequence error) {
+            mControllerState = CONTROLLER_STATE_FAILED;
+            // TODO: We need some UI for announcing an error.
+            Log.e(LOG_TAG, "Error during layout: " + error);
+            PrintJobConfigActivity.this.finish();
+        }
+
+        private void handleOnWriteFinished(PageRange[] pages) {
+            if (isCancelled()) {
+                if (mEditor.isDone()) {
+                    PrintJobConfigActivity.this.finish();
+                }
+                return;
+            }
+
+            mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
+
+            // Update which pages we have fetched.
+            mDocument.pages = PageRangeUtils.normalize(pages);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
+                        + " and got: " + Arrays.toString(mDocument.pages));
+            }
+
+            // Adjust the print job pages based on what was requested and written.
+            // The cases are ordered in the most expected to the least expected.
+            if (Arrays.equals(mDocument.pages, mRequestedPages)) {
+                // We got a document with exactly the pages we wanted. Hence,
+                // the printer has to print all pages in the data.
+                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY);
+            } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
+                // We requested specific pages but got all of them. Hence,
+                // the printer has to print only the requested pages.
+                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages);
+            } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+                // We requested specific pages and got more but not all pages.
+                // Hence, we have to offset appropriately the printed pages to
+                // excle the pages we did not request. Note that pages is
+                // guaranteed to be not null and not empty.
+                final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
+                PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
+                PageRangeUtils.offsetStart(offsetPages, offset);
+                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages);
+            } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
+                    && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
+                    && mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) {
+                // We requested all pages via the special constant and got all
+                // of them as an explicit enumeration. Hence, the printer has
+                // to print only the requested pages.
+                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages);
+            } else {
+                // We did not get the pages we requested, then the application
+                // misbehaves, so we fail quickly.
+                // TODO: We need some UI for announcing an error.
+                mControllerState = CONTROLLER_STATE_FAILED;
+                Log.e(LOG_TAG, "Received invalid pages from the app");
+                PrintJobConfigActivity.this.finish();
+            }
+
+            if (mEditor.isDone()) {
+                PrintJobConfigActivity.this.finish();
+            }
+        }
+
+        private void handleOnWriteFailed(CharSequence error) {
+            mControllerState = CONTROLLER_STATE_FAILED;
+            Log.e(LOG_TAG, "Error during write: " + error);
+            PrintJobConfigActivity.this.finish();
+        }
+
+        private final class MyHandler extends Handler {
+            public static final int MSG_ON_LAYOUT_FINISHED = 1;
+            public static final int MSG_ON_LAYOUT_FAILED = 2;
+            public static final int MSG_ON_WRITE_FINISHED = 3;
+            public static final int MSG_ON_WRITE_FAILED = 4;
+
+            public MyHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_ON_LAYOUT_FINISHED: {
+                        PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
+                        final boolean changed = (message.arg1 == 1);
+                        mController.handleOnLayoutFinished(info, changed);
+                    } break;
+
+                    case MSG_ON_LAYOUT_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        mController.handleOnLayoutFailed(error);
+                    } break;
+
+                    case MSG_ON_WRITE_FINISHED: {
+                        PageRange[] pages = (PageRange[]) message.obj;
+                        mController.handleOnWriteFinished(pages);
+                    } break;
+
+                    case MSG_ON_WRITE_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        mController.handleOnWriteFailed(error);
+                    } break;
+                }
             }
         }
     }
 
-    private class Editor {
-        private EditText mCopiesEditText;
+    private final class Editor {
+        private final EditText mCopiesEditText;
 
-        private EditText mRangeEditText;
+        private final TextView mRangeTitle;
+        private final EditText mRangeEditText;
 
-        private Spinner mDestinationSpinner;
-        public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+        private final Spinner mDestinationSpinner;
+        private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
 
-        private Spinner mMediaSizeSpinner;
-        public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+        private final Spinner mMediaSizeSpinner;
+        private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
 
-        private Spinner mColorModeSpinner;
-        public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+        private final Spinner mColorModeSpinner;
+        private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
 
-        private Spinner mOrientationSpinner;
-        public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+        private final Spinner mOrientationSpinner;
+        private final  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
 
-        private Spinner mRangeOptionsSpinner;
-        public ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
+        private final Spinner mRangeOptionsSpinner;
+        private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
 
-        private Button mPrintPreviewButton;
+        private final SimpleStringSplitter mStringCommaSplitter =
+                new SimpleStringSplitter(',');
 
-        private Button mPrintButton;
+        private final Button mPrintPreviewButton;
+
+        private final Button mPrintButton;
 
         private final OnItemSelectedListener mOnItemSelectedListener =
                 new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
                 if (spinner == mDestinationSpinner) {
-                    mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
                     mCurrPrintAttributes.clear();
-                    final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
-                    if (selectedIndex >= 0) {
-                        mDestinationSpinnerAdapter.getItem(selectedIndex).value.getDefaults(
-                                mCurrPrintAttributes);
+                    SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
+                    if (dstItem != null) {
+                        PrinterInfo printer = dstItem.value;
+                        mSpooler.setPrintJobPrinterIdNoPersistence(mPrintJobId, printer.getId());
+                        printer.getDefaults(mCurrPrintAttributes);
+                        if (!printer.hasAllRequiredAttributes()) {
+                            List<PrinterId> printerIds = new ArrayList<PrinterId>();
+                            printerIds.add(printer.getId());
+                            mSpooler.onReqeustUpdatePrinters(printerIds);
+                            //TODO: We need a timeout for the update.
+                        } else {
+                            if (!mController.hasStarted()) {
+                                mController.start();
+                            }
+                            if (!hasErrors()) {
+                                mController.update();
+                            }
+                        }
                     }
-                    updateUiIfNeeded();
-                    notifyPrintableStartIfNeeded();
-                    updatePrintableContentIfNeeded();
+                    updateUi();
                 } else if (spinner == mMediaSizeSpinner) {
                     SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
-                    mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
                     mCurrPrintAttributes.setMediaSize(mediaItem.value);
-                    updatePrintableContentIfNeeded();
+                    if (!hasErrors()) {
+                        mController.update();
+                    }
                 } else if (spinner == mColorModeSpinner) {
                     SpinnerItem<Integer> colorModeItem =
                             mColorModeSpinnerAdapter.getItem(position);
-                    mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
                     mCurrPrintAttributes.setColorMode(colorModeItem.value);
-                    updatePrintableContentIfNeeded();
+                    if (!hasErrors()) {
+                        mController.update();
+                    }
                 } else if (spinner == mOrientationSpinner) {
                     SpinnerItem<Integer> orientationItem =
                             mOrientationSpinnerAdapter.getItem(position);
-                    mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
                     mCurrPrintAttributes.setOrientation(orientationItem.value);
-                    updatePrintableContentIfNeeded();
+                    if (!hasErrors()) {
+                        mController.update();
+                    }
                 } else if (spinner == mRangeOptionsSpinner) {
-                    updateUiIfNeeded();
-                    updatePrintableContentIfNeeded();
+                    updateUi();
+                    if (!hasErrors()) {
+                        mController.update();
+                    }
                 }
             }
 
@@ -512,19 +636,28 @@
 
             @Override
             public void afterTextChanged(Editable editable) {
+                final boolean hadErrors = hasErrors();
+
                 if (editable.length() == 0) {
                     mCopiesEditText.setError("");
-                    mPrintButton.setEnabled(false);
+                    updateUi();
                     return;
                 }
+
                 final int copies = Integer.parseInt(editable.toString());
                 if (copies < MIN_COPIES) {
                     mCopiesEditText.setError("");
-                    mPrintButton.setEnabled(false);
+                    updateUi();
                     return;
                 }
-                mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
-                mCurrPrintAttributes.setCopies(copies);
+
+                mCopiesEditText.setError(null);
+                mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+                updateUi();
+
+                if (hadErrors && !hasErrors() && printAttributesChanged()) {
+                    mController.update();
+                }
             }
         };
 
@@ -541,18 +674,20 @@
 
             @Override
             public void afterTextChanged(Editable editable) {
+                final boolean hadErrors = hasErrors();
+
                 String text = editable.toString();
 
                 if (TextUtils.isEmpty(text)) {
                     mRangeEditText.setError("");
-                    mPrintButton.setEnabled(false);
+                    updateUi();
                     return;
                 }
 
                 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
                 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
                     mRangeEditText.setError("");
-                    mPrintButton.setEnabled(false);
+                    updateUi();
                     return;
                 }
 
@@ -560,100 +695,36 @@
                 while (matcher.find()) {
                     String numericString = text.substring(matcher.start(), matcher.end());
                     final int pageIndex = Integer.parseInt(numericString);
-                    if (pageIndex < 1 || pageIndex > mPrintDocumentInfo.getPageCount()) {
+                    if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
                         mRangeEditText.setError("");
-                        mPrintButton.setEnabled(false);
+                        updateUi();
                         return;
                     }
                 }
 
+                //TODO: Catch the error if start is less grater than the end.
+
                 mRangeEditText.setError(null);
                 mPrintButton.setEnabled(true);
+                updateUi();
+
+                if (hadErrors && !hasErrors() && printAttributesChanged()) {
+                    updateUi();
+                }
             }
         };
 
+        private int mEditorState;
+
         public Editor() {
-            Bundle extras = getIntent().getExtras();
-
-            mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
-            if (mPrintJobId < 0) {
-                throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
-            }
-
-            mAppId = extras.getInt(EXTRA_APP_ID, -1);
-            if (mAppId < 0) {
-                throw new IllegalArgumentException("Invalid app id: " + mAppId);
-            }
-
-            PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
-            if (attributes == null) {
-                mCurrPrintAttributes.copyFrom(attributes);
-            }
-
-            mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE);
-            if (mIPrintDocumentAdapter == null) {
-                throw new IllegalArgumentException("Printable cannot be null");
-            }
-            mRemotePrintAdapter = new RemotePrintDocumentAdapter(
-                    IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                    mPrintSpooler.generateFileForPrintJob(mPrintJobId));
-
-            try {
-                mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
-            } catch (RemoteException re) {
-                finish();
-            }
-
-            mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
-
-            bindUi();
-        }
-
-        private void bindUi() {
             // Copies
             mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
-            mCopiesEditText.setText(String.valueOf(MIN_COPIES));
             mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
-            mCopiesEditText.setText(String.valueOf(
-                    Math.max(mCurrPrintAttributes.getCopies(), MIN_COPIES)));
             mCopiesEditText.selectAll();
 
             // Destination.
             mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
-            mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(
-                    PrintJobConfigActivity.this, R.layout.spinner_dropdown_item) {
-                        @Override
-                        public View getDropDownView(int position, View convertView,
-                                ViewGroup parent) {
-                            return getView(position, convertView, parent);
-                        }
-
-                        @Override
-                        public View getView(int position, View convertView, ViewGroup parent) {
-                            if (convertView == null) {
-                                convertView = getLayoutInflater().inflate(
-                                        R.layout.spinner_dropdown_item, parent, false);
-                            }
-
-                            PrinterInfo printerInfo = getItem(position).value;
-                            TextView title = (TextView) convertView.findViewById(R.id.title);
-                            title.setText(printerInfo.getLabel());
-
-                            try {
-                                TextView subtitle = (TextView)
-                                        convertView.findViewById(R.id.subtitle);
-                                PackageManager pm = getPackageManager();
-                                PackageInfo packageInfo = pm.getPackageInfo(
-                                        printerInfo.getId().getService().getPackageName(), 0);
-                                subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
-                                subtitle.setVisibility(View.VISIBLE);
-                            } catch (NameNotFoundException nnfe) {
-                                /* ignore */
-                            }
-
-                            return convertView;
-                        }
-            };
+            mDestinationSpinnerAdapter = new DestinationAdapter();
             mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
             mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
@@ -682,6 +753,7 @@
             mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
             // Range
+            mRangeTitle = (TextView) findViewById(R.id.page_range_title);
             mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
             mRangeEditText.addTextChangedListener(mRangeTextWatcher);
 
@@ -690,8 +762,6 @@
             mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
                     PrintJobConfigActivity.this,
                     R.layout.spinner_dropdown_item, R.id.title);
-            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
-            mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
             final int[] rangeOptionsValues = getResources().getIntArray(
                     R.array.page_options_values);
             String[] rangeOptionsLabels = getResources().getStringArray(
@@ -701,13 +771,29 @@
                 mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
                         rangeOptionsValues[i], rangeOptionsLabels[i]));
             }
+            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
             mRangeOptionsSpinner.setSelection(0);
+            // Here is some voodoo to circumvent the weird behavior of AdapterView
+            // in which a selection listener may get a callback for an event that
+            // happened before the listener was registered. The reason for that is
+            // that the selection change is handled on the next layout pass.
+            Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
+                    new Runnable() {
+                @Override
+                public void run() {
+                    mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+                }
+            }, null, Choreographer.getFrameDelay() * 2);
 
             mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button);
             mPrintPreviewButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    // TODO: Implement
+                    mEditor.confirmPreview();
+                    // TODO: Implement me
+                    Toast.makeText(PrintJobConfigActivity.this,
+                            "Stop poking me! Not implemented yet :)",
+                            Toast.LENGTH_LONG).show();
                 }
             });
 
@@ -715,21 +801,108 @@
             mPrintButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    mPrintConfirmed = true;
-                    finish();
+                    mEditor.confirmPrint();
+                    updateUi();
+                    mController.update();
                 }
             });
         }
 
-        private void updateUiIfNeeded() {
+        public void initialize() {
+            mEditorState = EDITOR_STATE_INITIALIZED;
+            mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+        }
+
+        public boolean isCancelled() {
+            return mEditorState == EDITOR_STATE_CANCELLED;
+        }
+
+        public void cancel() {
+            mEditorState = EDITOR_STATE_CANCELLED;
+            mController.cancel();
+            updateUi();
+        }
+
+        public boolean isDone() {
+            return isPrintConfirmed() || isPreviewConfirmed() || isCancelled();
+        }
+
+        public boolean isPrintConfirmed() {
+            return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+        }
+
+        public void confirmPrint() {
+            mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
+        }
+
+        public boolean isPreviewConfirmed() {
+            return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+        }
+
+        public void confirmPreview() {
+            mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW;
+        }
+
+        public PageRange[] getRequestedPages() {
+            if (hasErrors()) {
+                return null;
+            }
+            if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+                List<PageRange> pageRanges = new ArrayList<PageRange>();
+                mStringCommaSplitter.setString(mRangeEditText.getText().toString());
+
+                while (mStringCommaSplitter.hasNext()) {
+                    String range = mStringCommaSplitter.next().trim();
+                    final int dashIndex = range.indexOf('-');
+                    final int fromIndex;
+                    final int toIndex;
+
+                    if (dashIndex > 0) {
+                        fromIndex = Integer.parseInt(range.substring(0, dashIndex)) - 1;
+                        toIndex = Integer.parseInt(range.substring(
+                                dashIndex + 1, range.length())) - 1;
+                    } else {
+                        fromIndex = toIndex = Integer.parseInt(range);
+                    }
+
+                    PageRange pageRange = new PageRange(fromIndex, toIndex);
+                    pageRanges.add(pageRange);
+                }
+
+                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+                pageRanges.toArray(pageRangesArray);
+
+                return PageRangeUtils.normalize(pageRangesArray);
+            }
+
+            return ALL_PAGES_ARRAY;
+        }
+
+        public void updateUi() {
+            if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
+                mDestinationSpinner.setEnabled(false);
+                mCopiesEditText.setEnabled(false);
+                mMediaSizeSpinner.setEnabled(false);
+                mColorModeSpinner.setEnabled(false);
+                mOrientationSpinner.setEnabled(false);
+                mRangeOptionsSpinner.setEnabled(false);
+                mRangeEditText.setEnabled(false);
+                mPrintPreviewButton.setEnabled(false);
+                mPrintButton.setEnabled(false);
+                return;
+            }
+
             final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
 
-            if (selectedIndex < 0) {
+            if (selectedIndex < 0 || !mDestinationSpinnerAdapter.getItem(
+                    selectedIndex).value.hasAllRequiredAttributes()) {
+
                 // Destination
                 mDestinationSpinner.setEnabled(false);
 
-                // Copies
-                mCopiesEditText.setText("1");
+                mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher);
+                mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+                mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
                 mCopiesEditText.setEnabled(false);
 
                 // Media size
@@ -751,7 +924,11 @@
                 mRangeOptionsSpinner.setOnItemSelectedListener(null);
                 mRangeOptionsSpinner.setSelection(0);
                 mRangeOptionsSpinner.setEnabled(false);
+                mRangeTitle.setText(getString(R.string.label_pages,
+                        getString(R.string.page_count_unknown)));
+                mRangeEditText.removeTextChangedListener(mRangeTextWatcher);
                 mRangeEditText.setText("");
+                mRangeEditText.addTextChangedListener(mRangeTextWatcher);
                 mRangeEditText.setEnabled(false);
                 mRangeEditText.setVisibility(View.INVISIBLE);
 
@@ -884,46 +1061,65 @@
                 }
 
                 // Range options
-                if (mPrintDocumentInfo != null && (mPrintDocumentInfo.getPageCount() > 1
-                        || mPrintDocumentInfo.getPageCount()
-                            == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
+                PrintDocumentInfo info = mDocument.info;
+                if (info != null && (info.getPageCount() > 1
+                        || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
                     mRangeOptionsSpinner.setEnabled(true);
                     if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
                             && !mRangeEditText.isEnabled()) {
                         mRangeEditText.setEnabled(true);
-                        mRangeEditText.setError("");
                         mRangeEditText.setVisibility(View.VISIBLE);
                         mRangeEditText.requestFocus();
                         InputMethodManager imm = (InputMethodManager)
                                 getSystemService(INPUT_METHOD_SERVICE);
                         imm.showSoftInput(mRangeEditText, 0);
                     }
+                    final int pageCount = mDocument.info.getPageCount();
+                    mRangeTitle.setText(getString(R.string.label_pages,
+                            (pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+                                    ? getString(R.string.page_count_unknown)
+                                    : String.valueOf(pageCount)));
                 } else {
                     mRangeOptionsSpinner.setOnItemSelectedListener(null);
                     mRangeOptionsSpinner.setSelection(0);
                     mRangeOptionsSpinner.setEnabled(false);
+                    mRangeTitle.setText(getString(R.string.label_pages,
+                            getString(R.string.page_count_unknown)));
                     mRangeEditText.setEnabled(false);
-                    mRangeEditText.setText("");
                     mRangeEditText.setVisibility(View.INVISIBLE);
                 }
 
-                // Print preview
-                mPrintPreviewButton.setEnabled(true);
-                if (hasPdfViewer()) {
-                    mPrintPreviewButton.setText(getString(R.string.print_preview));
+                // Print/Print preview
+                if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
+                            && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors()))
+                        || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+                            && (!mController.hasPerformedLayout() || hasErrors()))) {
+                    mPrintPreviewButton.setEnabled(false);
+                    mPrintButton.setEnabled(false);
                 } else {
-                    mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+                    mPrintPreviewButton.setEnabled(true);
+                    if (hasPdfViewer()) {
+                        mPrintPreviewButton.setText(getString(R.string.print_preview));
+                    } else {
+                        mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+                    }
+                    mPrintButton.setEnabled(true);
                 }
 
-                // Print
-                mPrintButton.setEnabled(true);
+                // Copies
+                if (mCopiesEditText.getError() == null
+                        && TextUtils.isEmpty(mCopiesEditText.getText())) {
+                    mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+                    mCopiesEditText.selectAll();
+                    mCopiesEditText.requestFocus();
+                }
             }
 
             // Here is some voodoo to circumvent the weird behavior of AdapterView
             // in which a selection listener may get a callback for an event that
             // happened before the listener was registered. The reason for that is
             // that the selection change is handled on the next layout pass.
-            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_TRAVERSAL,
+            Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
                     new Runnable() {
                 @Override
                 public void run() {
@@ -932,15 +1128,7 @@
                     mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
                     mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
                 }
-            }, null);
-        }
-
-        public PrinterInfo getCurrentPrinter() {
-            final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
-            if (selectedIndex >= 0) {
-                return mDestinationSpinnerAdapter.getItem(selectedIndex).value;
-            }
-            return null;
+            }, null, Choreographer.getFrameDelay() * 2);
         }
 
         public void addPrinters(List<PrinterInfo> addedPrinters) {
@@ -995,8 +1183,317 @@
             }
         }
 
-        private void updatePrintPreview(File file) {
-            // TODO: Implement
+        @SuppressWarnings("unchecked")
+        public void updatePrinters(List<PrinterInfo> pritners) {
+            SpinnerItem<PrinterInfo> selectedItem =
+                    (SpinnerItem<PrinterInfo>) mDestinationSpinner.getSelectedItem();
+            PrinterId selectedPrinterId = (selectedItem != null)
+                    ? selectedItem.value.getId() : null;
+
+            boolean updated = false;
+
+            final int printerCount = pritners.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo updatedPrinter = pritners.get(i);
+                final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
+                for (int j = 0; j < existingPrinterCount; j++) {
+                    PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
+                    if (updatedPrinter.getId().equals(existingPrinter.getId())) {
+                        existingPrinter.copyFrom(updatedPrinter);
+                        updated = true;
+                        if (selectedPrinterId != null
+                                && selectedPrinterId.equals(updatedPrinter.getId())) {
+                            // The selected printer was updated. We simulate a fake
+                            // selection to reuse the normal printer change handling.
+                            mOnItemSelectedListener.onItemSelected(mDestinationSpinner,
+                                    mDestinationSpinner.getSelectedView(),
+                                    mDestinationSpinner.getSelectedItemPosition(),
+                                    mDestinationSpinner.getSelectedItemId());
+                            // TODO: This will reset the UI to the defaults for the
+                            // printer. We may need to revisit this.
+                            
+                        }
+                        break;
+                    }
+                }
+            }
+            if (updated) {
+                mDestinationSpinnerAdapter.notifyDataSetChanged();
+            }
+        }
+
+        private boolean hasErrors() {
+            return mRangeEditText.getError() != null
+                    || mCopiesEditText.getError() != null;
+        }
+
+        private boolean hasPdfViewer() {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setType("application/pdf");
+            return !getPackageManager().queryIntentActivities(intent,
+                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
+        }
+
+        private final class SpinnerItem<T> {
+            final T value;
+            CharSequence label;
+
+            public SpinnerItem(T value, CharSequence label) {
+                this.value = value;
+                this.label = label;
+            }
+
+            public String toString() {
+                return label.toString();
+            }
+        }
+
+        private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> {
+
+            public DestinationAdapter() {
+                super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item);
+            }
+
+            @Override
+            public View getDropDownView(int position, View convertView,
+                    ViewGroup parent) {
+                return getView(position, convertView, parent);
+            }
+
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                if (convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.spinner_dropdown_item, parent, false);
+                }
+
+                PrinterInfo printerInfo = getItem(position).value;
+                TextView title = (TextView) convertView.findViewById(R.id.title);
+                title.setText(printerInfo.getLabel());
+
+                try {
+                    TextView subtitle = (TextView)
+                            convertView.findViewById(R.id.subtitle);
+                    PackageManager pm = getPackageManager();
+                    PackageInfo packageInfo = pm.getPackageInfo(
+                            printerInfo.getId().getService().getPackageName(), 0);
+                    subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
+                    subtitle.setVisibility(View.VISIBLE);
+                } catch (NameNotFoundException nnfe) {
+                    /* ignore */
+                }
+
+                return convertView;
+            }
+        }
+    }
+
+    private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
+        private static final int MSG_ON_PRINTERS_ADDED = 1;
+        private static final int MSG_ON_PRINTERS_REMOVED = 2;
+        private static final int MSG_ON_PRINTERS_UPDATED = 3;
+
+        private Handler mHandler;
+        private Editor mEditor;
+
+        @SuppressWarnings("unchecked")
+        public PrinterDiscoveryObserver(Editor editor, Looper looper) {
+            mEditor = editor;
+            mHandler = new Handler(looper, null, true) {
+                @Override
+                public void handleMessage(Message message) {
+                    switch (message.what) {
+                        case MSG_ON_PRINTERS_ADDED: {
+                            List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+                            mEditor.addPrinters(printers);
+                        } break;
+
+                        case MSG_ON_PRINTERS_REMOVED: {
+                            List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+                            mEditor.removePrinters(printerIds);
+                        } break;
+
+                        case MSG_ON_PRINTERS_UPDATED: {
+                            List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+                            mEditor.updatePrinters(printers);
+                        } break;
+                    }
+                }
+            };
+        }
+
+        @Override
+        public void onPrintersAdded(List<PrinterInfo> printers) {
+            synchronized (this) {
+                if (mHandler != null) {
+                    mHandler.obtainMessage(MSG_ON_PRINTERS_ADDED, printers)
+                        .sendToTarget();
+                }
+            }
+        }
+
+        @Override
+        public void onPrintersRemoved(List<PrinterId> printers) {
+            synchronized (this) {
+                if (mHandler != null) {
+                    mHandler.obtainMessage(MSG_ON_PRINTERS_REMOVED, printers)
+                        .sendToTarget();
+                }
+            }
+        }
+
+        @Override
+        public void onPrintersUpdated(List<PrinterInfo> printers) {
+            synchronized (this) {
+                if (mHandler != null) {
+                    mHandler.obtainMessage(MSG_ON_PRINTERS_UPDATED, printers)
+                        .sendToTarget();
+                }
+            }
+        }
+
+        public void destroy() {
+            synchronized (this) {
+                mHandler = null;
+                mEditor = null;
+            }
+        }
+    }
+
+    /**
+     * An instance of this class class is intended to be the first focusable
+     * in a layout to which the system automatically gives focus. It performs
+     * some voodoo to avoid the first tap on it to start an edit mode, rather
+     * to bring up the IME, i.e. to get the behavior as if the view was not
+     * focused.
+     */
+    public static final class CustomEditText extends EditText {
+        private boolean mClickedBeforeFocus;
+        private CharSequence mError;
+
+        public CustomEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public boolean performClick() {
+            super.performClick();
+            if (isFocused() && !mClickedBeforeFocus) {
+                clearFocus();
+                requestFocus();
+            }
+            mClickedBeforeFocus = true;
+            return true;
+        }
+
+        @Override
+        public CharSequence getError() {
+            return mError;
+        }
+
+        @Override
+        public void setError(CharSequence error, Drawable icon) {
+            setCompoundDrawables(null, null, icon, null);
+            mError = error;
+        }
+
+        protected void onFocusChanged(boolean gainFocus, int direction,
+                Rect previouslyFocusedRect) {
+            if (!gainFocus) {
+                mClickedBeforeFocus = false;
+            }
+            super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        }
+    }
+
+    private static final class Document {
+        public PrintDocumentInfo info;
+        public PageRange[] pages;
+    }
+
+    private static final class PageRangeUtils {
+
+        private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
+            @Override
+            public int compare(PageRange lhs, PageRange rhs) {
+                return lhs.getStart() - rhs.getStart();
+            }
+        };
+
+        private PageRangeUtils() {
+            throw new UnsupportedOperationException();
+        }
+
+        public static boolean contains(PageRange[] ourPageRanges, PageRange[] otherPageRanges) {
+            if (ourPageRanges == null || otherPageRanges == null) {
+                return false;
+            }
+
+            otherPageRanges = normalize(otherPageRanges);
+
+            int otherPageIdx = 0;
+            final int myPageCount = ourPageRanges.length;
+            final int otherPageCount = otherPageRanges.length;
+            for (int i= 0; i < myPageCount; i++) {
+                PageRange myPage = ourPageRanges[i];
+                for (; otherPageIdx < otherPageCount; otherPageIdx++) {
+                    PageRange otherPage = otherPageRanges[otherPageIdx];
+                    if (otherPage.getStart() > myPage.getStart()) {
+                        break;
+                    }
+                    if ((otherPage.getStart() < myPage.getStart()
+                                    && otherPage.getEnd() > myPage.getStart())
+                            || (otherPage.getEnd() > myPage.getEnd()
+                                    && otherPage.getStart() < myPage.getEnd())
+                            || (otherPage.getEnd() < myPage.getStart())) {
+                        return false;
+                    }
+                }
+            }
+            if (otherPageIdx < otherPageCount) {
+                return false;
+            }
+            return true;
+        }
+
+        public static PageRange[] normalize(PageRange[] pageRanges) {
+            if (pageRanges == null) {
+                return null;
+            }
+            final int oldPageCount = pageRanges.length;
+            if (oldPageCount <= 1) {
+                return pageRanges;
+            }
+            Arrays.sort(pageRanges, sComparator);
+            int newRangeCount = 0;
+            for (int i = 0; i < oldPageCount - 1; i++) {
+                newRangeCount++;
+                PageRange currentRange = pageRanges[i];
+                PageRange nextRange = pageRanges[i + 1];
+                if (currentRange.getEnd() >= nextRange.getStart()) {
+                    newRangeCount--;
+                    pageRanges[i] = null;
+                    pageRanges[i + 1] = new PageRange(currentRange.getStart(),
+                            nextRange.getEnd());
+                }
+            }
+            if (newRangeCount == oldPageCount) {
+                return pageRanges;
+            }
+            return Arrays.copyOfRange(pageRanges, oldPageCount - newRangeCount,
+                    oldPageCount - 1);
+        }
+
+        public static void offsetStart(PageRange[] pageRanges, int offset) {
+            if (offset == 0) {
+                return;
+            }
+            final int pageRangeCount = pageRanges.length;
+            for (int i = 0; i < pageRangeCount; i++) {
+                final int start = pageRanges[i].getStart() + offset;
+                final int end = pageRanges[i].getEnd() + offset;
+                pageRanges[i] = new PageRange(start, end);
+            }
         }
     }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
index 53ae1459..870bfffd 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -19,6 +19,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.print.IPrintClient;
@@ -39,6 +42,7 @@
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastXmlSerializer;
 
 import libcore.io.IoUtils;
@@ -59,9 +63,9 @@
 
 public class PrintSpooler {
 
-    private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
+    private static final String LOG_TAG = "PrintSpooler";
 
-    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
 
     private static final boolean DEBUG_PERSISTENCE = true;
 
@@ -81,6 +85,8 @@
 
     private final PersistenceManager mPersistanceManager;
 
+    private final Handler mHandler;
+
     private final Context mContext;
 
     public IPrintSpoolerClient mClient;
@@ -97,6 +103,7 @@
     private PrintSpooler(Context context) {
         mContext = context;
         mPersistanceManager = new PersistenceManager(context);
+        mHandler = new MyHandler(context.getMainLooper());
     }
 
     public void setCleint(IPrintSpoolerClient client) {
@@ -111,37 +118,36 @@
         }
     }
 
-    public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
-        IPrintSpoolerClient client = null;
+    public void onReqeustUpdatePrinters(List<PrinterId> printers) {
         synchronized (mLock) {
-            client = mClient;
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = mClient;
+            args.arg2 = printers;
+            mHandler.obtainMessage(MyHandler.MSG_REQUEST_UPDATE_PRINTERS,
+                    args).sendToTarget();
         }
-        if (client != null) {
-            try {
-                client.onStartPrinterDiscovery(observer);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
-            }
+    }
+
+    public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
+        synchronized (mLock) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = mClient;
+            args.arg2 = observer;
+            mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+                    args).sendToTarget();
         }
     }
 
     public void stopPrinterDiscovery() {
-        IPrintSpoolerClient client = null;
         synchronized (mLock) {
-            client = mClient;
-        }
-        if (client != null) {
-            try {
-                client.onStopPrinterDiscovery();
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
-            }
+            mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY,
+                    mClient).sendToTarget();
         }
     }
 
     public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, int appId) {
+        List<PrintJobInfo> foundPrintJobs = null;
         synchronized (mLock) {
-            List<PrintJobInfo> foundPrintJobs = null;
             final int printJobCount = mPrintJobs.size();
             for (int i = 0; i < printJobCount; i++) {
                 PrintJobInfo printJob = mPrintJobs.get(i);
@@ -162,8 +168,8 @@
                     foundPrintJobs.add(printJob);
                 }
             }
-            return foundPrintJobs;
         }
+        return foundPrintJobs;
     }
 
     public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
@@ -172,11 +178,12 @@
             for (int i = 0; i < printJobCount; i++) {
                 PrintJobInfo printJob = mPrintJobs.get(i);
                 if (printJob.getId() == printJobId
-                        && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
+                        && (appId == PrintManager.APP_ID_ANY
+                                || appId == printJob.getAppId())) {
                     return printJob;
                 }
              }
-            return null;
+             return null;
         }
     }
 
@@ -217,7 +224,7 @@
         Map<ComponentName, List<PrintJobInfo>> activeJobsPerServiceMap =
                 new HashMap<ComponentName, List<PrintJobInfo>>();
 
-        synchronized(mLock) {
+        synchronized (mLock) {
             if (mClient == null) {
                 throw new IllegalStateException("Client cannot be null.");
             }
@@ -265,16 +272,25 @@
                 for (int i = 0; i < printJobCount; i++) {
                     PrintJobInfo printJob = printJobs.get(i);
                     if (printJob.getState() == PrintJobInfo.STATE_QUEUED) {
-                        callOnPrintJobQueuedQuietly(client, printJob);
+                        SomeArgs args = SomeArgs.obtain();
+                        args.arg1 = client;
+                        args.arg2 = new PrintJobInfo(printJob);
+                        mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+                                args).sendToTarget();
                     }
                 }
             } else {
-                callOnAllPrintJobsForServiceHandledQuietly(client, service);
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = client;
+                args.arg2 = service;
+                mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+                        args).sendToTarget();
             }
         }
 
         if (allPrintJobsHandled) {
-            callOnAllPrintJobsHandledQuietly(client);
+            mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+                    client).sendToTarget();
         }
     }
 
@@ -297,37 +313,43 @@
         return false;
     }
 
-    @SuppressWarnings("resource")
-    public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+    public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+        final PrintJobInfo printJob;
         synchronized (mLock) {
-            FileInputStream in = null;
-            FileOutputStream out = null;
-            try {
-                PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-                if (printJob != null) {
-                    File file = generateFileForPrintJob(printJobId);
-                    in = new FileInputStream(file);
-                    out = new FileOutputStream(fd.getFileDescriptor());
+            printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+        }
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                FileInputStream in = null;
+                FileOutputStream out = null;
+                try {
+                    if (printJob != null) {
+                        File file = generateFileForPrintJob(printJobId);
+                        in = new FileInputStream(file);
+                        out = new FileOutputStream(fd.getFileDescriptor());
+                    }
                     final byte[] buffer = new byte[8192];
                     while (true) {
                         final int readByteCount = in.read(buffer);
                         if (readByteCount < 0) {
-                            return true;
+                            return null;
                         }
                         out.write(buffer, 0, readByteCount);
                     }
+                } catch (FileNotFoundException fnfe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                    IoUtils.closeQuietly(out);
+                    IoUtils.closeQuietly(fd);
                 }
-            } catch (FileNotFoundException fnfe) {
-                Log.e(LOG_TAG, "Error writing print job data!", fnfe);
-            } catch (IOException ioe) {
-                Log.e(LOG_TAG, "Error writing print job data!", ioe);
-            } finally {
-                IoUtils.closeQuietly(in);
-                IoUtils.closeQuietly(out);
-                IoUtils.closeQuietly(fd);
+                Log.i(LOG_TAG, "[END WRITE]");
+                return null;
             }
-        }
-        return false;
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
 
     public File generateFileForPrintJob(int printJobId) {
@@ -354,28 +376,24 @@
     public boolean setPrintJobState(int printJobId, int state) {
         boolean success = false;
 
-        boolean allPrintJobsHandled = false;
-        boolean allPrintJobsForServiceHandled = false;
-
-        IPrintSpoolerClient client = null;
-        PrintJobInfo queuedPrintJob = null;
-        PrintJobInfo removedPrintJob = null;
-
         synchronized (mLock) {
             if (mClient == null) {
                 throw new IllegalStateException("Client cannot be null.");
             }
-            client = mClient;
 
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null && printJob.getState() < state) {
                 success = true;
                 printJob.setState(state);
+
+                if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                    Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+                }
+
                 // TODO: Update notifications.
                 switch (state) {
                     case PrintJobInfo.STATE_COMPLETED:
                     case PrintJobInfo.STATE_CANCELED: {
-                        removedPrintJob = printJob;
                         removePrintJobLocked(printJob);
 
                         // No printer means creation of a print job was cancelled,
@@ -387,83 +405,46 @@
                             return true;
                         }
 
-                        allPrintJobsHandled = !hasActivePrintJobsLocked();
-                        allPrintJobsForServiceHandled = !hasActivePrintJobsForServiceLocked(
-                                printerId.getService());
+                        ComponentName service = printerId.getService();
+                        if (!hasActivePrintJobsForServiceLocked(service)) {
+                            SomeArgs args = SomeArgs.obtain();
+                            args.arg1 = mClient;
+                            args.arg2 = service;
+                            mHandler.obtainMessage(
+                                    MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+                                    args).sendToTarget();
+                        }
+
+                        if (!hasActivePrintJobsLocked()) {
+                            mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+                                    mClient).sendToTarget();
+                        }
                     } break;
 
                     case PrintJobInfo.STATE_QUEUED: {
-                        queuedPrintJob = new PrintJobInfo(printJob);
+                        SomeArgs args = SomeArgs.obtain();
+                        args.arg1 = mClient;
+                        args.arg2 = new PrintJobInfo(printJob);
+                        mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+                                args).sendToTarget();
                     } break;
                 }
-                if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                    Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
+
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
                 }
-                mPersistanceManager.writeStateLocked();
             }
         }
 
-        if (queuedPrintJob != null) {
-            callOnPrintJobQueuedQuietly(client, queuedPrintJob);
-        }
-
-        if (allPrintJobsForServiceHandled) {
-            callOnAllPrintJobsForServiceHandledQuietly(client,
-                        removedPrintJob.getPrinterId().getService());
-        }
-
-        if (allPrintJobsHandled) {
-            callOnAllPrintJobsHandledQuietly(client);
-        }
-
         return success;
     }
 
-    private void callOnPrintJobQueuedQuietly(IPrintSpoolerClient client,
-            PrintJobInfo printJob) {
-        try {
-            client.onPrintJobQueued(printJob);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
-        }
-    }
-
-    private void callOnAllPrintJobsForServiceHandledQuietly(IPrintSpoolerClient client,
-            ComponentName service) {
-        try {
-            client.onAllPrintJobsForServiceHandled(service);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
-        }
-    }
-
-    private void callOnAllPrintJobsHandledQuietly(final IPrintSpoolerClient client) {
-        // This has to run on the tread that is persisting the current state
-        // since this call may result in the system unbinding from the spooler
-        // and as a result the spooler process may get killed before the write
-        // completes.
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                try {
-                    client.onAllPrintJobsHandled();
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
-                }
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
     private boolean hasActivePrintJobsLocked() {
         final int printJobCount = mPrintJobs.size();
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
-            switch (printJob.getState()) {
-                case PrintJobInfo.STATE_QUEUED:
-                case PrintJobInfo.STATE_STARTED: {
-                    return true;
-                }
+            if (!isActiveState(printJob.getState())) {
+                return true;
             }
         }
         return false;
@@ -473,72 +454,89 @@
         final int printJobCount = mPrintJobs.size();
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
-            switch (printJob.getState()) {
-                case PrintJobInfo.STATE_QUEUED:
-                case PrintJobInfo.STATE_STARTED: {
-                    if (printJob.getPrinterId().getService().equals(service)) {
-                        return true;
-                    }
-                } break;
+            if (!isActiveState(printJob.getState())
+                    && printJob.getPrinterId().getService().equals(service)) {
+                return true;
             }
         }
         return false;
     }
 
+    private static boolean isActiveState(int printJobState) {
+        return printJobState != PrintJobInfo.STATE_CREATED
+                || printJobState != PrintJobInfo.STATE_QUEUED
+                || printJobState != PrintJobInfo.STATE_STARTED;
+    }
+
     public boolean setPrintJobTag(int printJobId, String tag) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null) {
+                String printJobTag = printJob.getTag();
+                if (printJobTag == null) {
+                    if (tag == null) {
+                        return false;
+                    }
+                } else if (printJobTag.equals(tag)) {
+                    return false;
+                }
                 printJob.setTag(tag);
-                mPersistanceManager.writeStateLocked();
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
                 return true;
             }
         }
         return false;
     }
 
-    public final boolean setPrintJobPrintDocumentInfo(int printJobId, PrintDocumentInfo info) {
+    public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setCopies(copies);
+            }
+        }
+    }
+
+    public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null) {
                 printJob.setDocumentInfo(info);
-                mPersistanceManager.writeStateLocked();
-                return true;
             }
         }
-        return false;
     }
 
-    public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
+    public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null) {
                 printJob.setAttributes(attributes);
-                mPersistanceManager.writeStateLocked();
             }
         }
     }
 
-    public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
+    public void setPrintJobPrinterIdNoPersistence(int printJobId, PrinterId printerId) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null) {
                 printJob.setPrinterId(printerId);
-                mPersistanceManager.writeStateLocked();
             }
         }
     }
 
-    public boolean setPrintJobPages(int printJobId, PageRange[] pages) {
+    public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
             if (printJob != null) {
                 printJob.setPages(pages);
-                mPersistanceManager.writeStateLocked();
-                return true;
             }
         }
-        return false;
+    }
+
+    private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+        return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
     }
 
     private final class PersistenceManager {
@@ -558,6 +556,7 @@
         private static final String ATTR_APP_ID = "appId";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_TAG = "tag";
+        private static final String ATTR_COPIES = "copies";
 
         private static final String TAG_MEDIA_SIZE = "mediaSize";
         private static final String TAG_RESOLUTION = "resolution";
@@ -620,6 +619,9 @@
         }
 
         private void doWriteStateLocked() {
+            if (DEBUG_PERSISTENCE) {
+                Log.i(LOG_TAG, "[PERSIST START]");
+            }
             FileOutputStream out = null;
             try {
                 out = mStatePersistFile.startWrite();
@@ -652,6 +654,7 @@
                     if (tag != null) {
                         serializer.attribute(null, ATTR_TAG, tag);
                     }
+                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
 
                     PrinterId printerId = printJob.getPrinterId();
                     if (printerId != null) {
@@ -775,6 +778,9 @@
                 serializer.endTag(null, TAG_SPOOLER);
                 serializer.endDocument();
                 mStatePersistFile.finishWrite(out);
+                if (DEBUG_PERSISTENCE) {
+                    Log.i(LOG_TAG, "[PERSIST END]");
+                }
             } catch (IOException e) {
                 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
                 mStatePersistFile.failWrite(out);
@@ -861,6 +867,8 @@
             printJob.setUserId(userId);
             String tag = parser.getAttributeValue(null, ATTR_TAG);
             printJob.setTag(tag);
+            String copies = parser.getAttributeValue(null, ATTR_TAG);
+            printJob.setCopies(Integer.parseInt(copies));
 
             parser.next();
 
@@ -892,7 +900,9 @@
                 parser.next();
             }
             if (pageRanges != null) {
-                printJob.setPages((PageRange[]) pageRanges.toArray());
+                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+                pageRanges.toArray(pageRangesArray);
+                printJob.setPages(pageRangesArray);
             }
 
             skipEmptyTextTags(parser);
@@ -1054,4 +1064,108 @@
             return true;
         }
     }
+
+    private final class MyHandler extends Handler {
+        public static final int MSG_START_PRINTER_DISCOVERY = 1;
+        public static final int MSG_STOP_PRINTER_DISCOVERY = 2;
+        public static final int MSG_PRINT_JOB_QUEUED = 3;
+        public static final int MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
+        public static final int MSG_ALL_PRINT_JOBS_HANDLED = 5;
+        public static final int MSG_REQUEST_UPDATE_PRINTERS = 6;
+
+        public MyHandler(Looper looper) {
+            super(looper, null, false);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_START_PRINTER_DISCOVERY: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+                    IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg2;
+                    args.recycle();
+                    if (client != null) {
+                        try {
+                            client.onStartPrinterDiscovery(observer);
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_STOP_PRINTER_DISCOVERY: {
+                    IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+                    if (client != null) {
+                        try {
+                            client.onStopPrinterDiscovery();
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_PRINT_JOB_QUEUED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+                    PrintJobInfo printJob = (PrintJobInfo) args.arg2;
+                    args.recycle();
+                    if (client != null) {
+                        try {
+                            client.onPrintJobQueued(printJob);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+                    ComponentName service = (ComponentName) args.arg2;
+                    args.recycle();
+                    if (client != null) {
+                        try {
+                            client.onAllPrintJobsForServiceHandled(service);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for all print jobs per service"
+                                    + " handled.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_ALL_PRINT_JOBS_HANDLED: {
+                    final IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+                    // This has to run on the tread that is persisting the current state
+                    // since this call may result in the system unbinding from the spooler
+                    // and as a result the spooler process may get killed before the write
+                    // completes.
+                    new AsyncTask<Void, Void, Void>() {
+                        @Override
+                        protected Void doInBackground(Void... params) {
+                            try {
+                                client.onAllPrintJobsHandled();
+                            } catch (RemoteException re) {
+                                Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
+                            }
+                            return null;
+                        }
+                    }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+                } break;
+
+                case MSG_REQUEST_UPDATE_PRINTERS: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+                    List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
+                    args.recycle();
+                    try {
+                        client.onRequestUpdatePrinters(printerIds);
+                    } catch (RemoteException re) {
+                        Slog.e(LOG_TAG, "Error requesting to update pritners.", re);
+                    }
+                } break;
+            }
+        }
+    }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 26d2a33..5ff2aa6 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -114,12 +114,11 @@
                             attributes, appId);
                     if (printJob != null) {
                         Intent intent = mStartPrintJobConfigActivityIntent;
-                        intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
+                        intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
                                 printAdapter.asBinder());
-                        intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
                         intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
                                 printJob.getId());
-                        intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
+                        intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_ATTRIBUTES, attributes);
 
                         IntentSender sender = PendingIntent.getActivity(
                                 PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
index 25bb37c..4006a5a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
@@ -17,8 +17,8 @@
 package com.android.printspooler;
 
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Bundle;
-import android.os.ICancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.print.ILayoutResultCallback;
@@ -26,11 +26,7 @@
 import android.print.IWriteResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
 import android.util.Log;
-import android.util.Slog;
 
 import libcore.io.IoUtils;
 
@@ -40,8 +36,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * This class represents a remote print document adapter instance.
@@ -49,461 +43,99 @@
 final class RemotePrintDocumentAdapter {
     private static final String LOG_TAG = "RemotePrintDocumentAdapter";
 
-    private static final boolean DEBUG = true;
-
-    public static final int STATE_INITIALIZED = 0;
-    public static final int STATE_START_COMPLETED = 1;
-    public static final int STATE_LAYOUT_STARTED = 2;
-    public static final int STATE_LAYOUT_COMPLETED = 3;
-    public static final int STATE_WRITE_STARTED = 4;
-    public static final int STATE_WRITE_COMPLETED = 5;
-    public static final int STATE_FINISH_COMPLETED = 6;
-    public static final int STATE_FAILED = 7;
-
-    private final Object mLock = new Object();
-
-    private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>();
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
 
     private final IPrintDocumentAdapter mRemoteInterface;
 
     private final File mFile;
 
-    private int mState = STATE_INITIALIZED;
-
     public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
         mRemoteInterface = printAdatper;
         mFile = file;
     }
 
-    public File getFile() {
+    public void start()  {
         if (DEBUG) {
-            Log.i(LOG_TAG, "getFile()");
+            Log.i(LOG_TAG, "start()");
         }
-        synchronized (mLock) {
-            if (mState != STATE_WRITE_COMPLETED
-                    && mState != STATE_FINISH_COMPLETED) {
-                throw new IllegalStateException("Write not completed");
-            }
-            return mFile;
+        try {
+            mRemoteInterface.start();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling start()", re);
         }
     }
 
-    public void start() {
-        QueuedAsyncTask task = new QueuedAsyncTask() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "start()");
-                }
-                synchronized (mLock) {
-                    if (mState != STATE_INITIALIZED) {
-                        throw new IllegalStateException("Invalid state: " + mState);
-                    }
-                }
-                try {
-                    mRemoteInterface.start();
-                    synchronized (mLock) {
-                        mState = STATE_START_COMPLETED;
-                    }
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error reading file", re);
-                }
-                return null;
-            }
-
-            @Override
-            public void cancel() {
-                /* cannot be cancelled */
-            }
-        };
-        synchronized (mLock) {
-            mTaskQueue.add(task);
-        }
-        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
     public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
-            LayoutResultCallback callback, Bundle metadata) {
-        LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback,
-                metadata);
-        synchronized (mLock) {
-            mTaskQueue.add(task);
+            ILayoutResultCallback callback, Bundle metadata, int sequence) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "layout()");
         }
-        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    public void write(List<PageRange> pages, WriteResultCallback callback) {
-        WriteAsyncTask task = new WriteAsyncTask(pages, callback);
-        synchronized (mLock) {
-            mTaskQueue.add(task);
-        }
-        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    public void cancel() {
-        synchronized (mLock) {
-            final int taskCount = mTaskQueue.size();
-            for (int i = taskCount - 1; i >= 0; i--) {
-                mTaskQueue.remove(i).cancel();
-            }
+        try {
+            mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling layout()", re);
         }
     }
 
-    public void finish() {
-        QueuedAsyncTask task = new QueuedAsyncTask() {
+    public void write(final PageRange[] pages, final IWriteResultCallback callback,
+            final int sequence) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "write()");
+        }
+        new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "finish()");
-                }
-                synchronized (mLock) {
-                    if (mState < STATE_START_COMPLETED) {
-                        return null;
-                    }
-                }
+                InputStream in = null;
+                OutputStream out = null;
+                ParcelFileDescriptor source = null;
+                ParcelFileDescriptor sink = null;
                 try {
-                    mRemoteInterface.finish();
-                    synchronized (mLock) {
-                        mState = STATE_FINISH_COMPLETED;
+                    ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                    source = pipe[0];
+                    sink = pipe[1];
+
+                    in = new FileInputStream(source.getFileDescriptor());
+                    out = new FileOutputStream(mFile);
+
+                    // Async call to initiate the other process writing the data.
+                    mRemoteInterface.write(pages, sink, callback, sequence);
+
+                    // Close the source. It is now held by the client.
+                    sink.close();
+                    sink = null;
+
+                    // Read the data.
+                    final byte[] buffer = new byte[8192];
+                    while (true) {
+                        final int readByteCount = in.read(buffer);
+                        if (readByteCount < 0) {
+                            break;
+                        }
+                        out.write(buffer, 0, readByteCount);
                     }
                 } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error reading file", re);
-                    mState = STATE_FAILED;
+                    Log.e(LOG_TAG, "Error calling write()", re);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "Error calling write()", ioe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                    IoUtils.closeQuietly(out);
+                    IoUtils.closeQuietly(sink);
+                    IoUtils.closeQuietly(source);
                 }
                 return null;
             }
-
-            @Override
-            public void cancel() {
-                /* cannot be cancelled */
-            }
-        };
-        synchronized (mLock) {
-            mTaskQueue.add(task);
-        }
-        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
 
-    private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> {
-        public void cancel() {
-            super.cancel(true);
+    public void finish() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "finish()");
         }
-    }
-
-    private final class LayoutAsyncTask extends QueuedAsyncTask {
-
-        private final PrintAttributes mOldAttributes;
-
-        private final PrintAttributes mNewAttributes;
-
-        private final LayoutResultCallback mCallback;
-
-        private final Bundle mMetadata;
-
-        private final ILayoutResultCallback mILayoutResultCallback =
-                new ILayoutResultCallback.Stub() {
-            @Override
-            public void onLayoutStarted(ICancellationSignal cancellationSignal) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onLayoutStarted()");
-                }
-                synchronized (mLock) {
-                    mCancellationSignal = cancellationSignal;
-                    if (isCancelled()) {
-                        cancelSignalQuietlyLocked();
-                    }
-                }
-            }
-
-            @Override
-            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onLayoutFinished()");
-                }
-                final boolean cancelled;
-                synchronized (mLock) {
-                    cancelled = isCancelled();
-                    mCancellationSignal = null;
-                    mState = STATE_LAYOUT_COMPLETED;
-                    mTaskQueue.remove(this);
-                    mLock.notifyAll();
-                }
-                if (!cancelled) {
-                    mCallback.onLayoutFinished(info, changed);
-                }
-            }
-
-            @Override
-            public void onLayoutFailed(CharSequence error) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onLayoutFailed()");
-                }
-                final boolean cancelled;
-                synchronized (mLock) {
-                    cancelled = isCancelled();
-                    mCancellationSignal = null;
-                    mState = STATE_LAYOUT_COMPLETED;
-                    mTaskQueue.remove(this);
-                    mLock.notifyAll();
-                }
-                if (!cancelled) {
-                    mCallback.onLayoutFailed(error);
-                }
-            }
-        };
-
-        private ICancellationSignal mCancellationSignal;
-
-        public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes,
-                LayoutResultCallback callback, Bundle metadata) {
-            mOldAttributes = oldAttributes;
-            mNewAttributes = newAttributes;
-            mCallback = callback;
-            mMetadata = metadata;
-        }
-
-        @Override
-        public void cancel() {
-            synchronized (mLock) {
-                throwIfCancelledLocked();
-                cancelSignalQuietlyLocked();
-            }
-            super.cancel();
-        }
-
-        @Override
-        protected Void doInBackground(Void... params) {
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "layout()");
-                }
-                if (mState != STATE_START_COMPLETED
-                        && mState != STATE_LAYOUT_COMPLETED
-                        && mState != STATE_WRITE_COMPLETED) {
-                    throw new IllegalStateException("Invalid state: " + mState);
-                }
-                mState = STATE_LAYOUT_STARTED;
-            }
-            try {
-                mRemoteInterface.layout(mOldAttributes, mNewAttributes,
-                        mILayoutResultCallback, mMetadata);
-                synchronized (mLock) {
-                    while (true) {
-                        if (mState == STATE_LAYOUT_COMPLETED) {
-                            break;
-                        }
-                        try {
-                            mLock.wait();
-                        } catch (InterruptedException ie) {
-                            /* ignore */
-                        }
-                    }
-                }
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error calling layout", re);
-                mState = STATE_FAILED;
-                mTaskQueue.remove(this);
-                notifyLayoutFailedQuietly();
-            }
-            return null;
-        }
-
-        private void cancelSignalQuietlyLocked() {
-            if (mCancellationSignal != null) {
-                try {
-                    mCancellationSignal.cancel();
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Error cancelling layout", re);
-                    notifyLayoutFailedQuietly();
-                }
-            }
-        }
-
-        public void notifyLayoutFailedQuietly() {
-            try {
-                mILayoutResultCallback.onLayoutFailed(null);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
-
-        private void throwIfCancelledLocked() {
-            if (isCancelled()) {
-                throw new IllegalStateException("Already cancelled");
-            }
-        }
-    }
-
-    private final class WriteAsyncTask extends QueuedAsyncTask {
-
-        private final List<PageRange> mPages;
-
-        private final WriteResultCallback mCallback;
-
-        private final IWriteResultCallback mIWriteResultCallback =
-                new IWriteResultCallback.Stub() {
-            @Override
-            public void onWriteStarted(ICancellationSignal cancellationSignal) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onWriteStarted()");
-                }
-                synchronized (mLock) {
-                    mCancellationSignal = cancellationSignal;
-                    if (isCancelled()) {
-                        cancelSignalQuietlyLocked();
-                    }
-                }
-            }
-
-            @Override
-            public void onWriteFinished(List<PageRange> pages) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onWriteFinished()");
-                }
-                synchronized (mLock) {
-                    mCancellationSignal = null;
-                    mState = STATE_WRITE_COMPLETED;
-                    mTaskQueue.remove(this);
-                    mLock.notifyAll();
-                }
-                mCallback.onWriteFinished(pages);
-            }
-
-            @Override
-            public void onWriteFailed(CharSequence error) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "onWriteFailed()");
-                }
-                synchronized (mLock) {
-                    mCancellationSignal = null;
-                    mState = STATE_WRITE_COMPLETED;
-                    mTaskQueue.remove(this);
-                    mLock.notifyAll();
-                }
-                Slog.e(LOG_TAG, "Error writing print document: " + error);
-                mCallback.onWriteFailed(error);
-            }
-        };
-
-        private ICancellationSignal mCancellationSignal;
-
-        private Thread mWriteThread;
-
-        public WriteAsyncTask(List<PageRange> pages, WriteResultCallback callback) {
-            mPages = pages;
-            mCallback = callback;
-        }
-
-        @Override
-        public void cancel() {
-            synchronized (mLock) {
-                throwIfCancelledLocked();
-                cancelSignalQuietlyLocked();
-                mWriteThread.interrupt();
-            }
-            super.cancel();
-        }
-
-        @Override
-        protected Void doInBackground(Void... params) {
-            if (DEBUG) {
-                Log.i(LOG_TAG, "write()");
-            }
-            synchronized (mLock) {
-                if (mState != STATE_LAYOUT_COMPLETED
-                        && mState != STATE_WRITE_COMPLETED) {
-                    throw new IllegalStateException("Invalid state: " + mState);
-                }
-                mState = STATE_WRITE_STARTED;
-            }
-            InputStream in = null;
-            OutputStream out = null;
-            ParcelFileDescriptor source = null;
-            ParcelFileDescriptor sink = null;
-            synchronized (mLock) {
-                mWriteThread = Thread.currentThread();
-            }
-            try {
-                ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
-                source = pipe[0];
-                sink = pipe[1];
-
-                in = new FileInputStream(source.getFileDescriptor());
-                out = new FileOutputStream(mFile);
-
-                // Async call to initiate the other process writing the data.
-                mRemoteInterface.write(mPages, sink, mIWriteResultCallback);
-
-                // Close the source. It is now held by the client.
-                sink.close();
-                sink = null;
-
-                final byte[] buffer = new byte[8192];
-                while (true) {
-                    if (Thread.currentThread().isInterrupted()) {
-                        Thread.currentThread().interrupt();
-                        break;
-                    }
-                    final int readByteCount = in.read(buffer);
-                    if (readByteCount < 0) {
-                        break;
-                    }
-                    out.write(buffer, 0, readByteCount);
-                }
-                synchronized (mLock) {
-                    while (true) {
-                        if (mState == STATE_WRITE_COMPLETED) {
-                            break;
-                        }
-                        try {
-                            mLock.wait();
-                        } catch (InterruptedException ie) {
-                            /* ignore */
-                        }
-                    }
-                }
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error writing print document", re);
-                mState = STATE_FAILED;
-                mTaskQueue.remove(this);
-                notifyWriteFailedQuietly();
-            } catch (IOException ioe) {
-                Slog.e(LOG_TAG, "Error writing print document", ioe);
-                mState = STATE_FAILED;
-                mTaskQueue.remove(this);
-                notifyWriteFailedQuietly();
-            } finally {
-                IoUtils.closeQuietly(in);
-                IoUtils.closeQuietly(out);
-                IoUtils.closeQuietly(sink);
-                IoUtils.closeQuietly(source);
-            }
-            return null;
-        }
-
-        private void cancelSignalQuietlyLocked() {
-            if (mCancellationSignal != null) {
-                try {
-                    mCancellationSignal.cancel();
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Error cancelling layout", re);
-                    notifyWriteFailedQuietly();
-                }
-            }
-        }
-
-        private void notifyWriteFailedQuietly() {
-            try {
-                mIWriteResultCallback.onWriteFailed(null);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
-
-        private void throwIfCancelledLocked() {
-            if (isCancelled()) {
-                throw new IllegalStateException("Already cancelled");
-            }
+        try {
+            mRemoteInterface.finish();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling finish()", re);
         }
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 459c92c..26f910e 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2985,6 +2985,13 @@
                         mActionBar.initIndeterminateProgress();
                     }
 
+                    final ActionBarOverlayLayout abol = (ActionBarOverlayLayout) findViewById(
+                            com.android.internal.R.id.action_bar_overlay_layout);
+                    if (abol != null) {
+                        abol.setOverlayMode(
+                                (localFeatures & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0);
+                    }
+
                     boolean splitActionBar = false;
                     final boolean splitWhenNarrow =
                             (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 1ba79ae..dc8fab6 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -640,15 +640,17 @@
     private void rescheduleKernelAlarmsLocked() {
         // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
         // prior to that which contains no wakeups, we schedule that as well.
-        final Batch firstWakeup = findFirstWakeupBatchLocked();
-        final Batch firstBatch = mAlarmBatches.get(0);
-        if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
-            mNextWakeup = firstWakeup.start;
-            setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
-        }
-        if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
-            mNextNonWakeup = firstBatch.start;
-            setLocked(ELAPSED_REALTIME, firstBatch.start);
+        if (mAlarmBatches.size() > 0) {
+            final Batch firstWakeup = findFirstWakeupBatchLocked();
+            final Batch firstBatch = mAlarmBatches.get(0);
+            if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
+                mNextWakeup = firstWakeup.start;
+                setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+            }
+            if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
+                mNextNonWakeup = firstBatch.start;
+                setLocked(ELAPSED_REALTIME, firstBatch.start);
+            }
         }
     }
 
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 6b4d248..b2d8b94 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -64,6 +64,8 @@
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
 
+    static final int CURRENT_VERSION = 1;
+
     Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
@@ -704,6 +706,9 @@
                         throw new IllegalStateException("no start tag found");
                     }
 
+                    String versStr = parser.getAttributeValue(null, "vers");
+                    int vers = versStr != null ? Integer.parseInt(versStr) : 0;
+
                     int outerDepth = parser.getDepth();
                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -713,7 +718,7 @@
 
                         String tagName = parser.getName();
                         if (tagName.equals("pkg")) {
-                            readPackage(parser);
+                            readPackage(parser, vers);
                         } else {
                             Slog.w(TAG, "Unknown element under <app-ops>: "
                                     + parser.getName());
@@ -746,7 +751,7 @@
         }
     }
 
-    void readPackage(XmlPullParser parser) throws NumberFormatException,
+    void readPackage(XmlPullParser parser, int vers) throws NumberFormatException,
             XmlPullParserException, IOException {
         String pkgName = parser.getAttributeValue(null, "n");
         int outerDepth = parser.getDepth();
@@ -759,7 +764,7 @@
 
             String tagName = parser.getName();
             if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
+                readUid(parser, vers, pkgName);
             } else {
                 Slog.w(TAG, "Unknown element under <pkg>: "
                         + parser.getName());
@@ -768,7 +773,7 @@
         }
     }
 
-    void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
+    void readUid(XmlPullParser parser, int vers, String pkgName) throws NumberFormatException,
             XmlPullParserException, IOException {
         int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
         int outerDepth = parser.getDepth();
@@ -784,7 +789,12 @@
                 Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
                 String mode = parser.getAttributeValue(null, "m");
                 if (mode != null) {
-                    op.mode = Integer.parseInt(mode);
+                    if (vers < CURRENT_VERSION && op.op != AppOpsManager.OP_POST_NOTIFICATION) {
+                        Slog.w(TAG, "AppOps vers " + vers + ": drop mode from "
+                                + pkgName + "/" + uid + " op " + op.op);
+                    } else {
+                        op.mode = Integer.parseInt(mode);
+                    }
                 }
                 String time = parser.getAttributeValue(null, "t");
                 if (time != null) {
@@ -834,6 +844,7 @@
                 out.setOutput(stream, "utf-8");
                 out.startDocument(null, true);
                 out.startTag(null, "app-ops");
+                out.attribute(null, "vers", Integer.toString(CURRENT_VERSION));
 
                 if (allOps != null) {
                     String lastPkg = null;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 9615ff5..17ae85f 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2433,7 +2433,7 @@
         CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
         if (curLp != null) {
             // check for the delta between the current set and the new
-            routeDiff = curLp.compareRoutes(newLp);
+            routeDiff = curLp.compareAllRoutes(newLp);
             dnsDiff = curLp.compareDnses(newLp);
         } else if (newLp != null) {
             routeDiff.added = newLp.getAllRoutes();
@@ -3909,8 +3909,10 @@
                         NetworkInfo.State state = mCs
                                 .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
                         if (state != NetworkInfo.State.CONNECTED) {
-                            log("isMobileOk: not connected ni=" +
+                            if (VDBG) {
+                                log("isMobileOk: not connected ni=" +
                                     mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+                            }
                             sleep(1);
                             result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
                             continue;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 8200a69..da584e2 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -899,7 +899,8 @@
             Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
                     + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
                     + " calling userId = " + userId + ", foreground user id = "
-                    + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid());
+                    + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+                    + InputMethodUtils.getApiCallStack());
         }
         if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
             return true;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index dfaafb8..cde84dc 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -117,6 +117,9 @@
 
     private static final long NANOS_PER_MILLI = 1000000L;
 
+    // The maximum interval a location request can have and still be considered "high power".
+    private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+
     // Location Providers may sometimes deliver location updates
     // slightly faster that requested - provide grace period so
     // we don't unnecessarily filter events that are otherwise on
@@ -459,16 +462,20 @@
         final ILocationListener mListener;
         final PendingIntent mPendingIntent;
         final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
+        final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
         final Object mKey;
 
         final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
 
+        // True if app ops has started monitoring this receiver for locations.
         boolean mOpMonitoring;
+        // True if app ops has started monitoring this receiver for high power (gps) locations.
+        boolean mOpHighPowerMonitoring;
         int mPendingBroadcasts;
         PowerManager.WakeLock mWakeLock;
 
         Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
-                String packageName, WorkSource workSource) {
+                String packageName, WorkSource workSource, boolean hideFromAppOps) {
             mListener = listener;
             mPendingIntent = intent;
             if (listener != null) {
@@ -484,6 +491,7 @@
                 workSource = null;
             }
             mWorkSource = workSource;
+            mHideFromAppOps = hideFromAppOps;
 
             updateMonitoring(true);
 
@@ -526,18 +534,52 @@
         }
 
         public void updateMonitoring(boolean allow) {
-            if (!mOpMonitoring) {
-                if (allow) {
-                    mOpMonitoring = mAppOps.startOpNoThrow(AppOpsManager.OP_MONITOR_LOCATION,
-                            mUid, mPackageName) == AppOpsManager.MODE_ALLOWED;
-                }
-            } else {
-                if (!allow || mAppOps.checkOpNoThrow(AppOpsManager.OP_MONITOR_LOCATION,
-                        mUid, mPackageName) != AppOpsManager.MODE_ALLOWED) {
-                    mAppOps.finishOp(AppOpsManager.OP_MONITOR_LOCATION, mUid, mPackageName);
-                    mOpMonitoring = false;
+            if (mHideFromAppOps) {
+                return;
+            }
+
+            // First update monitoring of any location request (including high power).
+            mOpMonitoring = updateMonitoring(allow, mOpMonitoring,
+                    AppOpsManager.OP_MONITOR_LOCATION);
+
+            // Now update monitoring of high power requests only.
+            // A high power request is any gps request with interval under a threshold.
+            boolean allowHighPower = allow;
+            if (allowHighPower) {
+                UpdateRecord gpsRecord = mUpdateRecords.get(LocationManager.GPS_PROVIDER);
+                if (gpsRecord == null
+                        || gpsRecord.mRequest.getInterval() > HIGH_POWER_INTERVAL_MS) {
+                    allowHighPower = false;
                 }
             }
+            mOpHighPowerMonitoring = updateMonitoring(allowHighPower, mOpHighPowerMonitoring,
+                    AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
+        }
+
+        /**
+         * Update AppOps monitoring for a single location request and op type.
+         *
+         * @param allowMonitoring True if monitoring is allowed for this request/op.
+         * @param currentlyMonitoring True if AppOps is currently monitoring this request/op.
+         * @param op AppOps code for the op to update.
+         * @return True if monitoring is on for this request/op after updating.
+         */
+        private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
+                int op) {
+            if (!currentlyMonitoring) {
+                if (allowMonitoring) {
+                    return mAppOps.startOpNoThrow(op, mUid, mPackageName)
+                            == AppOpsManager.MODE_ALLOWED;
+                }
+            } else {
+                if (!allowMonitoring || mAppOps.checkOpNoThrow(op, mUid, mPackageName)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    mAppOps.finishOp(op, mUid, mPackageName);
+                    return false;
+                }
+            }
+
+            return currentlyMonitoring;
         }
 
         public boolean isListener() {
@@ -895,11 +937,16 @@
      * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
      * for battery).
      */
-    private void checkWorkSourceAllowed() {
+    private void checkDeviceStatsAllowed() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.UPDATE_DEVICE_STATS, null);
     }
 
+    private void checkUpdateAppOpsAllowed() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
+    }
+
     public static int resolutionLevelToOp(int allowedResolutionLevel) {
         if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
             if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
@@ -1224,11 +1271,12 @@
     }
 
     private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
-            String packageName, WorkSource workSource) {
+            String packageName, WorkSource workSource, boolean hideFromAppOps) {
         IBinder binder = listener.asBinder();
         Receiver receiver = mReceivers.get(binder);
         if (receiver == null) {
-            receiver = new Receiver(listener, null, pid, uid, packageName, workSource);
+            receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+                    hideFromAppOps);
             mReceivers.put(binder, receiver);
 
             try {
@@ -1242,10 +1290,11 @@
     }
 
     private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
-            WorkSource workSource) {
+            WorkSource workSource, boolean hideFromAppOps) {
         Receiver receiver = mReceivers.get(intent);
         if (receiver == null) {
-            receiver = new Receiver(null, intent, pid, uid, packageName, workSource);
+            receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+                    hideFromAppOps);
             mReceivers.put(intent, receiver);
         }
         return receiver;
@@ -1307,16 +1356,16 @@
     }
 
     private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
-            int pid, int uid, String packageName, WorkSource workSource) {
+            int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
         if (intent == null && listener == null) {
             throw new IllegalArgumentException("need either listener or intent");
         } else if (intent != null && listener != null) {
             throw new IllegalArgumentException("cannot register both listener and intent");
         } else if (intent != null) {
             checkPendingIntent(intent);
-            return getReceiverLocked(intent, pid, uid, packageName, workSource);
+            return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
         } else {
-            return getReceiverLocked(listener, pid, uid, packageName, workSource);
+            return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
         }
     }
 
@@ -1330,7 +1379,11 @@
                 request.getProvider());
         WorkSource workSource = request.getWorkSource();
         if (workSource != null && workSource.size() > 0) {
-            checkWorkSourceAllowed();
+            checkDeviceStatsAllowed();
+        }
+        boolean hideFromAppOps = request.getHideFromAppOps();
+        if (hideFromAppOps) {
+            checkUpdateAppOpsAllowed();
         }
         LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel);
 
@@ -1345,7 +1398,7 @@
 
             synchronized (mLock) {
                 Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
-                        packageName, workSource);
+                        packageName, workSource, hideFromAppOps);
                 requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName);
             }
         } finally {
@@ -1383,6 +1436,9 @@
             // Notify the listener that updates are currently disabled
             receiver.callProviderEnabledLocked(name, false);
         }
+        // Update the monitoring here just in case multiple location requests were added to the
+        // same receiver (this request may be high power and the initial might not have been).
+        receiver.updateMonitoring(true);
     }
 
     @Override
@@ -1395,8 +1451,9 @@
 
         synchronized (mLock) {
             WorkSource workSource = null;
-            Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName,
-                    workSource);
+            boolean hideFromAppOps = false;
+            Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
+                    packageName, workSource, hideFromAppOps);
 
             // providers may use public location API's, need to clear identity
             long identity = Binder.clearCallingIdentity();
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index bdf6129..1e8a5899 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1633,7 +1633,7 @@
             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
         }
         checkCallerIsSystemOrSameApp(pkg);
-        final boolean isSystemNotification = isCallerSystem() || ("android".equals(pkg));
+        final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
 
         final int userId = ActivityManager.handleIncomingUser(callingPid,
                 callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
@@ -1873,9 +1873,9 @@
                             }
                             mSoundNotification = r;
                             // do not play notifications if stream volume is 0 (typically because
-                            // ringer mode is silent) or if speech recognition is active.
+                            // ringer mode is silent) or if there is a user of exclusive audio focus
                             if ((audioManager.getStreamVolume(audioStreamType) != 0)
-                                    && !audioManager.isSpeechRecognitionActive()) {
+                                    && !audioManager.isAudioFocusExclusive()) {
                                 final long identity = Binder.clearCallingIdentity();
                                 try {
                                     final IRingtonePlayer player = mAudioService.getRingtonePlayer();
@@ -2151,14 +2151,18 @@
         cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
     }
 
-    // Return true if the caller is a system or phone UID and therefore should not have
+    // Return true if the UID is a system or phone UID and therefore should not have
     // any notifications or toasts blocked.
-    boolean isCallerSystem() {
-        final int uid = Binder.getCallingUid();
+    boolean isUidSystem(int uid) {
         final int appid = UserHandle.getAppId(uid);
         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
     }
 
+    // same as isUidSystem(int, int) for the Binder caller's UID.
+    boolean isCallerSystem() {
+        return isUidSystem(Binder.getCallingUid());
+    }
+
     void checkCallerIsSystem() {
         if (isCallerSystem()) {
             return;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index c7b2213..cb1747f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -229,6 +229,7 @@
     static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
     static final boolean DEBUG_USER_LEAVING = localLOGV || false;
     static final boolean DEBUG_VISBILITY = localLOGV || false;
+    static final boolean DEBUG_PSS = localLOGV || false;
     static final boolean VALIDATE_TOKENS = true;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
 
@@ -270,23 +271,12 @@
     static final int GC_MIN_INTERVAL = 60*1000;
 
     // The minimum amount of time between successive PSS requests for a process.
-    static final int PSS_MIN_INTERVAL = 2*60*1000;
-
-    // The amount of time we will sample PSS of the current top process while the
-    // screen is on.
-    static final int PSS_TOP_INTERVAL = 2*60*1000;
-
-    // The amount of time we will sample PSS of any processes that more at least as
-    // important as perceptible while the screen is on.
-    static final int PSS_PERCEPTIBLE_INTERVAL = 10*60*1000;
-
-    // The maximum amount of time for a process to be around until we will take
-    // a PSS snapshot on its next oom change.
-    static final int PSS_MAX_INTERVAL = 30*60*1000;
-
-    // The minimum amount of time between successive PSS requests for a process.
     static final int FULL_PSS_MIN_INTERVAL = 10*60*1000;
 
+    // The minimum amount of time between successive PSS requests for a process
+    // when the request is due to the memory state being lowered.
+    static final int FULL_PSS_LOWERED_INTERVAL = 2*60*1000;
+
     // The rate at which we check for apps using excessive power -- 15 mins.
     static final int POWER_CHECK_DELAY = (DEBUG_POWER_QUICK ? 2 : 15) * 60*1000;
 
@@ -1497,27 +1487,26 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case COLLECT_PSS_BG_MSG: {
-                int i=0;
+                int i=0, num=0;
                 long start = SystemClock.uptimeMillis();
                 long[] tmp = new long[1];
                 do {
                     ProcessRecord proc;
-                    int oomAdj;
+                    int procState;
                     int pid;
                     synchronized (ActivityManagerService.this) {
                         if (i >= mPendingPssProcesses.size()) {
-                            Slog.i(TAG, "Collected PSS of " + i + " processes in "
-                                    + (SystemClock.uptimeMillis()-start) + "ms");
+                            if (DEBUG_PSS) Slog.i(TAG, "Collected PSS of " + num + " of " + i
+                                    + " processes in " + (SystemClock.uptimeMillis()-start) + "ms");
                             mPendingPssProcesses.clear();
                             return;
                         }
                         proc = mPendingPssProcesses.get(i);
-                        if (proc.thread != null) {
-                            oomAdj = proc.setAdj;
+                        procState = proc.pssProcState;
+                        if (proc.thread != null && procState == proc.setProcState) {
                             pid = proc.pid;
                         } else {
                             proc = null;
-                            oomAdj = 0;
                             pid = 0;
                         }
                         i++;
@@ -1525,7 +1514,10 @@
                     if (proc != null) {
                         long pss = Debug.getPss(pid, tmp);
                         synchronized (ActivityManagerService.this) {
-                            if (proc.thread != null && proc.setAdj == oomAdj && proc.pid == pid) {
+                            if (proc.thread != null && proc.setProcState == procState
+                                    && proc.pid == pid) {
+                                num++;
+                                proc.lastPssTime = SystemClock.uptimeMillis();
                                 proc.baseProcessTracker.addPss(pss, tmp[0], true);
                             }
                         }
@@ -4770,7 +4762,7 @@
                                             boolean sticky, int sendingUser) {
                                         synchronized (ActivityManagerService.this) {
                                             requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                                    true);
+                                                    true, false);
                                         }
                                     }
                                 },
@@ -14173,34 +14165,40 @@
     /**
      * Schedule PSS collection of a process.
      */
-    void requestPssLocked(ProcessRecord proc, long now, boolean always) {
-        if (!always && now < (proc.lastPssTime+PSS_MIN_INTERVAL)) {
-            return;
-        }
+    void requestPssLocked(ProcessRecord proc, int procState) {
         if (mPendingPssProcesses.contains(proc)) {
             return;
         }
-        proc.lastPssTime = now;
         if (mPendingPssProcesses.size() == 0) {
             mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
         }
+        if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of: " + proc);
+        proc.pssProcState = procState;
         mPendingPssProcesses.add(proc);
     }
 
     /**
      * Schedule PSS collection of all processes.
      */
-    void requestPssAllProcsLocked(long now, boolean always) {
-        if (!always && now < (mLastFullPssTime+FULL_PSS_MIN_INTERVAL)) {
-            return;
+    void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) {
+        if (!always) {
+            if (now < (mLastFullPssTime +
+                    (memLowered ? FULL_PSS_LOWERED_INTERVAL : FULL_PSS_MIN_INTERVAL))) {
+                return;
+            }
         }
+        if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs!  memLowered=" + memLowered);
         mLastFullPssTime = now;
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
         for (int i=mLruProcesses.size()-1; i>=0; i--) {
             ProcessRecord app = mLruProcesses.get(i);
-            app.lastPssTime = now;
-            mPendingPssProcesses.add(app);
+            if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
+                app.pssProcState = app.setProcState;
+                app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
+                        mSleeping, now);
+                mPendingPssProcesses.add(app);
+            }
         }
         mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
     }
@@ -14459,32 +14457,11 @@
             app.setRawAdj = app.curRawAdj;
         }
 
-        if (!mSleeping) {
-            if (app == TOP_APP && now > (app.lastPssTime+PSS_TOP_INTERVAL)) {
-                // For the current top application we will very aggressively collect
-                // PSS data to have a good measure of memory use while in the foreground.
-                requestPssLocked(app, now, true);
-            } else if (app.curAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
-                    && now > (app.lastPssTime+PSS_TOP_INTERVAL)) {
-                // For any unkillable processes, we will more regularly collect their PSS
-                // since they have a significant impact on the memory state of the device.
-                requestPssLocked(app, now, true);
-            }
-        }
-
         if (app.curAdj != app.setAdj) {
             if (Process.setOomAdj(app.pid, app.curAdj)) {
                 if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
                     TAG, "Set " + app.pid + " " + app.processName +
                     " adj " + app.curAdj + ": " + app.adjType);
-                if (app.setAdj == ProcessList.SERVICE_ADJ
-                        && app.curAdj == ProcessList.SERVICE_B_ADJ) {
-                    // If a service is dropping to the B list, it has been running for
-                    // a while, take a PSS snapshot.
-                    requestPssLocked(app, now, false);
-                } else if (now > (app.lastPssTime+PSS_MAX_INTERVAL)) {
-                    requestPssLocked(app, now, true);
-                }
                 app.setAdj = app.curAdj;
             } else {
                 success = false;
@@ -14542,19 +14519,39 @@
                 }
             }
         }
+        if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
+                app.setProcState)) {
+            if (DEBUG_PSS) Slog.d(TAG, "Process state change from " + app.setProcState
+                    + " to " + app.curProcState + ": " + app);
+            app.lastStateTime = now;
+            app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
+                    mSleeping, now);
+        } else {
+            if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
+                    && now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) {
+                requestPssLocked(app, app.setProcState);
+                app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
+                        mSleeping, now);
+            }
+        }
         if (app.setProcState != app.curProcState) {
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
                     "Proc state change of " + app.processName
                     + " to " + app.curProcState);
             app.setProcState = app.curProcState;
-            app.procStateChanged = true;
             if (!doingAll) {
-                app.setProcessTrackerState(mProcessTracker.getMemFactorLocked(), now);
+                setProcessTrackerState(app, mProcessTracker.getMemFactorLocked(), now);
+            } else {
+                app.procStateChanged = true;
             }
         }
         return success;
     }
 
+    private final void setProcessTrackerState(ProcessRecord proc, int memFactor, long now) {
+        proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+    }
+
     private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
             ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
         if (app.thread == null) {
@@ -14835,7 +14832,8 @@
             for (int i=N-1; i>=0; i--) {
                 ProcessRecord app = mLruProcesses.get(i);
                 if (allChanged || app.procStateChanged) {
-                    app.setProcessTrackerState(trackerMemFactor, now);
+                    setProcessTrackerState(app, trackerMemFactor, now);
+                    app.procStateChanged = false;
                 }
                 if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                         && !app.killedBackground) {
@@ -14924,7 +14922,8 @@
             for (int i=N-1; i>=0; i--) {
                 ProcessRecord app = mLruProcesses.get(i);
                 if (allChanged || app.procStateChanged) {
-                    app.setProcessTrackerState(ProcessTracker.ADJ_MEM_FACTOR_NORMAL, now);
+                    setProcessTrackerState(app, trackerMemFactor, now);
+                    app.procStateChanged = false;
                 }
                 if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                         || app.systemNoUi) && app.pendingUiClean) {
@@ -14952,7 +14951,7 @@
         }
 
         if (allChanged) {
-            requestPssAllProcsLocked(now, false);
+            requestPssAllProcsLocked(now, false, mProcessTracker.isMemFactorLowered());
         }
 
         if (mProcessTracker.shouldWriteNowLocked(now)) {
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 925fb3f..1681c2d 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1783,7 +1783,6 @@
         if (allResumedActivitiesIdle()) {
             if (r != null) {
                 mService.scheduleAppGcsLocked();
-                mService.requestPssLocked(r.app, SystemClock.uptimeMillis(), false);
             }
 
             if (mLaunchingActivity.isHeld()) {
diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java
index 884f280..fb81b3a 100644
--- a/services/java/com/android/server/am/ProcessList.java
+++ b/services/java/com/android/server/am/ProcessList.java
@@ -278,6 +278,106 @@
         return (totalProcessLimit*2)/3;
     }
 
+    // The minimum amount of time after a state change it is safe ro collect PSS.
+    public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000;
+
+    // The maximum amount of time we want to go between PSS collections.
+    public static final int PSS_MAX_INTERVAL = 30*60*1000;
+
+    // The minimum amount of time between successive PSS requests for *all* processes.
+    public static final int PSS_ALL_INTERVAL = 10*60*1000;
+
+    // The minimum amount of time between successive PSS requests for a process.
+    private static final int PSS_SHORT_INTERVAL = 2*60*1000;
+
+    // The amount of time until PSS when a process first becomes top.
+    private static final int PSS_FIRST_TOP_INTERVAL = 15*1000;
+
+    // The amount of time until PSS when a process first becomes cached.
+    private static final int PSS_FIRST_CACHED_INTERVAL = 5*60*1000;
+
+    // The amount of time until PSS when an important process stays in the same state.
+    private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000;
+
+    // The amount of time until PSS when a service process stays in the same state.
+    private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000;
+
+    // The amount of time until PSS when a cached process stays in the same state.
+    private static final int PSS_SAME_CACHED_INTERVAL = 30*60*1000;
+
+    public static final int PROC_MEM_PERSISTENT = 0;
+    public static final int PROC_MEM_TOP = 1;
+    public static final int PROC_MEM_IMPORTANT = 2;
+    public static final int PROC_MEM_SERVICE = 3;
+    public static final int PROC_MEM_CACHED = 4;
+
+    private static final int[] sProcStateToProcMem = new int[] {
+        PROC_MEM_PERSISTENT,            // ActivityManager.PROCESS_STATE_PERSISTENT
+        PROC_MEM_PERSISTENT,            // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BACKUP
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PROC_MEM_SERVICE,               // ActivityManager.PROCESS_STATE_SERVICE
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_RECEIVER
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_HOME
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sFirstAwakePssTimes = new long[] {
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_FIRST_TOP_INTERVAL,         // ActivityManager.PROCESS_STATE_TOP
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HOME
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sSameAwakePssTimes = new long[] {
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_TOP
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    public static boolean procStatesDifferForMem(int procState1, int procState2) {
+        return sProcStateToProcMem[procState1] != sProcStateToProcMem[procState2];
+    }
+
+    public static long computeNextPssTime(int procState, boolean first, boolean sleeping,
+            long now) {
+        final long[] table = sleeping
+                ? (first
+                        ? sFirstAwakePssTimes
+                        : sSameAwakePssTimes)
+                : (first
+                        ? sFirstAwakePssTimes
+                        : sSameAwakePssTimes);
+        return now + table[procState];
+    }
+
     long getMemLevel(int adjustment) {
         for (int i=0; i<mOomAdj.length; i++) {
             if (adjustment <= mOomAdj[i]) {
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index cade2bb..eaa3691 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -62,7 +62,9 @@
     boolean starting;           // True if the process is being started
     long lastActivityTime;      // For managing the LRU list
     long lruWeight;             // Weight for ordering in LRU list
-    long lastPssTime;           // Last time we requested PSS data
+    long lastPssTime;           // Last time we retrieved PSS data
+    long nextPssTime;           // Next time we want to request PSS data
+    long lastStateTime;         // Last time setProcState changed
     int maxAdj;                 // Maximum OOM adjustment for this process
     int curRawAdj;              // Current OOM unlimited adjustment for this process
     int setRawAdj;              // Last set OOM unlimited adjustment for this process
@@ -75,6 +77,7 @@
     int curProcState = -1;      // Currently computed process state: ActivityManager.PROCESS_STATE_*
     int repProcState = -1;      // Last reported process state
     int setProcState = -1;      // Last set process state in process tracker
+    int pssProcState = -1;      // The proc state we are currently requesting pss for
     boolean serviceb;           // Process currently is on the service B list
     boolean keeping;            // Actively running code so don't kill due to that?
     boolean setIsForeground;    // Running foreground UI when last set?
@@ -223,10 +226,18 @@
                 pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
         pw.print(prefix); pw.print("curProcState="); pw.print(curProcState);
                 pw.print(" repProcState="); pw.print(repProcState);
-                pw.print(" setProcState="); pw.println(setProcState);
+                pw.print(" pssProcState="); pw.print(pssProcState);
+                pw.print(" setProcState="); pw.print(setProcState);
+                pw.print(" lastStateTime=");
+                TimeUtils.formatDuration(lastStateTime, now, pw);
+                pw.println();
         pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
                 pw.print(" lruSeq="); pw.print(lruSeq);
-                pw.print(" lastPssTime="); pw.println(lastPssTime);
+                pw.print(" lastPssTime=");
+                TimeUtils.formatDuration(lastPssTime, now, pw);
+                pw.print(" nextPssTime=");
+                TimeUtils.formatDuration(nextPssTime, now, pw);
+                pw.println();
         if (hasShownUi || pendingUiClean || hasAboveClient) {
             pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
                     pw.print(" pendingUiClean="); pw.print(pendingUiClean);
@@ -354,7 +365,7 @@
         curAdj = setAdj = -100;
         persistent = false;
         removed = false;
-        lastPssTime = SystemClock.uptimeMillis();
+        lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis();
     }
 
     public void setPid(int _pid) {
@@ -499,10 +510,6 @@
         }
     }
 
-    public void setProcessTrackerState(int memFactor, long now) {
-        baseProcessTracker.setState(repProcState, memFactor, now, pkgList);
-    }
-
     /*
      *  Delete all packages from list except the package indicated in info
      */
diff --git a/services/java/com/android/server/am/ProcessTracker.java b/services/java/com/android/server/am/ProcessTracker.java
index 6b98b03..e7865eb6 100644
--- a/services/java/com/android/server/am/ProcessTracker.java
+++ b/services/java/com/android/server/am/ProcessTracker.java
@@ -188,6 +188,8 @@
     State mState;
     boolean mCommitPending;
     boolean mShuttingDown;
+    int mLastMemOnlyState = -1;
+    boolean mMemFactorLowered;
 
     final ReentrantLock mWriteLock = new ReentrantLock();
 
@@ -1822,7 +1824,13 @@
         return ss;
     }
 
+    public boolean isMemFactorLowered() {
+        return mMemFactorLowered;
+    }
+
     public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
+        mMemFactorLowered = memFactor < mLastMemOnlyState;
+        mLastMemOnlyState = memFactor;
         if (screenOn) {
             memFactor += ADJ_SCREEN_ON;
         }
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 203bc86..7acf6ab 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -28,6 +29,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.IBinder.DeathRecipient;
 import android.print.IPrinterDiscoveryObserver;
 import android.print.PrintJobInfo;
 import android.print.PrintManager;
@@ -46,11 +48,11 @@
  * and unbinding from the remote implementation. Clients can call methods of
  * this class without worrying about when and how to bind/unbind.
  */
-final class RemotePrintService {
+final class RemotePrintService implements DeathRecipient {
 
     private static final String LOG_TAG = "RemotePrintService";
 
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
 
     private final Context mContext;
 
@@ -98,7 +100,16 @@
     }
 
     public void onAllPrintJobsHandled() {
-        mHandler.sendEmptyMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED);
+        mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+    }
+
+    @Override
+    public void binderDied() {
+        mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
+    }
+
+    private void handleBinderDied() {
+        ensureUnbound();
     }
 
     private void handleOnAllPrintJobsHandled() {
@@ -113,7 +124,7 @@
     }
 
     public void onRequestCancelPrintJob(PrintJobInfo printJob) {
-        mHandler.obtainMessage(MyHandler.MSG_REQUEST_CANCEL_PRINT_JOB,
+        mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB,
                 printJob).sendToTarget();
     }
 
@@ -126,7 +137,7 @@
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnRequestCancelPrintJob()");
             }
             try {
-                mPrintService.requestCancelPrintJob(printJob);
+                mPrintService.onRequestCancelPrintJob(printJob);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error canceling pring job.", re);
             }
@@ -134,7 +145,7 @@
     }
 
     public void onPrintJobQueued(PrintJobInfo printJob) {
-        mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
                 printJob).sendToTarget();
     }
 
@@ -161,7 +172,7 @@
     }
 
     public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) {
-        mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, observer).sendToTarget();
+        mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY, observer).sendToTarget();
     }
 
     private void handleOnStartPrinterDiscovery(final IPrinterDiscoveryObserver observer) {
@@ -179,7 +190,7 @@
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] onStartPrinterDiscovery()");
             }
             try {
-                mPrintService.startPrinterDiscovery(observer);
+                mPrintService.onStartPrinterDiscovery(observer);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
             }
@@ -187,7 +198,7 @@
     }
 
     public void onStopPrinterDiscovery() {
-        mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
+        mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY);
     }
 
     private void handleStopPrinterDiscovery() {
@@ -205,13 +216,40 @@
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] onStopPrinterDiscovery()");
             }
             try {
-                mPrintService.stopPrinterDiscovery();
+                mPrintService.onStopPrinterDiscovery();
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error announcing stop printer dicovery.", re);
             }
         }
     }
 
+    public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+        mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
+                printerIds).sendToTarget();
+    }
+
+    private void handleReqeustUpdatePritners(final List<PrinterId> printerIds) {
+        throwIfDestroyed();
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(new Runnable() {
+                @Override
+                public void run() {
+                    handleReqeustUpdatePritners(printerIds);
+                }
+            });
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] handleReqeustUpdatePritners()");
+            }
+            try {
+                mPrintService.onRequestUpdatePrinters(printerIds);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error requesting to update printers.", re);
+            }
+        }
+    }
+
     private boolean isBound() {
         return mPrintService != null;
     }
@@ -243,6 +281,7 @@
             } catch (RemoteException re) {
                 /* ignore */
             }
+            mPrintService.asBinder().unlinkToDeath(this, 0);
             mPrintService = null;
             mContext.unbindService(mServiceConnection);
         }
@@ -263,10 +302,16 @@
             mBinding = false;
             mPrintService = IPrintService.Stub.asInterface(service);
             try {
+                service.linkToDeath(RemotePrintService.this, 0);
+            } catch (RemoteException re) {
+                handleBinderDied();
+                return;
+            }
+            try {
                 mPrintService.setClient(mPrintServiceClient);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error setting client for: " + service, re);
-                handleDestroy();
+                handleBinderDied();
                 return;
             }
             final int pendingCommandCount = mPendingCommands.size();
@@ -283,47 +328,59 @@
     }
 
     private final class MyHandler extends Handler {
-        public static final int MSG_ALL_PRINT_JOBS_HANDLED = 1;
-        public static final int MSG_REQUEST_CANCEL_PRINT_JOB = 2;
-        public static final int MSG_PRINT_JOB_QUEUED = 3;
-        public static final int MSG_START_PRINTER_DISCOVERY = 4;
-        public static final int MSG_STOP_PRINTER_DISCOVERY = 5;
-        public static final int MSG_DESTROY = 6;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
+        public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
+        public static final int MSG_ON_START_PRINTER_DISCOVERY = 4;
+        public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 5;
+        public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 6;
+        public static final int MSG_DESTROY = 7;
+        public static final int MSG_BINDER_DIED = 8;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
         }
 
         @Override
+        @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
             switch (message.what) {
-                case MSG_ALL_PRINT_JOBS_HANDLED: {
+                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
                     handleOnAllPrintJobsHandled();
                 } break;
 
-                case MSG_REQUEST_CANCEL_PRINT_JOB: {
+                case MSG_ON_REQUEST_CANCEL_PRINT_JOB: {
                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
                     handleOnRequestCancelPrintJob(printJob);
                 } break;
 
-                case MSG_PRINT_JOB_QUEUED: {
+                case MSG_ON_PRINT_JOB_QUEUED: {
                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
                     handleOnPrintJobQueued(printJob);
                 } break;
 
-                case MSG_START_PRINTER_DISCOVERY: {
+                case MSG_ON_START_PRINTER_DISCOVERY: {
                     IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) message.obj;
                     handleOnStartPrinterDiscovery(new SecurePrinterDiscoveryObserver(
                             mComponentName, observer));
                 } break;
 
-                case MSG_STOP_PRINTER_DISCOVERY: {
+                case MSG_ON_REQUEST_UPDATE_PRINTERS: {
+                    List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+                    handleReqeustUpdatePritners(printerIds);
+                } break;
+
+                case MSG_ON_STOP_PRINTER_DISCOVERY: {
                     handleStopPrinterDiscovery();
                 } break;
 
                 case MSG_DESTROY: {
                     handleDestroy();
                 } break;
+
+                case MSG_BINDER_DIED: {
+                    handleBinderDied();
+                } break;
             }
         }
     }
@@ -420,22 +477,32 @@
         }
 
         @Override
-        public void addDiscoveredPrinters(List<PrinterInfo> printers) {
+        public void onPrintersAdded(List<PrinterInfo> printers) {
             throwIfPrinterIdsForPrinterInfoTampered(printers);
             try {
-                mDecoratedObsever.addDiscoveredPrinters(printers);
+                mDecoratedObsever.onPrintersAdded(printers);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error delegating to addDiscoveredPrinters", re);
+                Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
             }
         }
 
         @Override
-        public void removeDiscoveredPrinters(List<PrinterId> printerIds) {
+        public void onPrintersUpdated(List<PrinterInfo> printers) {
+            throwIfPrinterIdsForPrinterInfoTampered(printers);
+            try {
+                mDecoratedObsever.onPrintersUpdated(printers);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
+            }
+        }
+
+        @Override
+        public void onPrintersRemoved(List<PrinterId> printerIds) {
             throwIfPrinterIdsTampered(printerIds);
             try {
-                mDecoratedObsever.removeDiscoveredPrinters(printerIds);
+                mDecoratedObsever.onPrintersRemoved(printerIds);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error delegating to removeDiscoveredPrinters", re);
+                Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
             }
         }
 
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index 6445c2e..fe5b067 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -34,6 +35,7 @@
 import android.print.IPrinterDiscoveryObserver;
 import android.print.PrintAttributes;
 import android.print.PrintJobInfo;
+import android.print.PrinterId;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
@@ -54,7 +56,7 @@
 
     private static final String LOG_TAG = "RemotePrintSpooler";
 
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
 
     private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
 
@@ -88,11 +90,14 @@
 
     private boolean mDestroyed;
 
+    private boolean mCanUnbind;
+
     public static interface PrintSpoolerCallbacks {
         public void onPrintJobQueued(PrintJobInfo printJob);
         public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
         public void onStopPrinterDiscovery();
         public void onAllPrintJobsForServiceHandled(ComponentName printService);
+        public void onRequestUpdatePrinters(List<PrinterId> printerIds);
     }
 
     public RemotePrintSpooler(Context context, int userId,
@@ -111,6 +116,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
@@ -122,6 +128,11 @@
             Slog.e(LOG_TAG, "Error getting print jobs.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error getting print jobs.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return null;
     }
@@ -131,6 +142,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
@@ -142,6 +154,11 @@
             Slog.e(LOG_TAG, "Error creating print job.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error creating print job.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return null;
     }
@@ -150,6 +167,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()");
@@ -161,6 +179,11 @@
             Slog.e(LOG_TAG, "Error canceling print job.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error canceling print job.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return false;
     }
@@ -169,6 +192,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
@@ -183,6 +207,10 @@
             // We passed the file descriptor across and now the other
             // side is responsible to close it, so close the local copy.
             IoUtils.closeQuietly(fd);
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
     }
 
@@ -190,6 +218,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
@@ -201,6 +230,11 @@
             Slog.e(LOG_TAG, "Error getting print job info.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error getting print job info.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return null;
     }
@@ -209,6 +243,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
@@ -220,6 +255,11 @@
             Slog.e(LOG_TAG, "Error setting print job state.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error setting print job state.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return false;
     }
@@ -228,6 +268,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
@@ -239,6 +280,11 @@
             Slog.e(LOG_TAG, "Error setting print job tag.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error setting print job tag.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
         return false;
     }
@@ -247,6 +293,7 @@
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
+            mCanUnbind = false;
         }
         if (DEBUG) {
             Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
@@ -258,6 +305,11 @@
             Slog.e(LOG_TAG, "Error asking for active print job notification.", re);
         } catch (TimeoutException te) {
             Slog.e(LOG_TAG, "Error asking for active print job notification.", te);
+        } finally {
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
         }
     }
 
@@ -270,6 +322,7 @@
             throwIfDestroyedLocked();
             unbindLocked();
             mDestroyed = true;
+            mCanUnbind = false;
         }
     }
 
@@ -314,15 +367,29 @@
                 /* ignore */
             }
         }
+
+        mCanUnbind = true;
+        mLock.notifyAll();
     }
 
     private void unbindLocked() {
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+        while (true) {
+            if (mCanUnbind) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+                }
+                clearClientLocked();
+                mRemoteInstance = null;
+                mContext.unbindService(mServiceConnection);
+                return;
+            }
+            try {
+                mLock.wait();
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
         }
-        clearClientLocked();
-        mRemoteInstance = null;
-        mContext.unbindService(mServiceConnection);
+
     }
 
     private void setClientLocked() {
@@ -597,7 +664,7 @@
         }
 
         @Override
-        public void onStopPrinterDiscovery() throws RemoteException {
+        public void onStopPrinterDiscovery() {
             RemotePrintSpooler spooler = mWeakSpooler.get();
             if (spooler != null) {
                 final long identity = Binder.clearCallingIdentity();
@@ -608,5 +675,18 @@
                 }
             }
         }
+
+        @Override
+        public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+            RemotePrintSpooler spooler = mWeakSpooler.get();
+            if (spooler != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    spooler.mCallbacks.onRequestUpdatePrinters(printerIds);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
     }
 }
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index 5cef4d3..c41f9b0 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -23,6 +23,7 @@
 import android.content.pm.ResolveInfo;
 import android.print.IPrinterDiscoveryObserver;
 import android.print.PrintJobInfo;
+import android.print.PrinterId;
 import android.printservice.PrintServiceInfo;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -138,6 +139,21 @@
         }
     }
 
+    @Override
+    public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+        final RemotePrintService service;
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            if (mActiveServices.isEmpty()) {
+                return;
+            }
+            service = mActiveServices.get(printerIds.get(0).getService());
+        }
+        if (service != null) {
+            service.onRequestUpdatePrinters(printerIds);
+        }
+    }
+
     public void updateIfNeededLocked() {
         throwIfDestroyedLocked();
         if (readConfigurationLocked()) {
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java b/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
index 28e86bf..4ad6dc7 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
@@ -146,6 +146,65 @@
             }
         }
 
+        if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
+            Log.e("test", "Entry set hash codes differ: map=0x"
+                    + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.entrySet().hashCode()));
+            return false;
+        }
+
+        if (!map.entrySet().equals(array.entrySet())) {
+            Log.e("test", "Failed calling equals on map entry set against array set");
+            return false;
+        }
+
+        if (!array.entrySet().equals(map.entrySet())) {
+            Log.e("test", "Failed calling equals on array entry set against map set");
+            return false;
+        }
+
+        if (map.keySet().hashCode() != array.keySet().hashCode()) {
+            Log.e("test", "Key set hash codes differ: map=0x"
+                    + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.keySet().hashCode()));
+            return false;
+        }
+
+        if (!map.keySet().equals(array.keySet())) {
+            Log.e("test", "Failed calling equals on map key set against array set");
+            return false;
+        }
+
+        if (!array.keySet().equals(map.keySet())) {
+            Log.e("test", "Failed calling equals on array key set against map set");
+            return false;
+        }
+
+        if (!map.keySet().containsAll(array.keySet())) {
+            Log.e("test", "Failed map key set contains all of array key set");
+            return false;
+        }
+
+        if (!array.keySet().containsAll(map.keySet())) {
+            Log.e("test", "Failed array key set contains all of map key set");
+            return false;
+        }
+
+        if (!array.containsAll(map.keySet())) {
+            Log.e("test", "Failed array contains all of map key set");
+            return false;
+        }
+
+        if (!map.entrySet().containsAll(array.entrySet())) {
+            Log.e("test", "Failed map entry set contains all of array entry set");
+            return false;
+        }
+
+        if (!array.entrySet().containsAll(map.entrySet())) {
+            Log.e("test", "Failed array entry set contains all of map entry set");
+            return false;
+        }
+
         return true;
     }
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java
index 066e35c..fe4d602 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java
@@ -48,7 +48,7 @@
     }
 
     public static class RegionView extends FrameLayout {
-        private final Region mRegion = new Region();
+        private Region mRegion = new Region();
         private float mClipPosition = 0.0f;
 
         public RegionView(Context c) {
@@ -69,9 +69,7 @@
 
             canvas.save();
 
-            mRegion.setEmpty();
-            mRegion.op(0, 0, getWidth(), getHeight(),
-                    Region.Op.REPLACE);
+            mRegion.set(0, 0, getWidth(), getHeight());
             mRegion.op(getWidth() / 4, getHeight() / 4, 3 * getWidth() / 4, 3 * getHeight() / 4,
                     Region.Op.DIFFERENCE);