Merge "Refactor printing"
diff --git a/api/current.txt b/api/current.txt
index 234126b..d29580b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22500,6 +22500,7 @@
     method protected void onDisconnected();
     method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
     method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+    field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
     field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
     field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
     field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl
index 43b8c30..68c1dac 100644
--- a/core/java/android/print/ILayoutResultCallback.aidl
+++ b/core/java/android/print/ILayoutResultCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.print;
 
+import android.os.ICancellationSignal;
 import android.print.PrintDocumentInfo;
 
 /**
@@ -24,6 +25,8 @@
  * @hide
  */
 oneway interface ILayoutResultCallback {
+    void onLayoutStarted(ICancellationSignal cancellation, int sequence);
     void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
     void onLayoutFailed(CharSequence error, int sequence);
+    void onLayoutCanceled(int sequence);
 }
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 2b95c12..9d384fb 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -37,5 +37,4 @@
     void write(in PageRange[] pages, in ParcelFileDescriptor fd,
             IWriteResultCallback callback, int sequence);
     void finish();
-    void cancel();
 }
diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl
index 8281c4e..8fb33e1 100644
--- a/core/java/android/print/IWriteResultCallback.aidl
+++ b/core/java/android/print/IWriteResultCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.print;
 
+import android.os.ICancellationSignal;
 import android.print.PageRange;
 
 /**
@@ -24,6 +25,8 @@
  * @hide
  */
 oneway interface IWriteResultCallback {
+    void onWriteStarted(ICancellationSignal cancellation, int sequence);
     void onWriteFinished(in PageRange[] pages, int sequence);
     void onWriteFailed(CharSequence error, int sequence);
+    void onWriteCanceled(int sequence);
 }
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index c6254e0..2810d55 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -151,6 +151,105 @@
         mColorMode = colorMode;
     }
 
+    /**
+     * Gets whether this print attributes are in portrait orientation,
+     * which is the media size is in portrait and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return Whether this print attributes are in portrait.
+     *
+     * @hide
+     */
+    public boolean isPortrait() {
+        return mMediaSize.isPortrait();
+    }
+
+    /**
+     * Gets a new print attributes instance which is in portrait orientation,
+     * which is the media size is in portrait and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return New instance in portrait orientation if this one is in
+     * landscape, otherwise this instance.
+     *
+     * @hide
+     */
+    public PrintAttributes asPortrait() {
+        if (isPortrait()) {
+            return this;
+        }
+
+        PrintAttributes attributes = new PrintAttributes();
+
+        // Rotate the media size.
+        attributes.setMediaSize(getMediaSize().asPortrait());
+
+        // Rotate the resolution.
+        Resolution oldResolution = getResolution();
+        Resolution newResolution = new Resolution(
+                oldResolution.getId(),
+                oldResolution.getLabel(),
+                oldResolution.getVerticalDpi(),
+                oldResolution.getHorizontalDpi());
+        attributes.setResolution(newResolution);
+
+        // Rotate the physical margins.
+        Margins oldMinMargins = getMinMargins();
+        Margins newMinMargins = new Margins(
+                oldMinMargins.getBottomMils(),
+                oldMinMargins.getLeftMils(),
+                oldMinMargins.getTopMils(),
+                oldMinMargins.getRightMils());
+        attributes.setMinMargins(newMinMargins);
+
+        attributes.setColorMode(getColorMode());
+
+        return attributes;
+    }
+
+    /**
+     * Gets a new print attributes instance which is in landscape orientation,
+     * which is the media size is in landscape and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return New instance in landscape orientation if this one is in
+     * portrait, otherwise this instance.
+     *
+     * @hide
+     */
+    public PrintAttributes asLandscape() {
+        if (!isPortrait()) {
+            return this;
+        }
+
+        PrintAttributes attributes = new PrintAttributes();
+
+        // Rotate the media size.
+        attributes.setMediaSize(getMediaSize().asLandscape());
+
+        // Rotate the resolution.
+        Resolution oldResolution = getResolution();
+        Resolution newResolution = new Resolution(
+                oldResolution.getId(),
+                oldResolution.getLabel(),
+                oldResolution.getVerticalDpi(),
+                oldResolution.getHorizontalDpi());
+        attributes.setResolution(newResolution);
+
+        // Rotate the physical margins.
+        Margins oldMinMargins = getMinMargins();
+        Margins newMargins = new Margins(
+                oldMinMargins.getTopMils(),
+                oldMinMargins.getRightMils(),
+                oldMinMargins.getBottomMils(),
+                oldMinMargins.getLeftMils());
+        attributes.setMinMargins(newMargins);
+
+        attributes.setColorMode(getColorMode());
+
+        return attributes;
+    }
+
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         if (mMediaSize != null) {
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 811751d..9361286 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -24,6 +24,7 @@
 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;
@@ -41,6 +42,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -50,12 +52,12 @@
  * <p>
  * To obtain a handle to the print manager do the following:
  * </p>
- * 
+ *
  * <pre>
  * PrintManager printManager =
  *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
  * </pre>
- * 
+ *
  * <h3>Print mechanics</h3>
  * <p>
  * The key idea behind printing on the platform is that the content to be printed
@@ -344,7 +346,7 @@
         try {
             mService.cancelPrintJob(printJobId, mAppId, mUserId);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
+            Log.e(LOG_TAG, "Error canceling a print job: " + printJobId, re);
         }
     }
 
@@ -505,30 +507,17 @@
 
     private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
             implements ActivityLifecycleCallbacks {
-
         private final Object mLock = new Object();
 
-        private CancellationSignal mLayoutOrWriteCancellation;
+        private Activity mActivity; // Strong reference OK - cleared in destroy
 
-        private Activity mActivity; // Strong reference OK - cleared in finish()
+        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy
 
-        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
+        private Handler mHandler; // Strong reference OK - cleared in destroy
 
-        private Handler mHandler; // Strong reference OK - cleared in finish()
+        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy
 
-        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
-
-        private LayoutSpec mLastLayoutSpec;
-
-        private WriteSpec mLastWriteSpec;
-
-        private boolean mStartReqeusted;
-        private boolean mStarted;
-
-        private boolean mFinishRequested;
-        private boolean mFinished;
-
-        private boolean mDestroyed;
+        private DestroyableCallback mPendingCallback;
 
         public PrintDocumentAdapterDelegate(Activity activity,
                 PrintDocumentAdapter documentAdapter) {
@@ -542,11 +531,10 @@
         public void setObserver(IPrintDocumentAdapterObserver observer) {
             final boolean destroyed;
             synchronized (mLock) {
-                if (!mDestroyed) {
-                    mObserver = observer;
-                }
-                destroyed = mDestroyed;
+                mObserver = observer;
+                destroyed = isDestroyedLocked();
             }
+
             if (destroyed) {
                 try {
                     observer.onDestroy();
@@ -559,126 +547,89 @@
         @Override
         public void start() {
             synchronized (mLock) {
-                // Started called or finish called or destroyed - nothing to do.
-                if (mStartReqeusted || mFinishRequested || mDestroyed) {
-                    return;
+                // If destroyed the handler is null.
+                if (!isDestroyedLocked()) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_START,
+                            mDocumentAdapter).sendToTarget();
                 }
-
-                mStartReqeusted = true;
-
-                doPendingWorkLocked();
             }
         }
 
         @Override
         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
                 ILayoutResultCallback callback, Bundle metadata, int sequence) {
-            final boolean destroyed;
-            synchronized (mLock) {
-                destroyed = mDestroyed;
-                // If start called and not finished called and not destroyed - do some work.
-                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
-                    // Layout cancels write and overrides layout.
-                    if (mLastWriteSpec != null) {
-                        IoUtils.closeQuietly(mLastWriteSpec.fd);
-                        mLastWriteSpec = null;
-                    }
 
-                    mLastLayoutSpec = new LayoutSpec();
-                    mLastLayoutSpec.callback = callback;
-                    mLastLayoutSpec.oldAttributes = oldAttributes;
-                    mLastLayoutSpec.newAttributes = newAttributes;
-                    mLastLayoutSpec.metadata = metadata;
-                    mLastLayoutSpec.sequence = sequence;
-
-                    // Cancel the previous cancellable operation.When the
-                    // cancellation completes we will do the pending work.
-                    if (cancelPreviousCancellableOperationLocked()) {
-                        return;
-                    }
-
-                    doPendingWorkLocked();
-                }
+            ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
+            try {
+                callback.onLayoutStarted(cancellationTransport, sequence);
+            } catch (RemoteException re) {
+                // The spooler is dead - can't recover.
+                Log.e(LOG_TAG, "Error notifying for layout start", re);
+                return;
             }
-            if (destroyed) {
-                try {
-                    callback.onLayoutFailed(null, sequence);
-                } catch (RemoteException re) {
-                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
+
+            synchronized (mLock) {
+                // If destroyed the handler is null.
+                if (isDestroyedLocked()) {
+                    return;
                 }
+
+                CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+                        cancellationTransport);
+
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = mDocumentAdapter;
+                args.arg2 = oldAttributes;
+                args.arg3 = newAttributes;
+                args.arg4 = cancellationSignal;
+                args.arg5 = new MyLayoutResultCallback(callback, sequence);
+                args.arg6 = metadata;
+
+                mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget();
             }
         }
 
         @Override
         public void write(PageRange[] pages, ParcelFileDescriptor fd,
                 IWriteResultCallback callback, int sequence) {
-            final boolean destroyed;
-            synchronized (mLock) {
-                destroyed = mDestroyed;
-                // If start called and not finished called and not destroyed - do some work.
-                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
-                    // Write cancels previous writes.
-                    if (mLastWriteSpec != null) {
-                        IoUtils.closeQuietly(mLastWriteSpec.fd);
-                        mLastWriteSpec = null;
-                    }
 
-                    mLastWriteSpec = new WriteSpec();
-                    mLastWriteSpec.callback = callback;
-                    mLastWriteSpec.pages = pages;
-                    mLastWriteSpec.fd = fd;
-                    mLastWriteSpec.sequence = sequence;
-
-                    // Cancel the previous cancellable operation.When the
-                    // cancellation completes we will do the pending work.
-                    if (cancelPreviousCancellableOperationLocked()) {
-                        return;
-                    }
-
-                    doPendingWorkLocked();
-                }
+            ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
+            try {
+                callback.onWriteStarted(cancellationTransport, sequence);
+            } catch (RemoteException re) {
+                // The spooler is dead - can't recover.
+                Log.e(LOG_TAG, "Error notifying for write start", re);
+                return;
             }
-            if (destroyed) {
-                try {
-                    callback.onWriteFailed(null, sequence);
-                } catch (RemoteException re) {
-                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
+
+            synchronized (mLock) {
+                // If destroyed the handler is null.
+                if (isDestroyedLocked()) {
+                    return;
                 }
+
+                CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+                        cancellationTransport);
+
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = mDocumentAdapter;
+                args.arg2 = pages;
+                args.arg3 = fd;
+                args.arg4 = cancellationSignal;
+                args.arg5 = new MyWriteResultCallback(callback, fd, sequence);
+
+                mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget();
             }
         }
 
         @Override
         public void finish() {
             synchronized (mLock) {
-                // Start not called or finish called or destroyed - nothing to do.
-                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
-                    return;
+                // If destroyed the handler is null.
+                if (!isDestroyedLocked()) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_FINISH,
+                            mDocumentAdapter).sendToTarget();
                 }
-
-                mFinishRequested = true;
-
-                // When the current write or layout complete we
-                // will do the pending work.
-                if (mLastLayoutSpec != null || mLastWriteSpec != null) {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "Waiting for current operation");
-                    }
-                    return;
-                }
-
-                doPendingWorkLocked();
-            }
-        }
-
-        @Override
-        public void cancel() {
-            // Start not called or finish called or destroyed - nothing to do.
-            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
-                return;
-            }
-            // Request cancellation of pending work if needed.
-            synchronized (mLock) {
-                cancelPreviousCancellableOperationLocked();
             }
         }
 
@@ -719,20 +670,14 @@
             // Note the the spooler has a death recipient that observes if
             // this process gets killed so we cover the case of onDestroy not
             // being called due to this process being killed to reclaim memory.
-            final IPrintDocumentAdapterObserver observer;
+            IPrintDocumentAdapterObserver observer = null;
             synchronized (mLock) {
                 if (activity == mActivity) {
-                    mDestroyed = true;
                     observer = mObserver;
-                    clearLocked();
-                } else {
-                    observer = null;
-                    activity = null;
+                    destroyLocked();
                 }
             }
             if (observer != null) {
-                activity.getApplication().unregisterActivityLifecycleCallbacks(
-                        PrintDocumentAdapterDelegate.this);
                 try {
                     observer.onDestroy();
                 } catch (RemoteException re) {
@@ -741,67 +686,39 @@
             }
         }
 
-        private boolean isFinished() {
-            return mDocumentAdapter == null;
+        private boolean isDestroyedLocked() {
+            return (mActivity == null);
         }
 
-        private void clearLocked() {
+        private void destroyLocked() {
+            mActivity.getApplication().unregisterActivityLifecycleCallbacks(
+                    PrintDocumentAdapterDelegate.this);
             mActivity = null;
+
             mDocumentAdapter = null;
+
+            // This method is only called from the main thread, so
+            // clearing the messages guarantees that any time a
+            // message is handled we are not in a destroyed state.
+            mHandler.removeMessages(MyHandler.MSG_ON_START);
+            mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
+            mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
+            mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
             mHandler = null;
-            mLayoutOrWriteCancellation = null;
-            mLastLayoutSpec = null;
-            if (mLastWriteSpec != null) {
-                IoUtils.closeQuietly(mLastWriteSpec.fd);
-                mLastWriteSpec = null;
+
+            mObserver = null;
+
+            if (mPendingCallback != null) {
+                mPendingCallback.destroy();
+                mPendingCallback = null;
             }
         }
 
-        private boolean cancelPreviousCancellableOperationLocked() {
-            if (mLayoutOrWriteCancellation != null) {
-                mLayoutOrWriteCancellation.cancel();
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Cancelling previous operation");
-                }
-                return true;
-            }
-            return false;
-        }
-
-        private void doPendingWorkLocked() {
-            if (mStartReqeusted && !mStarted) {
-                mStarted = true;
-                mHandler.sendEmptyMessage(MyHandler.MSG_START);
-            } else if (mLastLayoutSpec != null) {
-                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
-            } else if (mLastWriteSpec != null) {
-                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
-            } else if (mFinishRequested && !mFinished) {
-                mFinished = true;
-                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
-            }
-        }
-
-        private class LayoutSpec {
-            ILayoutResultCallback callback;
-            PrintAttributes oldAttributes;
-            PrintAttributes newAttributes;
-            Bundle metadata;
-            int sequence;
-        }
-
-        private class WriteSpec {
-            IWriteResultCallback callback;
-            PageRange[] pages;
-            ParcelFileDescriptor fd;
-            int sequence;
-        }
-
         private final class MyHandler extends Handler {
-            public static final int MSG_START = 1;
-            public static final int MSG_LAYOUT = 2;
-            public static final int MSG_WRITE = 3;
-            public static final int MSG_FINISH = 4;
+            public static final int MSG_ON_START = 1;
+            public static final int MSG_ON_LAYOUT = 2;
+            public static final int MSG_ON_WRITE = 3;
+            public static final int MSG_ON_FINISH = 4;
 
             public MyHandler(Looper looper) {
                 super(looper, null, true);
@@ -809,84 +726,71 @@
 
             @Override
             public void handleMessage(Message message) {
-                if (isFinished()) {
-                    return;
-                }
                 switch (message.what) {
-                    case MSG_START: {
-                        final PrintDocumentAdapter adapter;
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                        }
-                        if (adapter != null) {
-                            adapter.onStart();
-                        }
-                    } break;
-
-                    case MSG_LAYOUT: {
-                        final PrintDocumentAdapter adapter;
-                        final CancellationSignal cancellation;
-                        final LayoutSpec layoutSpec;
-
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            layoutSpec = mLastLayoutSpec;
-                            mLastLayoutSpec = null;
-                            cancellation = new CancellationSignal();
-                            mLayoutOrWriteCancellation = cancellation;
-                        }
-
-                        if (layoutSpec != null && adapter != null) {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "Performing layout");
-                            }
-                            adapter.onLayout(layoutSpec.oldAttributes,
-                                    layoutSpec.newAttributes, cancellation,
-                                    new MyLayoutResultCallback(layoutSpec.callback,
-                                            layoutSpec.sequence), layoutSpec.metadata);
-                        }
-                    } break;
-
-                    case MSG_WRITE: {
-                        final PrintDocumentAdapter adapter;
-                        final CancellationSignal cancellation;
-                        final WriteSpec writeSpec;
-
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            writeSpec = mLastWriteSpec;
-                            mLastWriteSpec = null;
-                            cancellation = new CancellationSignal();
-                            mLayoutOrWriteCancellation = cancellation;
-                        }
-
-                        if (writeSpec != null && adapter != null) {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "Performing write");
-                            }
-                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
-                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
-                                            writeSpec.fd, writeSpec.sequence));
-                        }
-                    } break;
-
-                    case MSG_FINISH: {
+                    case MSG_ON_START: {
                         if (DEBUG) {
-                            Log.i(LOG_TAG, "Performing finish");
+                            Log.i(LOG_TAG, "onStart()");
                         }
-                        final PrintDocumentAdapter adapter;
-                        final Activity activity;
+
+                        ((PrintDocumentAdapter) message.obj).onStart();
+                    } break;
+
+                    case MSG_ON_LAYOUT: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
+                        PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
+                        PrintAttributes newAttributes = (PrintAttributes) args.arg3;
+                        CancellationSignal cancellation = (CancellationSignal) args.arg4;
+                        LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
+                        Bundle metadata = (Bundle) args.arg6;
+                        args.recycle();
+
+                        if (DEBUG) {
+                            StringBuilder builder = new StringBuilder();
+                            builder.append("PrintDocumentAdapter#onLayout() {\n");
+                            builder.append("\n  oldAttributes:").append(oldAttributes);
+                            builder.append("\n  newAttributes:").append(newAttributes);
+                            builder.append("\n  preview:").append(metadata.getBoolean(
+                                    PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
+                            builder.append("\n}");
+                            Log.i(LOG_TAG, builder.toString());
+                        }
+
+                        adapter.onLayout(oldAttributes, newAttributes, cancellation,
+                                callback, metadata);
+                    } break;
+
+                    case MSG_ON_WRITE: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
+                        PageRange[] pages = (PageRange[]) args.arg2;
+                        ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
+                        CancellationSignal cancellation = (CancellationSignal) args.arg4;
+                        WriteResultCallback callback = (WriteResultCallback) args.arg5;
+                        args.recycle();
+
+                        if (DEBUG) {
+                            StringBuilder builder = new StringBuilder();
+                            builder.append("PrintDocumentAdapter#onWrite() {\n");
+                            builder.append("\n  pages:").append(Arrays.toString(pages));
+                            builder.append("\n}");
+                            Log.i(LOG_TAG, builder.toString());
+                        }
+
+                        adapter.onWrite(pages, fd, cancellation, callback);
+                    } break;
+
+                    case MSG_ON_FINISH: {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "onFinish()");
+                        }
+
+                        ((PrintDocumentAdapter) message.obj).onFinish();
+
+                        // Done printing, so destroy this instance as it
+                        // should not be used anymore.
                         synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            activity = mActivity;
-                            clearLocked();
-                        }
-                        if (adapter != null) {
-                            adapter.onFinish();
-                        }
-                        if (activity != null) {
-                            activity.getApplication().unregisterActivityLifecycleCallbacks(
-                                    PrintDocumentAdapterDelegate.this);
+                            destroyLocked();
                         }
                     } break;
 
@@ -898,7 +802,12 @@
             }
         }
 
-        private final class MyLayoutResultCallback extends LayoutResultCallback {
+        private interface DestroyableCallback {
+            public void destroy();
+        }
+
+        private final class MyLayoutResultCallback extends LayoutResultCallback
+                implements DestroyableCallback {
             private ILayoutResultCallback mCallback;
             private final int mSequence;
 
@@ -910,25 +819,31 @@
 
             @Override
             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-                if (info == null) {
-                    throw new NullPointerException("document info cannot be null");
-                }
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    if (info == null) {
+                        throw new NullPointerException("document info cannot be null");
+                    }
+
                     try {
                         callback.onLayoutFinished(info, changed, mSequence);
                     } catch (RemoteException re) {
                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
                     }
+                } finally {
+                    destroy();
                 }
             }
 
@@ -936,46 +851,64 @@
             public void onLayoutFailed(CharSequence error) {
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
-                    try {
-                        callback.onLayoutFailed(error, mSequence);
-                    } catch (RemoteException re) {
-                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
-                    }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onLayoutFailed(error, mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
             @Override
             public void onLayoutCancelled() {
+                final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
-                    clearLocked();
+                    callback = mCallback;
+                }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onLayoutCanceled(mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
-            private void clearLocked() {
-                mLayoutOrWriteCancellation = null;
-                mCallback = null;
-                doPendingWorkLocked();
+            @Override
+            public void destroy() {
+                synchronized (mLock) {
+                    mCallback = null;
+                    mPendingCallback = null;
+                }
             }
         }
 
-        private final class MyWriteResultCallback extends WriteResultCallback {
+        private final class MyWriteResultCallback extends WriteResultCallback
+                implements DestroyableCallback {
             private ParcelFileDescriptor mFd;
-            private int mSequence;
             private IWriteResultCallback mCallback;
+            private final int mSequence;
 
             public MyWriteResultCallback(IWriteResultCallback callback,
                     ParcelFileDescriptor fd, int sequence) {
@@ -988,26 +921,32 @@
             public void onWriteFinished(PageRange[] pages) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (pages == null) {
-                    throw new IllegalArgumentException("pages cannot be null");
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
                 }
-                if (pages.length == 0) {
-                    throw new IllegalArgumentException("pages cannot be empty");
-                }
-                if (callback != null) {
+
+                try {
+                    if (pages == null) {
+                        throw new IllegalArgumentException("pages cannot be null");
+                    }
+                    if (pages.length == 0) {
+                        throw new IllegalArgumentException("pages cannot be empty");
+                    }
+
                     try {
                         callback.onWriteFinished(pages, mSequence);
                     } catch (RemoteException re) {
                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
                     }
+                } finally {
+                    destroy();
                 }
             }
 
@@ -1015,41 +954,58 @@
             public void onWriteFailed(CharSequence error) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
-                    try {
-                        callback.onWriteFailed(error, mSequence);
-                    } catch (RemoteException re) {
-                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
-                    }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onWriteFailed(error, mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
             @Override
             public void onWriteCancelled() {
+                final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
-                    clearLocked();
+                    callback = mCallback;
+                }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onWriteCanceled(mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
+                } finally {
+                    destroy();
                 }
             }
 
-            private void clearLocked() {
-                mLayoutOrWriteCancellation = null;
-                IoUtils.closeQuietly(mFd);
-                mCallback = null;
-                mFd = null;
-                doPendingWorkLocked();
+            @Override
+            public void destroy() {
+                synchronized (mLock) {
+                    IoUtils.closeQuietly(mFd);
+                    mCallback = null;
+                    mFd = null;
+                    mPendingCallback = null;
+                }
             }
         }
     }
diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index d32b71b..abb441b 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -72,9 +72,9 @@
         }
     }
 
-    public final void startPrinterDisovery(List<PrinterId> priorityList) {
+    public final void startPrinterDiscovery(List<PrinterId> priorityList) {
         if (isDestroyed()) {
-            Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed");
+            Log.w(LOG_TAG, "Ignoring start printers discovery - session destroyed");
             return;
         }
         if (!mIsPrinterDiscoveryStarted) {
@@ -122,7 +122,7 @@
         try {
             mPrintManager.stopPrinterStateTracking(printerId, mUserId);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error stoping printer state tracking", re);
+            Log.e(LOG_TAG, "Error stopping printer state tracking", re);
         }
     }
 
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index eb0ac2e..1557ab0 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -201,9 +201,9 @@
      * should build another one using the {@link PrintJobInfo.Builder} class. You
      * can specify any standard properties and add advanced, printer specific,
      * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
-     * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link
+     * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link
      * PrintJobInfo.Builder#putAdvancedOption(String, int)
-     * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options
+     * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options
      * are not interpreted by the system, they will not be visible to applications,
      * and can only be accessed by your print service via {@link
      * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
@@ -212,14 +212,26 @@
      * <p>
      * If the advanced print options activity offers changes to the standard print
      * options, you can get the current {@link android.print.PrinterInfo} using the
-     * "android.intent.extra.print.EXTRA_PRINTER_INFO" extra which will allow you to
-     * present the user with UI options supported by the current printer. For example,
-     * if the current printer does not support a give media size, you should not
-     * offer it in the advanced print options dialog.
+     * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user
+     * with UI options supported by the current printer. For example, if the current
+     * printer does not support a given media size, you should not offer it in the
+     * advanced print options UI.
      * </p>
+     *
+     * @see #EXTRA_PRINTER_INFO
      */
     public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
 
+    /**
+     * If you declared an optional activity with advanced print options via the
+     * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+     * attribute, this extra is used to pass in the currently selected printer's
+     * {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
+     *
+     * @see #EXTRA_PRINT_JOB_INFO
+     */
+    public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
+
     private Handler mHandler;
 
     private IPrintServiceClient mClient;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e97a768..06869fd 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7648,7 +7648,7 @@
      * notification is at at most once every
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
      * to avoid unnecessary load to the system. Also once a view has a pending
-     * notifucation this method is a NOP until the notification has been sent.
+     * notification this method is a NOP until the notification has been sent.
      *
      * @hide
      */
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 7edf4cc..c977997 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -45,6 +45,7 @@
     public Object arg3;
     public Object arg4;
     public Object arg5;
+    public Object arg6;
     public int argi1;
     public int argi2;
     public int argi3;
@@ -95,6 +96,7 @@
         arg3 = null;
         arg4 = null;
         arg5 = null;
+        arg6 = null;
         argi1 = 0;
         argi2 = 0;
         argi3 = 0;
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index 9e7b969..96592b4 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -24,4 +24,6 @@
 
 LOCAL_JAVA_LIBRARIES := framework-base
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 include $(BUILD_PACKAGE)
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index e1d0aec..4c0bbb8 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -42,23 +42,23 @@
     <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"/>
 
     <application
-            android:allowClearUserData="true"
-            android:label="@string/app_label"
-            android:allowBackup= "false"
-            android:supportsRtl="true"
-            android:icon="@*android:drawable/ic_print">
+        android:allowClearUserData="true"
+        android:label="@string/app_label"
+        android:allowBackup= "false"
+        android:supportsRtl="true"
+        android:icon="@*android:drawable/ic_print">
 
         <service
-            android:name=".PrintSpoolerService"
+            android:name=".model.PrintSpoolerService"
             android:exported="true"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE">
         </service>
 
         <activity
-            android:name=".PrintJobConfigActivity"
+            android:name=".ui.PrintActivity"
             android:configChanges="orientation|screenSize"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
-            android:theme="@style/PrintJobConfigActivityTheme">
+            android:theme="@android:style/Theme.DeviceDefault.NoActionBar">
             <intent-filter>
                 <action android:name="android.print.PRINT_DIALOG" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -67,7 +67,7 @@
         </activity>
 
         <activity
-            android:name=".SelectPrinterActivity"
+            android:name=".ui.SelectPrinterActivity"
             android:label="@string/all_printers_label"
             android:theme="@style/SelectPrinterActivityTheme"
             android:exported="false">
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..d2e5408
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..f4c4b0c
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
new file mode 100644
index 0000000..88e8495
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..3220eea
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..5530f52
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
new file mode 100644
index 0000000..3a37b27
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..f0074275
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..43debb3
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
new file mode 100644
index 0000000..b2ed8cd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..39bc2ba
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..664f3f2
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..fe9c539
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..18d075c
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..b0c7d51
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true">
+
+    <item
+        android:state_checked="true">
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item
+        android:state_pressed="true">
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlNormal">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
new file mode 100644
index 0000000..b809c25
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true">
+
+    <item
+        android:state_checked="true">
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item
+        android:state_pressed="true">
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlNormal">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/print_button.xml b/packages/PrintSpooler/res/drawable/print_button.xml
new file mode 100644
index 0000000..d4b6a82
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/print_button.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:tint="@color/print_button_tint_color"
+    android:pinned="true">
+    <item
+        android:drawable="@drawable/print_button_background">
+    </item>
+</ripple>
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
new file mode 100644
index 0000000..9715322
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -0,0 +1,389 @@
+<?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.
+-->
+
+<com.android.printspooler.widget.ContentView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:printspooler="http://schemas.android.com/apk/res/com.android.printspooler"
+    android:id="@+id/options_content"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:visibility="invisible"
+    android:background="?android:attr/colorForeground">
+
+    <FrameLayout
+        android:id="@+id/static_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dip"
+        android:background="?android:attr/colorForegroundInverse">
+
+        <!-- Destination -->
+
+        <Spinner
+            android:id="@+id/destination_spinner"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:dropDownWidth="wrap_content"
+            android:minHeight="?android:attr/listPreferredItemHeightSmall">
+        </Spinner>
+
+    </FrameLayout>
+
+    <!-- Summary -->
+
+    <LinearLayout
+        android:id="@+id/summary_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="16dip"
+        android:paddingEnd="16dip"
+        android:orientation="horizontal"
+        android:background="?android:attr/colorForegroundInverse">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="12dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:labelFor="@+id/copies_count_summary"
+            android:text="@string/label_copies_summary">
+        </TextView>
+
+        <TextView
+            android:id="@+id/copies_count_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="16dip"
+            android:textAppearance="?android:attr/textAppearanceMedium">
+        </TextView>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="32dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:labelFor="@+id/paper_size_summary"
+            android:text="@string/label_paper_size_summary">
+        </TextView>
+
+        <TextView
+            android:id="@+id/paper_size_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="16dip"
+            android:textAppearance="?android:attr/textAppearanceMedium">
+        </TextView>
+
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/dynamic_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="16dip">
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:id="@+id/draggable_content"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <com.android.printspooler.widget.PrintOptionsLayout
+                    android:id="@+id/options_container"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:background="?android:attr/colorForegroundInverse"
+                    printspooler:columnCount="@integer/print_option_column_count">
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Copies -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/copies_edittext"
+                            android:text="@string/label_copies">
+                        </TextView>
+
+                        <view
+                            class="com.android.printspooler.widget.FirstFocusableEditText"
+                            android:id="@+id/copies_edittext"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="?android:attr/editTextStyle"
+                            android:inputType="numberDecimal">
+                        </view>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Paper size -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/paper_size_spinner"
+                            android:text="@string/label_paper_size">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/paper_size_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Color -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/color_spinner"
+                            android:text="@string/label_color">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/color_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Orientation -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/orientation_spinner"
+                            android:text="@string/label_orientation">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/orientation_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Range options -->
+
+                        <TextView
+                            android:id="@+id/range_options_title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/range_options_spinner"
+                            android:text="@string/page_count_unknown">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/range_options_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Pages -->
+
+                        <TextView
+                            android:id="@+id/page_range_title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:text="@string/pages_range_example"
+                            android:labelFor="@+id/page_range_edittext"
+                            android:textAllCaps="false"
+                            android:visibility="visible">
+                        </TextView>
+
+                        <view
+                            class="com.android.printspooler.widget.FirstFocusableEditText"
+                            android:id="@+id/page_range_edittext"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="bottom|fill_horizontal"
+                            style="@style/PrintOptionEditTextStyle"
+                            android:visibility="visible"
+                            android:inputType="textNoSuggestions">
+                        </view>
+
+                    </LinearLayout>
+
+                </com.android.printspooler.widget.PrintOptionsLayout>
+
+                <!-- More options -->
+
+                <LinearLayout
+                    android:id="@+id/more_options_container"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="28dip"
+                    android:paddingEnd="28dip"
+                    android:orientation="vertical"
+                    android:visibility="visible"
+                    android:background="?android:attr/colorForegroundInverse">
+
+                    <ImageView
+                        android:layout_width="fill_parent"
+                        android:layout_height="1dip"
+                        android:layout_gravity="fill_horizontal"
+                        android:background="?android:attr/colorControlNormal"
+                        android:contentDescription="@null">
+                    </ImageView>
+
+                    <Button
+                        android:id="@+id/more_options_button"
+                        style="?android:attr/borderlessButtonStyle"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="fill_horizontal"
+                        android:text="@string/more_options_button"
+                        android:gravity="start|center_vertical"
+                        android:textAllCaps="false">
+                    </Button>
+
+                    <ImageView
+                        android:layout_width="fill_parent"
+                        android:layout_height="1dip"
+                        android:layout_gravity="fill_horizontal"
+                        android:background="?android:attr/colorControlNormal"
+                        android:contentDescription="@null">
+                    </ImageView>
+
+                </LinearLayout>
+
+            </LinearLayout>
+
+            <!-- Expand/collapse handle -->
+
+            <FrameLayout
+                android:id="@+id/expand_collapse_handle"
+                android:layout_width="fill_parent"
+                android:layout_height="?android:attr/listPreferredItemHeightSmall"
+                android:layout_marginBottom="28dip"
+                android:background="?android:attr/colorForegroundInverse"
+                android:elevation="12dip">
+
+                <ImageButton
+                    android:id="@+id/expand_collapse_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:background="@drawable/ic_expand_more">
+                </ImageButton>
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <!-- Print button -->
+
+        <ImageButton
+            android:id="@+id/print_button"
+            style="?android:attr/buttonStyleSmall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end|bottom"
+            android:layout_marginEnd="16dip"
+            android:elevation="12dip"
+            android:background="@drawable/print_button"
+            android:src="@*android:drawable/ic_print">
+        </ImageButton>
+
+    </FrameLayout>
+
+
+    <FrameLayout
+        android:id="@+id/embedded_content_container"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:animateLayoutChanges="true">
+    </FrameLayout>
+
+</com.android.printspooler.widget.ContentView>
diff --git a/packages/PrintSpooler/res/layout/print_error_fragment.xml b/packages/PrintSpooler/res/layout/print_error_fragment.xml
new file mode 100644
index 0000000..dc44339
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_error_fragment.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 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="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dip"
+        android:src="@drawable/ic_grayedout_printer"
+        android:contentDescription="@null">
+    </ImageView>
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/print_error_default_message"
+        android:textAppearance="?android:attr/textAppearanceLargeInverse">
+    </TextView>
+
+    <Button
+        android:id="@+id/action_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/print_error_retry">
+    </Button>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
deleted file mode 100644
index 3303ef1..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<com.android.printspooler.PrintDialogFrame xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content">
-    <FrameLayout
-        android:id="@+id/content_container"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:background="@color/container_background">
-    </FrameLayout>
-</com.android.printspooler.PrintDialogFrame>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
deleted file mode 100644
index e50a7af..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
+++ /dev/null
@@ -1,282 +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.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:scrollbars="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <!-- Destination -->
-
-        <Spinner
-            android:id="@+id/destination_spinner"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            android:layout_marginTop="24dip"
-            android:layout_marginStart="24dip"
-            android:layout_marginEnd="24dip"
-            android:minHeight="?android:attr/listPreferredItemHeightSmall">
-        </Spinner>
-
-        <LinearLayout
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="24dip"
-            android:orientation="horizontal"
-            android:baselineAligned="false">
-
-            <LinearLayout
-                android:layout_width="0dip"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:orientation="vertical">
-
-                <!-- Copies -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:layout_marginEnd="6dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/copies_edittext"
-                    android:text="@string/label_copies">
-                </TextView>
-
-                <view
-                    class="com.android.printspooler.PrintJobConfigActivity$CustomEditText"
-                    android:id="@+id/copies_edittext"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionEditTextStyle"
-                    android:inputType="numberDecimal">
-                </view>
-
-                <!-- Color -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:layout_marginEnd="6dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/color_spinner"
-                    android:text="@string/label_color">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/color_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Range options -->
-
-                <TextView
-                    android:id="@+id/range_options_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/range_options_spinner"
-                    android:text="@string/page_count_unknown">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/range_options_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="0dip"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:orientation="vertical">
-
-                <!-- Paper size -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="18dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/paper_size_spinner"
-                    android:text="@string/label_paper_size">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/paper_size_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Orientation -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="18dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/orientation_spinner"
-                    android:text="@string/label_orientation">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/orientation_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Pages -->
-
-               <TextView
-                    android:id="@+id/page_range_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="12dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:text="@string/pages_range_example"
-                    android:labelFor="@+id/page_range_edittext"
-                    android:textAllCaps="false">
-                </TextView>
-
-                <view
-                    class="com.android.printspooler.PrintJobConfigActivity$CustomEditText"
-                    android:id="@+id/page_range_edittext"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    android:layout_gravity="bottom|fill_horizontal"
-                    style="@style/PrintOptionEditTextStyle"
-                    android:visibility="gone"
-                    android:inputType="textNoSuggestions">
-                </view>
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-        <!-- Advanced settings button -->
-
-        <LinearLayout
-           android:id="@+id/advanced_settings_container"
-           android:layout_width="fill_parent"
-           android:layout_height="wrap_content"
-           android:orientation="vertical"
-           android:visibility="gone">
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:layout_gravity="fill_horizontal"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-            <Button
-                android:id="@+id/advanced_settings_button"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:layout_gravity="fill_horizontal"
-                android:text="@string/advanced_settings_button"
-                android:gravity="start|center_vertical"
-                android:textSize="16sp"
-                android:textColor="@color/item_text_color">
-            </Button>
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_gravity="fill_horizontal"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-        </LinearLayout>
-
-        <!-- Print button -->
-
-        <FrameLayout
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="24dip"
-            android:background="@color/action_button_background">
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_gravity="fill_horizontal"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-            <Button
-                android:id="@+id/print_button"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_gravity="fill_horizontal"
-                android:text="@string/print_button"
-                android:textSize="16sp"
-                android:textColor="@color/item_text_color">
-            </Button>
-
-        </FrameLayout>
-
-    </LinearLayout>
-
-</ScrollView>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
deleted file mode 100644
index d9f0a9a..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
+++ /dev/null
@@ -1,70 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content_generating"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip"
-            android:layout_marginTop="32dip"
-            android:layout_marginBottom="32dip"
-            android:layout_gravity="center"
-            style="?android:attr/buttonBarButtonStyle"
-            android:ellipsize="end"
-            android:text="@string/print_error_default_message"
-            android:textColor="@color/important_text"
-            android:textSize="16sp">
-        </TextView>
-
-    </LinearLayout>
-
-    <FrameLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/action_button_background">
-
-        <View
-            android:layout_width="fill_parent"
-            android:layout_height="1dip"
-            android:background="@color/separator">
-        </View>
-
-        <Button
-            android:id="@+id/ok_button"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            style="?android:attr/buttonBarButtonStyle"
-            android:text="@android:string/ok"
-            android:textSize="16sp"
-            android:textColor="@color/important_text">
-        </Button>
-
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml
deleted file mode 100644
index 10602ee..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml
+++ /dev/null
@@ -1,79 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content_generating"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="vertical">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip"
-            android:layout_gravity="center"
-            style="?android:attr/buttonBarButtonStyle"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:text="@string/generating_print_job"
-            android:textColor="@color/important_text"
-            android:textSize="16sp">
-        </TextView>
-
-        <ProgressBar
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="32dip"
-            android:layout_marginEnd="32dip"
-            android:layout_marginTop="16dip"
-            android:layout_marginBottom="32dip"
-            android:layout_gravity="center_horizontal"
-            style="?android:attr/progressBarStyleLarge">
-        </ProgressBar>
-
-    </LinearLayout>
-
-    <FrameLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/action_button_background">
-
-        <View
-            android:layout_width="fill_parent"
-            android:layout_height="1dip"
-            android:background="@color/separator">
-        </View>
-
-        <Button
-            android:id="@+id/cancel_button"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            style="?android:attr/buttonBarButtonStyle"
-            android:text="@string/cancel"
-            android:textSize="16sp"
-            android:textColor="@color/important_text">
-        </Button>
-
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
new file mode 100644
index 0000000..212da9e
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dip"
+        android:src="@drawable/ic_grayedout_printer"
+        android:contentDescription="@null">
+    </ImageView>
+
+    <ProgressBar
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        style="?android:attr/progressBarStyleHorizontal">
+    </ProgressBar>
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:gravity="center"
+        android:animateLayoutChanges="true">
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:text="@string/print_operation_canceling"
+            android:visibility="gone">
+        </TextView>
+
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@android:string/cancel">
+        </Button>
+
+    </FrameLayout>
+
+</LinearLayout>
+
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index 1a61b99..43d8aaf 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -19,7 +19,7 @@
       android:layout_height="wrap_content"
       android:paddingStart="16dip"
       android:paddingEnd="16dip"
-      android:minHeight="?android:attr/listPreferredItemHeightSmall"
+      android:minHeight="56dip"
       android:orientation="horizontal"
       android:gravity="start|center_vertical">
 
@@ -49,7 +49,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:gravity="top|start"
-            android:textColor="@color/item_text_color"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
@@ -62,7 +62,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="@color/print_option_title"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
index 47eb0b5..1f5efbc 100644
--- a/packages/PrintSpooler/res/layout/printer_list_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -15,13 +15,13 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:orientation="horizontal"
-        android:gravity="start|center_vertical">
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="start|center_vertical">
 
     <ImageView
         android:id="@+id/icon"
@@ -31,7 +31,7 @@
         android:layout_marginEnd="8dip"
         android:duplicateParentState="true"
         android:contentDescription="@null"
-        android:visibility="gone">
+        android:visibility="invisible">
     </ImageView>
 
     <LinearLayout
@@ -49,7 +49,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:gravity="top|start"
-            android:textColor="@color/item_text_color"
+            android:textColor="?android:attr/textColorSecondary"
             android:duplicateParentState="true">
         </TextView>
 
@@ -62,7 +62,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="@color/print_option_title"
+            android:textColor="?android:attr/textColorSecondary"
             android:duplicateParentState="true">
         </TextView>
 
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 4488b6a..173057b 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -19,12 +19,16 @@
     android:layout_width="fill_parent"
     android:layout_height="fill_parent">
 
-    <fragment
-        android:name="com.android.printspooler.SelectPrinterFragment"
-        android:id="@+id/select_printer_fragment"
+    <ListView
+        android:id="@android:id/list"
         android:layout_width="fill_parent"
-        android:layout_height="wrap_content">
-    </fragment>
+        android:layout_height="fill_parent"
+        android:paddingStart="@dimen/printer_list_view_padding_start"
+        android:paddingEnd="@dimen/printer_list_view_padding_end"
+        android:scrollbarStyle="outsideOverlay"
+        android:cacheColorHint="@android:color/transparent"
+        android:scrollbarAlwaysDrawVerticalTrack="true" >
+    </ListView>
 
     <FrameLayout
         android:id="@+id/empty_print_state"
diff --git a/packages/PrintSpooler/res/layout/select_printer_fragment.xml b/packages/PrintSpooler/res/layout/select_printer_fragment.xml
deleted file mode 100644
index bbd012e..0000000
--- a/packages/PrintSpooler/res/layout/select_printer_fragment.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/list"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:paddingStart="@dimen/printer_list_view_padding_start"
-    android:paddingEnd="@dimen/printer_list_view_padding_end"
-    android:scrollbarStyle="outsideOverlay"
-    android:cacheColorHint="@android:color/transparent"
-    android:scrollbarAlwaysDrawVerticalTrack="true" >
-</ListView>
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index c3c5021..1fb221a 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -32,7 +32,7 @@
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:gravity="top|left"
-        android:textColor="@color/item_text_color"
+        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
@@ -45,7 +45,7 @@
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:visibility="gone"
-        android:textColor="@color/print_option_title"
+        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index d68b77e..0db7513 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -18,5 +18,6 @@
 
     <dimen name="printer_list_view_padding_start">48dip</dimen>
     <dimen name="printer_list_view_padding_end">48dip</dimen>
+    <integer name="print_option_column_count">3</integer>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp-land/constants.xml b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
new file mode 100644
index 0000000..cacdf98
--- /dev/null
+++ b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources>
+
+    <integer name="print_option_column_count">6</integer>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp/constants.xml b/packages/PrintSpooler/res/values-sw600dp/constants.xml
new file mode 100644
index 0000000..14c099c
--- /dev/null
+++ b/packages/PrintSpooler/res/values-sw600dp/constants.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<resources>
+
+    <integer name="print_option_column_count">3</integer>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values/attrs.xml b/packages/PrintSpooler/res/values/attrs.xml
new file mode 100644
index 0000000..feb8be1
--- /dev/null
+++ b/packages/PrintSpooler/res/values/attrs.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources>
+
+    <declare-styleable name="PrintOptionsLayout">
+
+        <attr name="columnCount" format="integer" />
+
+    </declare-styleable>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 4fc25b3..fd6ae195 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -16,10 +16,6 @@
 
 <resources>
 
-    <color name="container_background">#F2F2F2</color>
-    <color name="important_text">#333333</color>
-    <color name="print_option_title">#888888</color>
-    <color name="separator">#CCCCCC</color>
-    <color name="action_button_background">#FFFFFF</color>
+    <color name="print_button_tint_color">#EEFF41</color>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index e9c925c..9a2c14e 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -19,6 +19,8 @@
     <integer name="page_option_value_all">0</integer>
     <integer name="page_option_value_page_range">1</integer>
 
+    <integer name="print_option_column_count">2</integer>
+
     <integer-array name="page_options_values" translatable="false">
         <item>@integer/page_option_value_all</item>
         <item>@integer/page_option_value_page_range</item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index d2613d0..d85529c 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -19,14 +19,8 @@
     <!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
     <string name="app_label">Print Spooler</string>
 
-    <!-- Label of the print dialog's button for advanced printer settings. [CHAR LIMIT=25] -->
-    <string name="advanced_settings_button">Printer settings</string>
-
-    <!-- Label of the print dialog's print button. [CHAR LIMIT=16] -->
-    <string name="print_button">Print</string>
-
-    <!-- Label of the print dialog's save button. [CHAR LIMIT=16] -->
-    <string name="save_button">Save</string>
+    <!-- Label of the print dialog's button for more print options. [CHAR LIMIT=25] -->
+    <string name="more_options_button">More options</string>
 
     <!-- Label of the destination widget. [CHAR LIMIT=20] -->
     <string name="label_destination">Destination</string>
@@ -34,8 +28,14 @@
     <!-- Label of the copies count widget. [CHAR LIMIT=20] -->
     <string name="label_copies">Copies</string>
 
+    <!-- Label of the copies count for the print options summary. [CHAR LIMIT=20] -->
+    <string name="label_copies_summary">Copies:</string>
+
     <!-- Label of the paper size widget. [CHAR LIMIT=20] -->
-    <string name="label_paper_size">Paper Size</string>
+    <string name="label_paper_size">Paper size</string>
+
+    <!-- Label of the paper size for the print options summary. [CHAR LIMIT=20] -->
+    <string name="label_paper_size_summary">Paper size:</string>
 
     <!-- Label of the color mode widget. [CHAR LIMIT=20] -->
     <string name="label_color">Color</string>
@@ -118,19 +118,19 @@
 
     <!-- Notifications -->
 
-    <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a printing print job. [CHAR LIMIT=25] -->
     <string name="printing_notification_title_template">Printing <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a cancelling print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a cancelling print job. [CHAR LIMIT=25] -->
     <string name="cancelling_notification_title_template">Cancelling <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a failed print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a failed print job. [CHAR LIMIT=25] -->
     <string name="failed_notification_title_template">Printer error <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a blocked print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a blocked print job. [CHAR LIMIT=25] -->
     <string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
     <plurals name="composite_notification_title_template">
         <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item>
         <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item>
@@ -139,7 +139,7 @@
     <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
     <string name="cancel">Cancel</string>
 
-    <!-- Label for the notification button for restrating a filed print job. [CHAR LIMIT=25] -->
+    <!-- Label for the notification button for restarting a filed print job. [CHAR LIMIT=25] -->
     <string name="restart">Restart</string>
 
     <!-- Message that there is no connection to a printer. [CHAR LIMIT=40] -->
@@ -151,9 +151,6 @@
     <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
     <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
 
-    <!-- Default message of an alert dialog for app error while generating a print job. [CHAR LIMIT=50] -->
-    <string name="print_error_default_message">Couldn\'t generate print job</string>
-
     <!-- Arrays -->
 
     <!-- Color mode labels. -->
@@ -200,4 +197,23 @@
         holder to start the configuration activities of a print service. Should never be needed
         for normal apps.</string>
 
+    <!-- Error messages -->
+
+    <!-- Message for an error when trying to print to a PDF file. [CHAR LIMIT=50] -->
+    <string name="print_write_error_message">Couldn\'t write to file</string>
+
+    <!-- Default message for an error while generating a print job. [CHAR LIMIT=50] -->
+    <string name="print_error_default_message">Couldn\'t generate print job</string>
+
+    <!-- Label for the retry button in the error message. [CHAR LIMIT=50] -->
+    <string name="print_error_retry">Retry</string>
+
+    <!-- Message for the currently selected printer becoming unavailable. [CHAR LIMIT=50] -->
+    <string name="print_error_printer_unavailable">Printer unavailable</string>
+
+    <!-- Long running operations -->
+
+    <!-- Message for the cancel print operation UI while waiting for cancel to be performed. [CHAR LIMIT=50] -->
+    <string name="print_operation_canceling">Cancelling\u2026</string>
+
 </resources>
diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml
index d64380a..9637847 100644
--- a/packages/PrintSpooler/res/values/styles.xml
+++ b/packages/PrintSpooler/res/values/styles.xml
@@ -16,13 +16,6 @@
 
 <resources>
 
-    <style name="PrintOptionTitleTextAppearance">
-        <item name="android:textStyle">normal</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textColor">@color/print_option_title</item>
-    </style>
-
     <style name="PrintOptionSpinnerStyle">
         <item name="android:paddingTop">0dip</item>
         <item name="android:paddingBottom">0dip</item>
@@ -30,7 +23,7 @@
     </style>
 
     <style name="PrintOptionEditTextStyle">
-         <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
+
          <item name="android:singleLine">true</item>
          <item name="android:ellipsize">end</item>
     </style>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 94ab895..40bf725 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -16,15 +16,6 @@
 
 <resources>
 
-    <style name="PrintJobConfigActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustResize</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:backgroundDimEnabled">true</item>
-        <item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
-        <item name="android:windowIsFloating">true</item>
-    </style>
-
     <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
     </style>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
deleted file mode 100644
index c1c4d21..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
+++ /dev/null
@@ -1,69 +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.printspooler;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-public class PrintDialogFrame extends FrameLayout {
-
-    public final int mMaxWidth;
-
-    public int mHeight;
-
-    public PrintDialogFrame(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mMaxWidth = context.getResources().getDimensionPixelSize(
-                R.dimen.print_dialog_frame_max_width_dip);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        int measuredWidth  = getMeasuredWidth();
-        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        switch (widthMode) {
-            case MeasureSpec.UNSPECIFIED: {
-                measuredWidth = mMaxWidth;
-            } break;
-
-            case MeasureSpec.AT_MOST: {
-                final int receivedWidth = MeasureSpec.getSize(widthMeasureSpec);
-                measuredWidth = Math.min(mMaxWidth, receivedWidth);
-            } break;
-        }
-
-        mHeight = Math.max(mHeight, getMeasuredHeight());
-
-        int measuredHeight  = getMeasuredHeight();
-        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        switch (heightMode) {
-            case MeasureSpec.UNSPECIFIED: {
-                measuredHeight = mHeight;
-            } break;
-
-             case MeasureSpec.AT_MOST: {
-                final int receivedHeight = MeasureSpec.getSize(heightMeasureSpec);
-                measuredHeight = Math.min(mHeight, receivedHeight);
-            } break;
-        }
-
-        setMeasuredDimension(measuredWidth, measuredHeight);
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
deleted file mode 100644
index e3d8d05..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ /dev/null
@@ -1,2966 +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.printspooler;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.LoaderManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.ServiceConnection;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.print.ILayoutResultCallback;
-import android.print.IPrintDocumentAdapter;
-import android.print.IPrintDocumentAdapterObserver;
-import android.print.IWriteResultCallback;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentInfo;
-import android.print.PrintJobId;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterCapabilitiesInfo;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.printservice.PrintService;
-import android.printservice.PrintServiceInfo;
-import android.provider.DocumentsContract;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.TextWatcher;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.android.printspooler.MediaSizeUtils.MediaSizeComparator;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Activity for configuring a print job.
- */
-public class PrintJobConfigActivity extends Activity {
-
-    private static final String LOG_TAG = "PrintJobConfigActivity";
-
-    private static final boolean DEBUG = false;
-
-    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
-
-    private static final int LOADER_ID_PRINTERS_LOADER = 1;
-
-    private static final int ORIENTATION_PORTRAIT = 0;
-    private static final int ORIENTATION_LANDSCAPE = 1;
-
-    private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
-
-    private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
-    private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
-
-    private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
-    private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
-    private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
-
-    private static final int CONTROLLER_STATE_FINISHED = 1;
-    private static final int CONTROLLER_STATE_FAILED = 2;
-    private static final int CONTROLLER_STATE_CANCELLED = 3;
-    private static final int CONTROLLER_STATE_INITIALIZED = 4;
-    private static final int CONTROLLER_STATE_STARTED = 5;
-    private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
-    private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
-    private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
-    private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
-
-    private static final int EDITOR_STATE_INITIALIZED = 1;
-    private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
-    private static final int EDITOR_STATE_CANCELLED = 3;
-
-    private static final int MIN_COPIES = 1;
-    private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
-
-    private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
-
-    private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
-            "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
-
-    private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
-            "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
-            + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
-
-    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
-
-    private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build();
-    private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build();
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            finish();
-        }
-    };
-
-    private Editor mEditor;
-    private Document mDocument;
-    private PrintController mController;
-
-    private PrintJobId mPrintJobId;
-
-    private IBinder mIPrintDocumentAdapter;
-
-    private Dialog mGeneratingPrintJobDialog;
-
-    private PrintSpoolerProvider mSpoolerProvider;
-
-    private String mCallingPackageName;
-
-    @Override
-    protected void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-
-        setTitle(R.string.print_dialog);
-
-        Bundle extras = getIntent().getExtras();
-
-        PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
-        if (printJob == null) {
-            throw new IllegalArgumentException("printJob cannot be null");
-        }
-
-        mPrintJobId = printJob.getId();
-        mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
-        if (mIPrintDocumentAdapter == null) {
-            throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
-        }
-
-        try {
-            IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter)
-                    .setObserver(new PrintDocumentAdapterObserver(this));
-        } catch (RemoteException re) {
-            finish();
-            return;
-        }
-
-        PrintAttributes attributes = printJob.getAttributes();
-        if (attributes != null) {
-            mCurrPrintAttributes.copyFrom(attributes);
-        }
-
-        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
-
-        setContentView(R.layout.print_job_config_activity_container);
-
-        try {
-            mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
-        } catch (RemoteException re) {
-            finish();
-            return;
-        }
-
-        mDocument = new Document();
-        mEditor = new Editor();
-
-        mSpoolerProvider = new PrintSpoolerProvider(this,
-                new Runnable() {
-            @Override
-            public void run() {
-                // We got the spooler so unleash the UI.
-                mController = new PrintController(new RemotePrintDocumentAdapter(
-                        IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                        mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId)));
-                mController.initialize();
-
-                mEditor.initialize();
-                mEditor.postCreate();
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mSpoolerProvider.getSpooler() != null) {
-            mEditor.refreshCurrentPrinter();
-        }
-    }
-
-    @Override
-    public void onPause() {
-       if (isFinishing()) {
-           if (mController != null && mController.hasStarted()) {
-               mController.finish();
-           }
-           if (mEditor != null && mEditor.isPrintConfirmed()
-                   && mController != null && mController.isFinished()) {
-                   mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
-                           PrintJobInfo.STATE_QUEUED, null);
-           } else {
-               mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
-                       PrintJobInfo.STATE_CANCELED, null);
-           }
-           if (mGeneratingPrintJobDialog != null) {
-               mGeneratingPrintJobDialog.dismiss();
-               mGeneratingPrintJobDialog = null;
-           }
-           mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
-           mSpoolerProvider.destroy();
-       }
-        super.onPause();
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mController != null && mEditor != null &&
-                !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) {
-            if (!mController.isWorking()) {
-                PrintJobConfigActivity.this.finish();
-            }
-            mEditor.cancel();
-            return true;
-        }
-        return super.onTouchEvent(event);
-    }
-
-    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 (mController != null && mEditor != null) {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
-                if (mEditor.isShwoingGeneratingPrintJobUi()) {
-                    return true;
-                }
-                if (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 Bundle mMetadata;
-
-        private final ControllerHandler mHandler;
-
-        private final LayoutResultCallback mLayoutResultCallback;
-
-        private final WriteResultCallback mWriteResultCallback;
-
-        private int mControllerState = CONTROLLER_STATE_INITIALIZED;
-
-        private boolean mHasStarted;
-
-        private PageRange[] mRequestedPages;
-
-        public PrintController(RemotePrintDocumentAdapter adapter) {
-            mRemotePrintAdapter = adapter;
-            mMetadata = new Bundle();
-            mHandler = new ControllerHandler(getMainLooper());
-            mLayoutResultCallback = new LayoutResultCallback(mHandler);
-            mWriteResultCallback = new WriteResultCallback(mHandler);
-        }
-
-        public void initialize() {
-            mHasStarted = false;
-            mControllerState = CONTROLLER_STATE_INITIALIZED;
-        }
-
-        public void cancel() {
-            if (isWorking()) {
-                mRemotePrintAdapter.cancel();
-            }
-            mControllerState = CONTROLLER_STATE_CANCELLED;
-        }
-
-        public boolean isCancelled() {
-            return (mControllerState == CONTROLLER_STATE_CANCELLED);
-        }
-
-        public boolean isFinished() {
-            return (mControllerState == CONTROLLER_STATE_FINISHED);
-        }
-
-        public boolean hasStarted() {
-            return mHasStarted;
-        }
-
-        public boolean hasPerformedLayout() {
-            return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
-        }
-
-        public boolean isPerformingLayout() {
-            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED;
-        }
-
-        public boolean isWorking() {
-            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
-                    || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
-        }
-
-        public void start() {
-            mControllerState = CONTROLLER_STATE_STARTED;
-            mHasStarted = true;
-            mRemotePrintAdapter.start();
-        }
-
-        public void update() {
-            if (!mController.hasStarted()) {
-                mController.start();
-            }
-
-            // If the print attributes are the same and we are performing
-            // a layout, then we have to wait for it to completed which will
-            // trigger writing of the necessary pages.
-            final boolean printAttributesChanged = printAttributesChanged();
-            if (!printAttributesChanged && isPerformingLayout()) {
-                return;
-            }
-
-            // If print is confirmed we always do a layout since the previous
-            // ones were for preview and this one is for printing.
-            if (!printAttributesChanged && !mEditor.isPrintConfirmed()) {
-                if (mDocument.info == null) {
-                    // We are waiting for the result of a layout, so do nothing.
-                    return;
-                }
-                // If the attributes didn't change and we have done a layout, 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, mRequestCounter.get());
-            } else {
-                mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence(
-                        mPrintJobId, mCurrPrintAttributes);
-
-                mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW,
-                        !mEditor.isPrintConfirmed());
-
-                mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
-
-                mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
-                        mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
-
-                mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
-            }
-        }
-
-        public void finish() {
-            mControllerState = CONTROLLER_STATE_FINISHED;
-            mRemotePrintAdapter.finish();
-        }
-
-        private void handleOnLayoutFinished(PrintDocumentInfo info,
-                boolean layoutChanged, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-
-            if (isCancelled()) {
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            }
-
-            final int oldControllerState = mControllerState;
-
-            if (mControllerState == CONTROLLER_STATE_LAYOUT_STARTED) {
-                mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
-            }
-
-            // For layout purposes we care only whether the type or the page
-            // count changed. We still do not have the size since we did not
-            // call write. We use "layoutChanged" set by the application to
-            // know whether something else changed about the document.
-            final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info);
-            // If the info changed, we update the document and the print job.
-            if (infoChanged) {
-                mDocument.info = info;
-                // Set the info.
-                mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
-                        mPrintJobId, info);
-            }
-
-            // 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;
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
-                        mPrintJobId, null);
-            }
-
-            PageRange[] oldRequestedPages = mRequestedPages;
-
-            // 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 || mRequestedPages.length == 0) {
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            } else {
-                // If print is not confirmed we just ask for the first of the
-                // selected pages to emulate a behavior that shows preview
-                // increasing the chances that apps will implement the APIs
-                // correctly.
-                if (!mEditor.isPrintConfirmed()) {
-                    if (ALL_PAGES_ARRAY.equals(mRequestedPages)) {
-                        mRequestedPages = new PageRange[] {new PageRange(0, 0)};
-                    } else {
-                        final int firstPage = mRequestedPages[0].getStart();
-                        mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)};
-                    }
-                }
-            }
-
-            // If the info and the layout did not change...
-            if (!infoChanged && !layoutChanged
-                    // and we have the requested pages ... 
-                    && (PageRangeUtils.contains(mDocument.pages, mRequestedPages))
-                        // ...or the requested pages are being written...
-                        || (oldControllerState == CONTROLLER_STATE_WRITE_STARTED
-                            && oldRequestedPages != null
-                            && PageRangeUtils.contains(oldRequestedPages, mRequestedPages))) {
-                // Nothing interesting changed and we have all requested pages.
-                // Then update the print jobs's pages as we will not do a write
-                // and we usually update the pages in the write complete callback.
-                if (mDocument.pages != null) {
-                    // Update the print job's pages given we have them.
-                    updatePrintJobPages(mDocument.pages, mRequestedPages);
-                }
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    requestCreatePdfFileOrFinish();
-                }
-                return;
-            }
-
-            mEditor.updateUi();
-
-            // Request a write of the pages of interest.
-            mControllerState = CONTROLLER_STATE_WRITE_STARTED;
-            mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback,
-                    mRequestCounter.incrementAndGet());
-        }
-
-        private void handleOnLayoutFailed(final CharSequence error, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-            mControllerState = CONTROLLER_STATE_FAILED;
-            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
-                @Override
-                public void run() {
-                    if (!TextUtils.isEmpty(error)) {
-                        TextView messageView = (TextView) findViewById(R.id.message);
-                        messageView.setText(error);
-                    }
-                }
-            });
-        }
-
-        private void handleOnWriteFinished(PageRange[] pages, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-
-            if (isCancelled()) {
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            }
-
-            mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
-
-            // Update the document size.
-            File file = mSpoolerProvider.getSpooler()
-                    .generateFileForPrintJob(mPrintJobId);
-            mDocument.info.setDataSize(file.length());
-
-            // Update the print job with the updated info.
-            mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
-                    mPrintJobId, mDocument.info);
-
-            // 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));
-            }
-
-            updatePrintJobPages(mDocument.pages, mRequestedPages);
-
-            if (mEditor.isDone()) {
-                requestCreatePdfFileOrFinish();
-            }
-        }
-
-        private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) {
-            // 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(writtenPages, requestedPages)) {
-                // We got a document with exactly the pages we wanted. Hence,
-                // the printer has to print all pages in the data.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        ALL_PAGES_ARRAY);
-            } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
-                // We requested specific pages but got all of them. Hence,
-                // the printer has to print only the requested pages.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        requestedPages);
-            } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
-                // We requested specific pages and got more but not all pages.
-                // Hence, we have to offset appropriately the printed pages to
-                // be based off the start of the written ones instead of zero.
-                // The written pages are always non-null and not empty.
-                final int offset = -writtenPages[0].getStart();
-                PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length);
-                PageRangeUtils.offset(offsetPages, offset);
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        offsetPages);
-            } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
-                    && writtenPages.length == 1 && writtenPages[0].getStart() == 0
-                    && writtenPages[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.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        writtenPages);
-            } else {
-                // We did not get the pages we requested, then the application
-                // misbehaves, so we fail quickly.
-                mControllerState = CONTROLLER_STATE_FAILED;
-                Log.e(LOG_TAG, "Received invalid pages from the app: requested="
-                        + Arrays.toString(requestedPages) + " written="
-                        + Arrays.toString(writtenPages));
-                mEditor.showUi(Editor.UI_ERROR, null);
-            }
-        }
-
-        private void requestCreatePdfFileOrFinish() {
-            if (mEditor.isPrintingToPdf()) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-                intent.setType("application/pdf");
-                intent.putExtra(Intent.EXTRA_TITLE, mDocument.info.getName());
-                intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
-                startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
-            } else {
-                PrintJobConfigActivity.this.finish();
-            }
-        }
-
-        private void handleOnWriteFailed(final CharSequence error, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-            mControllerState = CONTROLLER_STATE_FAILED;
-            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
-                @Override
-                public void run() {
-                    if (!TextUtils.isEmpty(error)) {
-                        TextView messageView = (TextView) findViewById(R.id.message);
-                        messageView.setText(error);
-                    }
-                }
-            });
-        }
-
-        private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
-            if (lhs == rhs) {
-                return true;
-            }
-            if (lhs == null) {
-                if (rhs != null) {
-                    return false;
-                }
-            } else {
-                if (rhs == null) {
-                    return false;
-                }
-                if (lhs.getContentType() != rhs.getContentType()
-                        || lhs.getPageCount() != rhs.getPageCount()) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private final class ControllerHandler 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 ControllerHandler(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);
-                        final int sequence = message.arg2;
-                        handleOnLayoutFinished(info, changed, sequence);
-                    } break;
-
-                    case MSG_ON_LAYOUT_FAILED: {
-                        CharSequence error = (CharSequence) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnLayoutFailed(error, sequence);
-                    } break;
-
-                    case MSG_ON_WRITE_FINISHED: {
-                        PageRange[] pages = (PageRange[]) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnWriteFinished(pages, sequence);
-                    } break;
-
-                    case MSG_ON_WRITE_FAILED: {
-                        CharSequence error = (CharSequence) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnWriteFailed(error, sequence);
-                    } break;
-                }
-            }
-        }
-    }
-
-    private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
-        private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
-
-        public LayoutResultCallback(PrintController.ControllerHandler handler) {
-            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
-        }
-
-        @Override
-        public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED,
-                        changed ? 1 : 0, sequence, info).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onLayoutFailed(CharSequence error, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED,
-                        sequence, 0, error).sendToTarget();
-            }
-        }
-    }
-
-    private static final class WriteResultCallback extends IWriteResultCallback.Stub {
-        private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
-
-        public WriteResultCallback(PrintController.ControllerHandler handler) {
-            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
-        }
-
-        @Override
-        public void onWriteFinished(PageRange[] pages, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED,
-                        sequence, 0, pages).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onWriteFailed(CharSequence error, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED,
-                    sequence, 0, error).sendToTarget();
-            }
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case ACTIVITY_REQUEST_CREATE_FILE: {
-                if (data != null) {
-                    Uri uri = data.getData();
-                    writePrintJobDataAndFinish(uri);
-                } else {
-                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
-                            new Runnable() {
-                        @Override
-                        public void run() {
-                            mEditor.initialize();
-                            mEditor.bindUi();
-                            mEditor.reselectCurrentPrinter();
-                            mEditor.updateUi();
-                        }
-                    });
-                }
-            } break;
-
-            case ACTIVITY_REQUEST_SELECT_PRINTER: {
-                if (resultCode == RESULT_OK) {
-                    PrinterId printerId = (PrinterId) data.getParcelableExtra(
-                            INTENT_EXTRA_PRINTER_ID);
-                    if (printerId != null) {
-                        mEditor.ensurePrinterSelected(printerId);
-                        break;
-                    }
-                }
-                mEditor.ensureCurrentPrinterSelected();
-            } break;
-
-            case ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS: {
-                if (resultCode == RESULT_OK) {
-                    PrintJobInfo printJobInfo = (PrintJobInfo) data.getParcelableExtra(
-                            PrintService.EXTRA_PRINT_JOB_INFO);
-                    if (printJobInfo != null) {
-                        mEditor.updateFromAdvancedOptions(printJobInfo);
-                        break;
-                    }
-                }
-                mEditor.cancel();
-                PrintJobConfigActivity.this.finish();
-            } break;
-        }
-    }
-
-    private void writePrintJobDataAndFinish(final Uri uri) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                InputStream in = null;
-                OutputStream out = null;
-                try {
-                    PrintJobInfo printJob = mSpoolerProvider.getSpooler()
-                            .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
-                    if (printJob == null) {
-                        return null;
-                    }
-                    File file = mSpoolerProvider.getSpooler()
-                            .generateFileForPrintJob(mPrintJobId);
-                    in = new FileInputStream(file);
-                    out = getContentResolver().openOutputStream(uri);
-                    final byte[] buffer = new byte[8192];
-                    while (true) {
-                        final int readByteCount = in.read(buffer);
-                        if (readByteCount < 0) {
-                            break;
-                        }
-                        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);
-                }
-                return null;
-            }
-
-            @Override
-            public void onPostExecute(Void result) {
-                mEditor.cancel();
-                PrintJobConfigActivity.this.finish();
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    private final class Editor {
-        private static final int UI_NONE = 0;
-        private static final int UI_EDITING_PRINT_JOB = 1;
-        private static final int UI_GENERATING_PRINT_JOB = 2;
-        private static final int UI_ERROR = 3;
-
-        private EditText mCopiesEditText;
-
-        private TextView mRangeOptionsTitle;
-        private TextView mPageRangeTitle;
-        private EditText mPageRangeEditText;
-
-        private Spinner mDestinationSpinner;
-        private DestinationAdapter mDestinationSpinnerAdapter;
-
-        private Spinner mMediaSizeSpinner;
-        private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
-
-        private Spinner mColorModeSpinner;
-        private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
-
-        private Spinner mOrientationSpinner;
-        private  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
-
-        private Spinner mRangeOptionsSpinner;
-        private ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
-
-        private SimpleStringSplitter mStringCommaSplitter =
-                new SimpleStringSplitter(',');
-
-        private View mContentContainer;
-
-        private View mAdvancedPrintOptionsContainer;
-
-        private Button mAdvancedOptionsButton;
-
-        private Button mPrintButton;
-
-        private PrinterId mNextPrinterId;
-
-        private PrinterInfo mCurrentPrinter;
-
-        private MediaSizeComparator mMediaSizeComparator;
-
-        private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View view, boolean hasFocus) {
-                EditText editText = (EditText) view;
-                if (!TextUtils.isEmpty(editText.getText())) {
-                    editText.setSelection(editText.getText().length());
-                }
-            }
-        };
-
-        private final OnItemSelectedListener mOnItemSelectedListener =
-                new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
-                if (spinner == mDestinationSpinner) {
-                    if (mIgnoreNextDestinationChange) {
-                        mIgnoreNextDestinationChange = false;
-                        return;
-                    }
-
-                    if (position == AdapterView.INVALID_POSITION) {
-                        updateUi();
-                        return;
-                    }
-
-                    if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
-                        startSelectPrinterActivity();
-                        return;
-                    }
-
-                    mCapabilitiesTimeout.remove();
-
-                    mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
-                            .getItem(position);
-
-                    mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence(
-                            mPrintJobId, mCurrentPrinter);
-
-                    if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
-                        mCapabilitiesTimeout.post();
-                        updateUi();
-                        return;
-                    }
-
-                    PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
-                    if (capabilities == null) {
-                        mCapabilitiesTimeout.post();
-                        updateUi();
-                        refreshCurrentPrinter();
-                    } else {
-                        updatePrintAttributes(capabilities);
-                        updateUi();
-                        mController.update();
-                        refreshCurrentPrinter();
-                    }
-                } else if (spinner == mMediaSizeSpinner) {
-                    if (mIgnoreNextMediaSizeChange) {
-                        mIgnoreNextMediaSizeChange = false;
-                        return;
-                    }
-                    if (mOldMediaSizeSelectionIndex
-                            == mMediaSizeSpinner.getSelectedItemPosition()) {
-                        mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION;
-                        return;
-                    }
-                    SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
-                    if (mOrientationSpinner.getSelectedItemPosition() == 0) {
-                        mCurrPrintAttributes.setMediaSize(mediaItem.value.asPortrait());
-                    } else {
-                        mCurrPrintAttributes.setMediaSize(mediaItem.value.asLandscape());
-                    }
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mColorModeSpinner) {
-                    if (mIgnoreNextColorChange) {
-                        mIgnoreNextColorChange = false;
-                        return;
-                    }
-                    if (mOldColorModeSelectionIndex
-                            == mColorModeSpinner.getSelectedItemPosition()) {
-                        mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION;
-                        return;
-                    }
-                    SpinnerItem<Integer> colorModeItem =
-                            mColorModeSpinnerAdapter.getItem(position);
-                    mCurrPrintAttributes.setColorMode(colorModeItem.value);
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mOrientationSpinner) {
-                    if (mIgnoreNextOrientationChange) {
-                        mIgnoreNextOrientationChange = false;
-                        return;
-                    }
-                    SpinnerItem<Integer> orientationItem =
-                            mOrientationSpinnerAdapter.getItem(position);
-                    setCurrentPrintAttributesOrientation(orientationItem.value);
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mRangeOptionsSpinner) {
-                    if (mIgnoreNextRangeOptionChange) {
-                        mIgnoreNextRangeOptionChange = false;
-                        return;
-                    }
-                    updateUi();
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                }
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> parent) {
-                /* do nothing*/
-            }
-        };
-
-        private void setCurrentPrintAttributesOrientation(int orientation) {
-            MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
-            if (orientation == ORIENTATION_PORTRAIT) {
-                if (!mediaSize.isPortrait()) {
-                    // Rotate the media size.
-                    mCurrPrintAttributes.setMediaSize(mediaSize.asPortrait());
-
-                    // Rotate the resolution.
-                    Resolution oldResolution = mCurrPrintAttributes.getResolution();
-                    Resolution newResolution = new Resolution(
-                            oldResolution.getId(),
-                            oldResolution.getLabel(),
-                            oldResolution.getVerticalDpi(),
-                            oldResolution.getHorizontalDpi());
-                    mCurrPrintAttributes.setResolution(newResolution);
-
-                    // Rotate the physical margins.
-                    Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
-                    Margins newMinMargins = new Margins(
-                            oldMinMargins.getBottomMils(),
-                            oldMinMargins.getLeftMils(),
-                            oldMinMargins.getTopMils(),
-                            oldMinMargins.getRightMils());
-                    mCurrPrintAttributes.setMinMargins(newMinMargins);
-                }
-            } else {
-                if (mediaSize.isPortrait()) {
-                    // Rotate the media size.
-                    mCurrPrintAttributes.setMediaSize(mediaSize.asLandscape());
-
-                    // Rotate the resolution.
-                    Resolution oldResolution = mCurrPrintAttributes.getResolution();
-                    Resolution newResolution = new Resolution(
-                            oldResolution.getId(),
-                            oldResolution.getLabel(),
-                            oldResolution.getVerticalDpi(),
-                            oldResolution.getHorizontalDpi());
-                    mCurrPrintAttributes.setResolution(newResolution);
-
-                    // Rotate the physical margins.
-                    Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
-                    Margins newMargins = new Margins(
-                            oldMinMargins.getTopMils(),
-                            oldMinMargins.getRightMils(),
-                            oldMinMargins.getBottomMils(),
-                            oldMinMargins.getLeftMils());
-                    mCurrPrintAttributes.setMinMargins(newMargins);
-                }
-            }
-        }
-
-        private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) {
-            PrintAttributes defaults = capabilities.getDefaults();
-
-            // Sort the media sizes based on the current locale.
-            List<MediaSize> sortedMediaSizes = new ArrayList<MediaSize>(
-                    capabilities.getMediaSizes());
-            Collections.sort(sortedMediaSizes, mMediaSizeComparator);
-
-            // Media size.
-            MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize();
-            if (currMediaSize == null) {
-                mCurrPrintAttributes.setMediaSize(defaults.getMediaSize());
-            } else {
-                MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
-                final int mediaSizeCount = sortedMediaSizes.size();
-                for (int i = 0; i < mediaSizeCount; i++) {
-                    MediaSize mediaSize = sortedMediaSizes.get(i);
-                    if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
-                        mCurrPrintAttributes.setMediaSize(currMediaSize);
-                        break;
-                    }
-                }
-            }
-
-            // Color mode.
-            final int colorMode = mCurrPrintAttributes.getColorMode();
-            if ((capabilities.getColorModes() & colorMode) == 0) {
-                mCurrPrintAttributes.setColorMode(colorMode);
-            }
-
-            // Resolution
-            Resolution resolution = mCurrPrintAttributes.getResolution();
-            if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
-                mCurrPrintAttributes.setResolution(defaults.getResolution());
-            }
-
-            // Margins.
-            Margins margins = mCurrPrintAttributes.getMinMargins();
-            if (margins == null) {
-                mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
-            } else {
-                Margins minMargins = capabilities.getMinMargins();
-                if (margins.getLeftMils() < minMargins.getLeftMils()
-                        || margins.getTopMils() < minMargins.getTopMils()
-                        || margins.getRightMils() > minMargins.getRightMils()
-                        || margins.getBottomMils() > minMargins.getBottomMils()) {
-                    mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
-                }
-            }
-        }
-
-        private final TextWatcher mCopiesTextWatcher = new TextWatcher() {
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                /* do nothing */
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                /* do nothing */
-            }
-
-            @Override
-            public void afterTextChanged(Editable editable) {
-                if (mIgnoreNextCopiesChange) {
-                    mIgnoreNextCopiesChange = false;
-                    return;
-                }
-
-                final boolean hadErrors = hasErrors();
-
-                if (editable.length() == 0) {
-                    mCopiesEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                int copies = 0;
-                try {
-                    copies = Integer.parseInt(editable.toString());
-                } catch (NumberFormatException nfe) {
-                    /* ignore */
-                }
-
-                if (copies < MIN_COPIES) {
-                    mCopiesEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                mCopiesEditText.setError(null);
-                mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
-                        mPrintJobId, copies);
-                updateUi();
-
-                if (hadErrors && !hasErrors() && printAttributesChanged()) {
-                    mController.update();
-                }
-            }
-        };
-
-        private final TextWatcher mRangeTextWatcher = new TextWatcher() {
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                /* do nothing */
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                /* do nothing */
-            }
-
-            @Override
-            public void afterTextChanged(Editable editable) {
-                if (mIgnoreNextRangeChange) {
-                    mIgnoreNextRangeChange = false;
-                    return;
-                }
-
-                final boolean hadErrors = hasErrors();
-
-                String text = editable.toString();
-
-                if (TextUtils.isEmpty(text)) {
-                    mPageRangeEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
-                if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
-                    mPageRangeEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                // The range
-                Matcher matcher = PATTERN_DIGITS.matcher(text);
-                while (matcher.find()) {
-                    String numericString = text.substring(matcher.start(), matcher.end()).trim();
-                    if (TextUtils.isEmpty(numericString)) {
-                        continue;
-                    }
-                    final int pageIndex = Integer.parseInt(numericString);
-                    if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
-                        mPageRangeEditText.setError("");
-                        updateUi();
-                        return;
-                    }
-                }
-
-                // We intentionally do not catch the case of the from page being
-                // greater than the to page. When computing the requested pages
-                // we just swap them if necessary.
-
-                // Keep the print job up to date with the selected pages if we
-                // know how many pages are there in the document.
-                PageRange[] requestedPages = getRequestedPages();
-                if (requestedPages != null && requestedPages.length > 0
-                        && requestedPages[requestedPages.length - 1].getEnd()
-                                < mDocument.info.getPageCount()) {
-                    mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
-                            mPrintJobId, requestedPages);
-                }
-
-                mPageRangeEditText.setError(null);
-                mPrintButton.setEnabled(true);
-                updateUi();
-
-                if (hadErrors && !hasErrors() && printAttributesChanged()) {
-                    updateUi();
-                }
-            }
-        };
-
-        private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout =
-                new WaitForPrinterCapabilitiesTimeout();
-
-        private int mEditorState;
-
-        private boolean mIgnoreNextDestinationChange;
-        private int mOldMediaSizeSelectionIndex;
-        private int mOldColorModeSelectionIndex;
-        private boolean mIgnoreNextOrientationChange;
-        private boolean mIgnoreNextRangeOptionChange;
-        private boolean mIgnoreNextCopiesChange;
-        private boolean mIgnoreNextRangeChange;
-        private boolean mIgnoreNextMediaSizeChange;
-        private boolean mIgnoreNextColorChange;
-
-        private int mCurrentUi = UI_NONE;
-
-        private boolean mFavoritePrinterSelected;
-
-        public Editor() {
-            showUi(UI_EDITING_PRINT_JOB, null);
-        }
-
-        public void postCreate() {
-            // Destination.
-            mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this);
-            mDestinationSpinnerAdapter = new DestinationAdapter();
-            mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
-                @Override
-                public void onChanged() {
-                    // Initially, we have only safe to PDF as a printer but after some
-                    // printers are loaded we want to select the user's favorite one
-                    // which is the first.
-                    if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 1) {
-                        mFavoritePrinterSelected = true;
-                        mDestinationSpinner.setSelection(0);
-                        // Workaround again the weird spinner behavior to notify for selection
-                        // change on the next layout pass as the current printer is used below.
-                        mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0);
-                    }
-
-                    // If there is a next printer to select and we succeed selecting
-                    // it - done. Let the selection handling code make everything right.
-                    if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) {
-                        mNextPrinterId = null;
-                        return;
-                    }
-
-                    // If the current printer properties changed, we update the UI.
-                    if (mCurrentPrinter != null) {
-                        final int printerCount = mDestinationSpinnerAdapter.getCount();
-                        for (int i = 0; i < printerCount; i++) {
-                            Object item = mDestinationSpinnerAdapter.getItem(i);
-                            // Some items are not printers
-                            if (item instanceof PrinterInfo) {
-                                PrinterInfo printer = (PrinterInfo) item;
-                                if (!printer.getId().equals(mCurrentPrinter.getId())) {
-                                    continue;
-                                }
-
-                                // If nothing changed - done.
-                                if (mCurrentPrinter.equals(printer)) {
-                                    return;
-                                }
-
-                                // If the current printer became available and has no
-                                // capabilities, we refresh it.
-                                if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getCapabilities() == null) {
-                                    if (!mCapabilitiesTimeout.isPosted()) {
-                                        mCapabilitiesTimeout.post();
-                                    }
-                                    mCurrentPrinter.copyFrom(printer);
-                                    refreshCurrentPrinter();
-                                    return;
-                                }
-
-                                // If the current printer became unavailable or its
-                                // capabilities go away, we update the UI and add a
-                                // timeout to declare the printer as unavailable.
-                                if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE)
-                                    || (mCurrentPrinter.getCapabilities() != null
-                                        && printer.getCapabilities() == null)) {
-                                    if (!mCapabilitiesTimeout.isPosted()) {
-                                        mCapabilitiesTimeout.post();
-                                    }
-                                    mCurrentPrinter.copyFrom(printer);
-                                    updateUi();
-                                    return;
-                                }
-
-                                // We just refreshed the current printer.
-                                if (printer.getCapabilities() != null
-                                        && mCapabilitiesTimeout.isPosted()) {
-                                    mCapabilitiesTimeout.remove();
-                                    updatePrintAttributes(printer.getCapabilities());
-                                    updateUi();
-                                    mController.update();
-                                }
-
-                                // Update the UI if capabilities changed.
-                                boolean capabilitiesChanged = false;
-
-                                if (mCurrentPrinter.getCapabilities() == null) {
-                                    if (printer.getCapabilities() != null) {
-                                        updatePrintAttributes(printer.getCapabilities());
-                                        capabilitiesChanged = true;
-                                    }
-                                } else if (!mCurrentPrinter.getCapabilities().equals(
-                                        printer.getCapabilities())) {
-                                    capabilitiesChanged = true;
-                                }
-
-                                // Update the UI if the status changed.
-                                final boolean statusChanged = mCurrentPrinter.getStatus()
-                                        != printer.getStatus();
-
-                                // Update the printer with the latest info.
-                                if (!mCurrentPrinter.equals(printer)) {
-                                    mCurrentPrinter.copyFrom(printer);
-                                }
-
-                                if (capabilitiesChanged || statusChanged) {
-                                    // If something changed during update...
-                                    if (updateUi() || !mController.hasPerformedLayout()) {
-                                        // Update the document.
-                                        mController.update();
-                                    }
-                                }
-
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void onInvalidated() {
-                    /* do nothing - we always have one fake PDF printer */
-                }
-            });
-
-            // Media size.
-            mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-
-            // Color mode.
-            mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-
-            // Orientation
-            mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-            String[] orientationLabels = getResources().getStringArray(
-                  R.array.orientation_labels);
-            mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
-                    ORIENTATION_PORTRAIT, orientationLabels[0]));
-            mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
-                    ORIENTATION_LANDSCAPE, orientationLabels[1]));
-
-            // Range options
-            mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-            final int[] rangeOptionsValues = getResources().getIntArray(
-                    R.array.page_options_values);
-            String[] rangeOptionsLabels = getResources().getStringArray(
-                    R.array.page_options_labels);
-            final int rangeOptionsCount = rangeOptionsLabels.length;
-            for (int i = 0; i < rangeOptionsCount; i++) {
-                mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
-                        rangeOptionsValues[i], rangeOptionsLabels[i]));
-            }
-
-            showUi(UI_EDITING_PRINT_JOB, null);
-            bindUi();
-            updateUi();
-        }
-
-        public void reselectCurrentPrinter() {
-            if (mCurrentPrinter != null) {
-                // TODO: While the data did not change and we set the adapter
-                // to a newly inflated spinner, the latter does not show the
-                // current item unless we poke the adapter. This requires more
-                // investigation. Maybe an optimization in AdapterView does not
-                // call into the adapter if the view is not visible which is the
-                // case when we set the adapter.
-                mDestinationSpinnerAdapter.notifyDataSetChanged();
-                final int position = mDestinationSpinnerAdapter.getPrinterIndex(
-                        mCurrentPrinter.getId());
-                mDestinationSpinner.setSelection(position);
-            }
-        }
-
-        public void refreshCurrentPrinter() {
-            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-            if (printer != null) {
-                FusedPrintersProvider printersLoader = (FusedPrintersProvider)
-                        (Loader<?>) getLoaderManager().getLoader(
-                                LOADER_ID_PRINTERS_LOADER);
-                if (printersLoader != null) {
-                    printersLoader.setTrackedPrinter(printer.getId());
-                }
-            }
-        }
-
-        public void addCurrentPrinterToHistory() {
-            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-            PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId();
-            if (printer != null && !printer.getId().equals(fakePdfPritnerId)) {
-                FusedPrintersProvider printersLoader = (FusedPrintersProvider)
-                        (Loader<?>) getLoaderManager().getLoader(
-                                LOADER_ID_PRINTERS_LOADER);
-                if (printersLoader != null) {
-                    printersLoader.addHistoricalPrinter(printer);
-                }
-            }
-        }
-
-        public void updateFromAdvancedOptions(PrintJobInfo printJobInfo) {
-            boolean updateContent = false;
-
-            // Copies.
-            mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
-
-            // Media size and orientation
-            PrintAttributes attributes = printJobInfo.getAttributes();
-            if (!mCurrPrintAttributes.getMediaSize().equals(attributes.getMediaSize())) {
-                final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
-                for (int i = 0; i < mediaSizeCount; i++) {
-                    MediaSize mediaSize = mMediaSizeSpinnerAdapter.getItem(i).value;
-                    if (mediaSize.asPortrait().equals(attributes.getMediaSize().asPortrait())) {
-                        updateContent = true;
-                        mCurrPrintAttributes.setMediaSize(attributes.getMediaSize());
-                        mMediaSizeSpinner.setSelection(i);
-                        mIgnoreNextMediaSizeChange = true;
-                        if (attributes.getMediaSize().isPortrait()) {
-                            mOrientationSpinner.setSelection(0);
-                            mIgnoreNextOrientationChange = true;
-                        } else {
-                            mOrientationSpinner.setSelection(1);
-                            mIgnoreNextOrientationChange = true;
-                        }
-                        break;
-                    }
-                }
-            }
-
-            // Color mode.
-            final int colorMode = attributes.getColorMode();
-            if (mCurrPrintAttributes.getColorMode() != colorMode) {
-                if (colorMode == PrintAttributes.COLOR_MODE_MONOCHROME) {
-                    updateContent = true;
-                    mColorModeSpinner.setSelection(0);
-                    mIgnoreNextColorChange = true;
-                    mCurrPrintAttributes.setColorMode(attributes.getColorMode());
-                } else if (colorMode == PrintAttributes.COLOR_MODE_COLOR) {
-                    updateContent = true;
-                    mColorModeSpinner.setSelection(1);
-                    mIgnoreNextColorChange = true;
-                    mCurrPrintAttributes.setColorMode(attributes.getColorMode());
-                }
-            }
-
-            // Range.
-            PageRange[] pageRanges = printJobInfo.getPages();
-            if (pageRanges != null && pageRanges.length > 0) {
-                pageRanges = PageRangeUtils.normalize(pageRanges);
-                final int pageRangeCount = pageRanges.length;
-                if (pageRangeCount == 1 && pageRanges[0] == PageRange.ALL_PAGES) {
-                    mRangeOptionsSpinner.setSelection(0);
-                } else {
-                    final int pageCount = mDocument.info.getPageCount();
-                    if (pageRanges[0].getStart() >= 0
-                            && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
-                        mRangeOptionsSpinner.setSelection(1);
-                        StringBuilder builder = new StringBuilder();
-                        for (int i = 0; i < pageRangeCount; i++) {
-                            if (builder.length() > 0) {
-                                builder.append(',');
-                            }
-                            PageRange pageRange = pageRanges[i];
-                            final int shownStartPage = pageRange.getStart() + 1;
-                            final int shownEndPage = pageRange.getEnd() + 1;
-                            builder.append(shownStartPage);
-                            if (shownStartPage != shownEndPage) {
-                                builder.append('-');
-                                builder.append(shownEndPage);
-                            }
-                        }
-                        mPageRangeEditText.setText(builder.toString());
-                    }
-                }
-            }
-
-            // Update the advanced options.
-            mSpoolerProvider.getSpooler().setPrintJobAdvancedOptionsNoPersistence(
-                    mPrintJobId, printJobInfo.getAdvancedOptions());
-
-            // Update the content if needed.
-            if (updateContent) {
-                mController.update();
-            }
-        }
-
-        public void ensurePrinterSelected(PrinterId printerId) {
-            // If the printer is not present maybe the loader is not
-            // updated yet. In this case make a note and as soon as
-            // the printer appears will will select it.
-            if (!selectPrinter(printerId)) {
-                mNextPrinterId = printerId;
-            }
-        }
-
-        public boolean selectPrinter(PrinterId printerId) {
-            mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
-            final int position = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
-            if (position != AdapterView.INVALID_POSITION
-                    && position != mDestinationSpinner.getSelectedItemPosition()) {
-                Object item = mDestinationSpinnerAdapter.getItem(position);
-                mCurrentPrinter = (PrinterInfo) item;
-                mDestinationSpinner.setSelection(position);
-                return true;
-            }
-            return false;
-        }
-
-        public void ensureCurrentPrinterSelected() {
-            if (mCurrentPrinter != null) {
-                selectPrinter(mCurrentPrinter.getId());
-            }
-        }
-
-        public boolean isPrintingToPdf() {
-            return mDestinationSpinner.getSelectedItem()
-                    == mDestinationSpinnerAdapter.mFakePdfPrinter;
-        }
-
-        public boolean shouldCloseOnTouch(MotionEvent event) {
-            if (event.getAction() != MotionEvent.ACTION_DOWN) {
-                return false;
-            }
-
-            final int[] locationInWindow = new int[2];
-            mContentContainer.getLocationInWindow(locationInWindow);
-
-            final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this)
-                    .getScaledWindowTouchSlop();
-            final int eventX = (int) event.getX();
-            final int eventY = (int) event.getY();
-            final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop;
-            final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth()
-                    + windowTouchSlop;
-            final int lenientWindowTop = locationInWindow[1] - windowTouchSlop;
-            final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight()
-                    + windowTouchSlop;
-
-            if (eventX < lenientWindowLeft || eventX > lenientWindowRight
-                    || eventY < lenientWindowTop || eventY > lenientWindowBottom) {
-                return true;
-            }
-            return false;
-        }
-
-        public boolean isShwoingGeneratingPrintJobUi() {
-            return (mCurrentUi == UI_GENERATING_PRINT_JOB);
-        }
-
-        public void showUi(int ui, final Runnable postSwitchCallback) {
-            if (ui == UI_NONE) {
-                throw new IllegalStateException("cannot remove the ui");
-            }
-
-            if (mCurrentUi == ui) {
-                return;
-            }
-
-            final int oldUi = mCurrentUi;
-            mCurrentUi = ui;
-
-            switch (oldUi) {
-                case UI_NONE: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            doUiSwitch(R.layout.print_job_config_activity_content_editing);
-                            registerPrintButtonClickListener();
-                            if (postSwitchCallback != null) {
-                                postSwitchCallback.run();
-                            }
-                        } break;
-
-                        case UI_GENERATING_PRINT_JOB: {
-                            doUiSwitch(R.layout.print_job_config_activity_content_generating);
-                            registerCancelButtonClickListener();
-                            if (postSwitchCallback != null) {
-                                postSwitchCallback.run();
-                            }
-                        } break;
-                    }
-                } break;
-
-                case UI_EDITING_PRINT_JOB: {
-                    switch (ui) {
-                        case UI_GENERATING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_generating,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerCancelButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-
-                        case UI_ERROR: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerOkButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-
-                case UI_GENERATING_PRINT_JOB: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerPrintButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
-                        } break;
-
-                        case UI_ERROR: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerOkButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-
-                case UI_ERROR: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerPrintButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-            }
-        }
-
-        private void registerAdvancedPrintOptionsButtonClickListener() {
-            Button advancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
-            advancedOptionsButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
-                    String activityName = getAdvancedOptionsActivityName(serviceName);
-                    if (TextUtils.isEmpty(activityName)) {
-                        return;
-                    }
-                    Intent intent = new Intent(Intent.ACTION_MAIN);
-                    intent.setComponent(new ComponentName(serviceName.getPackageName(),
-                            activityName));
-
-                    List<ResolveInfo> resolvedActivities = getPackageManager()
-                            .queryIntentActivities(intent, 0);
-                    if (resolvedActivities.isEmpty()) {
-                        return;
-                    }
-                    // The activity is a component name, therefore it is one or none.
-                    if (resolvedActivities.get(0).activityInfo.exported) {
-                        PrintJobInfo printJobInfo = mSpoolerProvider.getSpooler().getPrintJobInfo(
-                                mPrintJobId, PrintManager.APP_ID_ANY);
-                        intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobInfo);
-                        // TODO: Make this an API for the next release.
-                        intent.putExtra("android.intent.extra.print.EXTRA_PRINTER_INFO",
-                                mCurrentPrinter);
-                        try {
-                            startActivityForResult(intent,
-                                    ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS);
-                        } catch (ActivityNotFoundException anfe) {
-                            Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
-                        }
-                    }
-                }
-            });
-        }
-
-        private void registerPrintButtonClickListener() {
-            Button printButton = (Button) findViewById(R.id.print_button);
-            printButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-                    if (printer != null) {
-                        mEditor.confirmPrint();
-                        mController.update();
-                        if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
-                            mEditor.refreshCurrentPrinter();
-                        }
-                    } else {
-                        mEditor.cancel();
-                        PrintJobConfigActivity.this.finish();
-                    }
-                }
-            });
-        }
-
-        private void registerCancelButtonClickListener() {
-            Button cancelButton = (Button) findViewById(R.id.cancel_button);
-            cancelButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (!mController.isWorking()) {
-                        PrintJobConfigActivity.this.finish();
-                    }
-                    mEditor.cancel();
-                }
-            });
-        }
-
-        private void registerOkButtonClickListener() {
-            Button okButton = (Button) findViewById(R.id.ok_button);
-            okButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() {
-                        @Override
-                        public void run() {
-                            // Start over with a clean slate.
-                            mOldPrintAttributes.clear();
-                            mController.initialize();
-                            mEditor.initialize();
-                            mEditor.bindUi();
-                            mEditor.reselectCurrentPrinter();
-                            if (!mController.hasPerformedLayout()) {
-                                mController.update();
-                            }
-                        }
-                    });
-                }
-            });
-        }
-
-        private void doUiSwitch(int showLayoutId) {
-            ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
-            contentContainer.removeAllViews();
-            getLayoutInflater().inflate(showLayoutId, contentContainer, true);
-        }
-
-        private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction,
-                final LayoutParams containerParams) {
-            // Find everything we will shuffle around.
-            final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
-            final View hidingView = contentContainer.getChildAt(0);
-            final View showingView = getLayoutInflater().inflate(showLayoutId,
-                    null, false);
-
-            // First animation - fade out the old content.
-            AutoCancellingAnimator.animate(hidingView).alpha(0.0f)
-                    .withLayer().withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    hidingView.setVisibility(View.INVISIBLE);
-
-                    // Prepare the new content with correct size and alpha.
-                    showingView.setMinimumWidth(contentContainer.getWidth());
-                    showingView.setAlpha(0.0f);
-
-                    // Compute how to much shrink /stretch the content.
-                    final int widthSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
-                    final int heightSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
-                    showingView.measure(widthSpec, heightSpec);
-                    final float scaleY = (float) showingView.getMeasuredHeight()
-                            / (float) contentContainer.getHeight();
-
-                    // Second animation - resize the container.
-                    AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY)
-                            .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            // Swap the old and the new content.
-                            contentContainer.removeAllViews();
-                            contentContainer.setScaleY(1.0f);
-                            contentContainer.addView(showingView);
-
-                            contentContainer.setLayoutParams(containerParams);
-
-                            beforeShowNewUiAction.run();
-
-                            // Third animation - show the new content.
-                            AutoCancellingAnimator.animate(showingView).alpha(1.0f);
-                        }
-                    });
-                }
-            });
-        }
-
-        public void initialize() {
-            mEditorState = EDITOR_STATE_INITIALIZED;
-        }
-
-        public boolean isCancelled() {
-            return mEditorState == EDITOR_STATE_CANCELLED;
-        }
-
-        public void cancel() {
-            mEditorState = EDITOR_STATE_CANCELLED;
-            mController.cancel();
-            updateUi();
-        }
-
-        public boolean isDone() {
-            return isPrintConfirmed() || isCancelled();
-        }
-
-        public boolean isPrintConfirmed() {
-            return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
-        }
-
-        public void confirmPrint() {
-            addCurrentPrinterToHistory();
-            mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
-            showUi(UI_GENERATING_PRINT_JOB, null);
-        }
-
-        public PageRange[] getRequestedPages() {
-            if (hasErrors()) {
-                return null;
-            }
-            if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
-                List<PageRange> pageRanges = new ArrayList<PageRange>();
-                mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
-
-                while (mStringCommaSplitter.hasNext()) {
-                    String range = mStringCommaSplitter.next().trim();
-                    if (TextUtils.isEmpty(range)) {
-                        continue;
-                    }
-                    final int dashIndex = range.indexOf('-');
-                    final int fromIndex;
-                    final int toIndex;
-
-                    if (dashIndex > 0) {
-                        fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
-                        // It is possible that the dash is at the end since the input
-                        // verification can has to allow the user to keep entering if
-                        // this would lead to a valid input. So we handle this.
-                        toIndex = (dashIndex < range.length() - 1)
-                                ? Integer.parseInt(range.substring(dashIndex + 1,
-                                        range.length()).trim()) - 1 : fromIndex;
-                    } else {
-                        fromIndex = toIndex = Integer.parseInt(range) - 1;
-                    }
-
-                    PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
-                            Math.max(fromIndex, toIndex));
-                    pageRanges.add(pageRange);
-                }
-
-                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
-                pageRanges.toArray(pageRangesArray);
-
-                return PageRangeUtils.normalize(pageRangesArray);
-            }
-
-            return ALL_PAGES_ARRAY;
-        }
-
-        private void bindUi() {
-            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
-                return;
-            }
-
-            // Content container
-            mContentContainer = findViewById(R.id.content_container);
-
-            // Copies
-            mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
-            mCopiesEditText.setOnFocusChangeListener(mFocusListener);
-            mCopiesEditText.setText(MIN_COPIES_STRING);
-            mCopiesEditText.setSelection(mCopiesEditText.getText().length());
-            mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
-            if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
-                mIgnoreNextCopiesChange = true;
-            }
-            mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
-                    mPrintJobId, MIN_COPIES);
-
-            // Destination.
-            mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
-            mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT);
-            mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
-            mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mDestinationSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextDestinationChange = true;
-            }
-
-            // Media size.
-            mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
-            mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
-            mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mMediaSizeSpinnerAdapter.getCount() > 0) {
-                mOldMediaSizeSelectionIndex = 0;
-            }
-
-            // Color mode.
-            mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
-            mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
-            mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mColorModeSpinnerAdapter.getCount() > 0) {
-                mOldColorModeSelectionIndex = 0;
-            }
-
-            // Orientation
-            mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
-            mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
-            mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mOrientationSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextOrientationChange = true;
-            }
-
-            // Range options
-            mRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title);
-            mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
-            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
-            mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextRangeOptionChange = true;
-            }
-
-            // Page range
-            mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
-            mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
-            mPageRangeEditText.setOnFocusChangeListener(mFocusListener);
-            mPageRangeEditText.addTextChangedListener(mRangeTextWatcher);
-
-            // Advanced options button.
-            mAdvancedPrintOptionsContainer = findViewById(R.id.advanced_settings_container);
-            mAdvancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
-            registerAdvancedPrintOptionsButtonClickListener();
-
-            // Print button
-            mPrintButton = (Button) findViewById(R.id.print_button);
-            registerPrintButtonClickListener();
-        }
-
-        public boolean updateUi() {
-            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
-                return false;
-            }
-            if (isPrintConfirmed() || isCancelled()) {
-                mDestinationSpinner.setEnabled(false);
-                mCopiesEditText.setEnabled(false);
-                mMediaSizeSpinner.setEnabled(false);
-                mColorModeSpinner.setEnabled(false);
-                mOrientationSpinner.setEnabled(false);
-                mRangeOptionsSpinner.setEnabled(false);
-                mPageRangeEditText.setEnabled(false);
-                mPrintButton.setEnabled(false);
-                mAdvancedOptionsButton.setEnabled(false);
-                return false;
-            }
-
-            // If a printer with capabilities is selected, then we enabled all options.
-            boolean allOptionsEnabled = false;
-            final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
-            if (selectedIndex >= 0) {
-                Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
-                if (item instanceof PrinterInfo) {
-                    PrinterInfo printer = (PrinterInfo) item;
-                    if (printer.getCapabilities() != null
-                            && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
-                        allOptionsEnabled = true;
-                    }
-                }
-            }
-
-            if (!allOptionsEnabled) {
-                mCopiesEditText.setEnabled(false);
-                mMediaSizeSpinner.setEnabled(false);
-                mColorModeSpinner.setEnabled(false);
-                mOrientationSpinner.setEnabled(false);
-                mRangeOptionsSpinner.setEnabled(false);
-                mPageRangeEditText.setEnabled(false);
-                mPrintButton.setEnabled(false);
-                mAdvancedOptionsButton.setEnabled(false);
-                return false;
-            } else {
-                boolean someAttributeSelectionChanged = false;
-
-                PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-                PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
-                PrintAttributes defaultAttributes = printer.getCapabilities().getDefaults();
-
-                // Media size.
-                // Sort the media sizes based on the current locale.
-                List<MediaSize> mediaSizes = new ArrayList<MediaSize>(capabilities.getMediaSizes());
-                Collections.sort(mediaSizes, mMediaSizeComparator);
-
-                // If the media sizes changed, we update the adapter and the spinner.
-                boolean mediaSizesChanged = false;
-                final int mediaSizeCount = mediaSizes.size();
-                if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
-                    mediaSizesChanged = true;
-                } else {
-                    for (int i = 0; i < mediaSizeCount; i++) {
-                        if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
-                            mediaSizesChanged = true;
-                            break;
-                        }
-                    }
-                }
-                if (mediaSizesChanged) {
-                    // Remember the old media size to try selecting it again.
-                    int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
-                    MediaSize oldMediaSize = mCurrPrintAttributes.getMediaSize();
-
-                    // Rebuild the adapter data.
-                    mMediaSizeSpinnerAdapter.clear();
-                    for (int i = 0; i < mediaSizeCount; i++) {
-                        MediaSize mediaSize = mediaSizes.get(i);
-                        if (mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
-                            // Update the index of the old selection.
-                            oldMediaSizeNewIndex = i;
-                        }
-                        mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
-                                mediaSize, mediaSize.getLabel(getPackageManager())));
-                    }
-
-                    mMediaSizeSpinner.setEnabled(true);
-
-                    if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
-                        // Select the old media size - nothing really changed.
-                        setMediaSizeSpinnerSelectionNoCallback(oldMediaSizeNewIndex);
-                    } else {
-                        // Select the first or the default and mark if selection changed.
-                        final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
-                                defaultAttributes.getMediaSize()), 0);
-                        setMediaSizeSpinnerSelectionNoCallback(mediaSizeIndex);
-                        if (oldMediaSize.isPortrait()) {
-                            mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
-                                    .getItem(mediaSizeIndex).value.asPortrait());
-                        } else {
-                            mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
-                                    .getItem(mediaSizeIndex).value.asLandscape());
-                        }
-                        someAttributeSelectionChanged = true;
-                    }
-                }
-                mMediaSizeSpinner.setEnabled(true);
-
-                // Color mode.
-                final int colorModes = capabilities.getColorModes();
-
-                // If the color modes changed, we update the adapter and the spinner.
-                boolean colorModesChanged = false;
-                if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
-                    colorModesChanged = true;
-                } else {
-                    int remainingColorModes = colorModes;
-                    int adapterIndex = 0;
-                    while (remainingColorModes != 0) {
-                        final int colorBitOffset = Integer.numberOfTrailingZeros(
-                                remainingColorModes);
-                        final int colorMode = 1 << colorBitOffset;
-                        remainingColorModes &= ~colorMode;
-                        if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
-                            colorModesChanged = true;
-                            break;
-                        }
-                        adapterIndex++;
-                    }
-                }
-                if (colorModesChanged) {
-                    // Remember the old color mode to try selecting it again.
-                    int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
-                    final int oldColorMode = mCurrPrintAttributes.getColorMode();
-
-                    // Rebuild the adapter data.
-                    mColorModeSpinnerAdapter.clear();
-                    String[] colorModeLabels = getResources().getStringArray(
-                            R.array.color_mode_labels);
-                    int remainingColorModes = colorModes;
-                    while (remainingColorModes != 0) {
-                        final int colorBitOffset = Integer.numberOfTrailingZeros(
-                                remainingColorModes);
-                        final int colorMode = 1 << colorBitOffset;
-                        if (colorMode == oldColorMode) {
-                            // Update the index of the old selection.
-                            oldColorModeNewIndex = colorBitOffset;
-                        }
-                        remainingColorModes &= ~colorMode;
-                        mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
-                                colorModeLabels[colorBitOffset]));
-                    }
-                    mColorModeSpinner.setEnabled(true);
-                    if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
-                        // Select the old color mode - nothing really changed.
-                        setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex);
-                    } else {
-                        final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
-                        final int itemCount = mColorModeSpinnerAdapter.getCount();
-                        for (int i = 0; i < itemCount; i++) {
-                            SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
-                            if (selectedColorMode == item.value) {
-                                setColorModeSpinnerSelectionNoCallback(i);
-                                mCurrPrintAttributes.setColorMode(selectedColorMode);
-                                someAttributeSelectionChanged = true;
-                            }
-                        }
-                    }
-                }
-                mColorModeSpinner.setEnabled(true);
-
-                // Orientation
-                MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
-                if (mediaSize.isPortrait()
-                        && mOrientationSpinner.getSelectedItemPosition() != 0) {
-                    mIgnoreNextOrientationChange = true;
-                    mOrientationSpinner.setSelection(0);
-                } else if (!mediaSize.isPortrait()
-                        && mOrientationSpinner.getSelectedItemPosition() != 1) {
-                    mIgnoreNextOrientationChange = true;
-                    mOrientationSpinner.setSelection(1);
-                }
-                mOrientationSpinner.setEnabled(true);
-
-                // Range options
-                PrintDocumentInfo info = mDocument.info;
-                if (info != null && info.getPageCount() > 0) {
-                    if (info.getPageCount() == 1) {
-                        mRangeOptionsSpinner.setEnabled(false);
-                    } else {
-                        mRangeOptionsSpinner.setEnabled(true);
-                        if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
-                            if (!mPageRangeEditText.isEnabled()) {
-                                mPageRangeEditText.setEnabled(true);
-                                mPageRangeEditText.setVisibility(View.VISIBLE);
-                                mPageRangeTitle.setVisibility(View.VISIBLE);
-                                mPageRangeEditText.requestFocus();
-                                InputMethodManager imm = (InputMethodManager)
-                                        getSystemService(INPUT_METHOD_SERVICE);
-                                imm.showSoftInput(mPageRangeEditText, 0);
-                            }
-                        } else {
-                            mPageRangeEditText.setEnabled(false);
-                            mPageRangeEditText.setVisibility(View.INVISIBLE);
-                            mPageRangeTitle.setVisibility(View.INVISIBLE);
-                        }
-                    }
-                    final int pageCount = mDocument.info.getPageCount();
-                    String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
-                            ? getString(R.string.label_pages, String.valueOf(pageCount))
-                            : getString(R.string.page_count_unknown);
-                    mRangeOptionsTitle.setText(title);
-                } else {
-                    if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
-                        mIgnoreNextRangeOptionChange = true;
-                        mRangeOptionsSpinner.setSelection(0);
-                    }
-                    mRangeOptionsSpinner.setEnabled(false);
-                    mRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
-                    mPageRangeEditText.setEnabled(false);
-                    mPageRangeEditText.setVisibility(View.INVISIBLE);
-                    mPageRangeTitle.setVisibility(View.INVISIBLE);
-                }
-
-                // Advanced print options
-                ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
-                if (!TextUtils.isEmpty(getAdvancedOptionsActivityName(serviceName))) {
-                    mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
-                    mAdvancedOptionsButton.setEnabled(true);
-                } else {
-                    mAdvancedPrintOptionsContainer.setVisibility(View.GONE);
-                    mAdvancedOptionsButton.setEnabled(false);
-                }
-
-                // Print
-                if (mDestinationSpinner.getSelectedItemId()
-                        != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
-                    String newText = getString(R.string.print_button);
-                    if (!TextUtils.equals(newText, mPrintButton.getText())) {
-                        mPrintButton.setText(R.string.print_button);
-                    }
-                } else {
-                    String newText = getString(R.string.save_button);
-                    if (!TextUtils.equals(newText, mPrintButton.getText())) {
-                        mPrintButton.setText(R.string.save_button);
-                    }
-                }
-                if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
-                            && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
-                        || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
-                            && (!mController.hasPerformedLayout() || hasErrors()))) {
-                    mPrintButton.setEnabled(false);
-                } else {
-                    mPrintButton.setEnabled(true);
-                }
-
-                // Copies
-                if (mDestinationSpinner.getSelectedItemId()
-                        != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
-                    mCopiesEditText.setEnabled(true);
-                } else {
-                    mCopiesEditText.setEnabled(false);
-                }
-                if (mCopiesEditText.getError() == null
-                        && TextUtils.isEmpty(mCopiesEditText.getText())) {
-                    mIgnoreNextCopiesChange = true;
-                    mCopiesEditText.setText(String.valueOf(MIN_COPIES));
-                    mCopiesEditText.requestFocus();
-                }
-
-                return someAttributeSelectionChanged;
-            }
-        }
-
-        private String getAdvancedOptionsActivityName(ComponentName serviceName) {
-            PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-            List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
-            final int printServiceCount = printServices.size();
-            for (int i = 0; i < printServiceCount; i ++) {
-                PrintServiceInfo printServiceInfo = printServices.get(i);
-                ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
-                if (serviceInfo.name.equals(serviceName.getClassName())
-                        && serviceInfo.packageName.equals(serviceName.getPackageName())) {
-                    return printServiceInfo.getAdvancedOptionsActivityName();
-                }
-            }
-            return null;
-        }
-
-        private void setMediaSizeSpinnerSelectionNoCallback(int position) {
-            if (mMediaSizeSpinner.getSelectedItemPosition() != position) {
-                mOldMediaSizeSelectionIndex = position;
-                mMediaSizeSpinner.setSelection(position);
-            }
-        }
-
-        private void setColorModeSpinnerSelectionNoCallback(int position) {
-            if (mColorModeSpinner.getSelectedItemPosition() != position) {
-                mOldColorModeSelectionIndex = position;
-                mColorModeSpinner.setSelection(position);
-            }
-        }
-
-        private void startSelectPrinterActivity() {
-            Intent intent = new Intent(PrintJobConfigActivity.this,
-                    SelectPrinterActivity.class);
-            startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
-        }
-
-        private boolean hasErrors() {
-            if (mCopiesEditText.getError() != null) {
-                return true;
-            }
-            return mPageRangeEditText.getVisibility() == View.VISIBLE
-                    && mPageRangeEditText.getError() != null;
-        }
-
-        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 WaitForPrinterCapabilitiesTimeout implements Runnable {
-            private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec
-
-            private boolean mIsPosted;
-
-            public void post() {
-                if (!mIsPosted) {
-                    mDestinationSpinner.postDelayed(this,
-                            GET_CAPABILITIES_TIMEOUT_MILLIS);
-                    mIsPosted = true;
-                }
-            }
-
-            public void remove() {
-                if (mIsPosted) {
-                    mIsPosted = false;
-                    mDestinationSpinner.removeCallbacks(this);
-                }
-            }
-
-            public boolean isPosted() {
-                return mIsPosted;
-            }
-
-            @Override
-            public void run() {
-                mIsPosted = false;
-                if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
-                    View itemView = mDestinationSpinner.getSelectedView();
-                    TextView titleView = (TextView) itemView.findViewById(R.id.subtitle);
-                    try {
-                        PackageInfo packageInfo = getPackageManager().getPackageInfo(
-                                mCurrentPrinter.getId().getServiceName().getPackageName(), 0);
-                        CharSequence service = packageInfo.applicationInfo.loadLabel(
-                                getPackageManager());
-                        String subtitle = getString(R.string.printer_unavailable, service.toString());
-                        titleView.setText(subtitle);
-                    } catch (NameNotFoundException nnfe) {
-                        /* ignore */
-                    }
-                }
-            }
-        }
-
-        private final class DestinationAdapter extends BaseAdapter
-                implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
-            private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
-            private PrinterInfo mFakePdfPrinter;
-
-            public DestinationAdapter() {
-                getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
-            }
-
-            public int getPrinterIndex(PrinterId printerId) {
-                for (int i = 0; i < getCount(); i++) {
-                    PrinterInfo printer = (PrinterInfo) getItem(i);
-                    if (printer != null && printer.getId().equals(printerId)) {
-                        return i;
-                    }
-                }
-                return AdapterView.INVALID_POSITION;
-            }
-
-            public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
-                final int printerCount = mPrinters.size();
-                for (int i = 0; i < printerCount; i++) {
-                    PrinterInfo printer = (PrinterInfo) mPrinters.get(i);
-                    if (printer.getId().equals(printerId)) {
-                        // If already in the list - do nothing.
-                        if (i < getCount() - 2) {
-                            return;
-                        }
-                        // Else replace the last one (two items are not printers).
-                        final int lastPrinterIndex = getCount() - 3;
-                        mPrinters.set(i, mPrinters.get(lastPrinterIndex));
-                        mPrinters.set(lastPrinterIndex, printer);
-                        notifyDataSetChanged();
-                        return;
-                    }
-                }
-            }
-
-            @Override
-            public int getCount() {
-                if (mFakePdfPrinter == null) {
-                    return 0;
-                }
-                return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
-            }
-
-            @Override
-            public boolean isEnabled(int position) {
-                Object item = getItem(position);
-                if (item instanceof PrinterInfo) {
-                    PrinterInfo printer = (PrinterInfo) item;
-                    return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
-                }
-                return true;
-            }
-
-            @Override
-            public Object getItem(int position) {
-                if (mPrinters.isEmpty()) {
-                    if (position == 0 && mFakePdfPrinter != null) {
-                        return mFakePdfPrinter;
-                    }
-                } else {
-                    if (position < 1) {
-                        return mPrinters.get(position);
-                    }
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        return mFakePdfPrinter;
-                    }
-                    if (position < getCount() - 1) {
-                        return mPrinters.get(position - 1);
-                    }
-                }
-                return null;
-            }
-
-            @Override
-            public long getItemId(int position) {
-                if (mPrinters.isEmpty()) {
-                    if (mFakePdfPrinter != null) {
-                        if (position == 0) {
-                            return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
-                        } else if (position == 1) {
-                            return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
-                        }
-                    }
-                } else {
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
-                    }
-                    if (position == getCount() - 1) {
-                        return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
-                    }
-                }
-                return position;
-            }
-
-            @Override
-            public View getDropDownView(int position, View convertView,
-                    ViewGroup parent) {
-                View view = getView(position, convertView, parent);
-                view.setEnabled(isEnabled(position));
-                return view;
-            }
-
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                if (convertView == null) {
-                    convertView = getLayoutInflater().inflate(
-                            R.layout.printer_dropdown_item, parent, false);
-                }
-
-                CharSequence title = null;
-                CharSequence subtitle = null;
-                Drawable icon = null;
-
-                if (mPrinters.isEmpty()) {
-                    if (position == 0 && mFakePdfPrinter != null) {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                    } else if (position == 1) {
-                        title = getString(R.string.all_printers);
-                    }
-                } else {
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                    } else if (position == getCount() - 1) {
-                        title = getString(R.string.all_printers);
-                    } else {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                        try {
-                            PackageInfo packageInfo = getPackageManager().getPackageInfo(
-                                    printer.getId().getServiceName().getPackageName(), 0);
-                            subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
-                            icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
-                        } catch (NameNotFoundException nnfe) {
-                            /* ignore */
-                        }
-                    }
-                }
-
-                TextView titleView = (TextView) convertView.findViewById(R.id.title);
-                titleView.setText(title);
-
-                TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
-                if (!TextUtils.isEmpty(subtitle)) {
-                    subtitleView.setText(subtitle);
-                    subtitleView.setVisibility(View.VISIBLE);
-                } else {
-                    subtitleView.setText(null);
-                    subtitleView.setVisibility(View.GONE);
-                }
-
-                ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
-                if (icon != null) {
-                    iconView.setImageDrawable(icon);
-                    iconView.setVisibility(View.VISIBLE);
-                } else {
-                    iconView.setVisibility(View.INVISIBLE);
-                }
-
-                return convertView;
-            }
-
-            @Override
-            public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
-                if (id == LOADER_ID_PRINTERS_LOADER) {
-                    return new FusedPrintersProvider(PrintJobConfigActivity.this);
-                }
-                return null;
-            }
-
-            @Override
-            public void onLoadFinished(Loader<List<PrinterInfo>> loader,
-                    List<PrinterInfo> printers) {
-                // If this is the first load, create the fake PDF printer.
-                // We do this to avoid flicker where the PDF printer is the
-                // only one and as soon as the loader loads the favorites
-                // it gets switched. Not a great user experience.
-                if (mFakePdfPrinter == null) {
-                    mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter();
-                    updatePrintAttributes(mCurrentPrinter.getCapabilities());
-                    updateUi();
-                }
-
-                // We rearrange the printers if the user selects a printer
-                // not shown in the initial short list. Therefore, we have
-                // to keep the printer order.
-
-                // No old printers - do not bother keeping their position.
-                if (mPrinters.isEmpty()) {
-                    mPrinters.addAll(printers);
-                    mEditor.ensureCurrentPrinterSelected();
-                    notifyDataSetChanged();
-                    return;
-                }
-
-                // Add the new printers to a map.
-                ArrayMap<PrinterId, PrinterInfo> newPrintersMap =
-                        new ArrayMap<PrinterId, PrinterInfo>();
-                final int printerCount = printers.size();
-                for (int i = 0; i < printerCount; i++) {
-                    PrinterInfo printer = printers.get(i);
-                    newPrintersMap.put(printer.getId(), printer);
-                }
-
-                List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>();
-
-                // Update printers we already have.
-                final int oldPrinterCount = mPrinters.size();
-                for (int i = 0; i < oldPrinterCount; i++) {
-                    PrinterId oldPrinterId = mPrinters.get(i).getId();
-                    PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
-                    if (updatedPrinter != null) {
-                        newPrinters.add(updatedPrinter);
-                    }
-                }
-
-                // Add the rest of the new printers, i.e. what is left.
-                newPrinters.addAll(newPrintersMap.values());
-
-                mPrinters.clear();
-                mPrinters.addAll(newPrinters);
-
-                mEditor.ensureCurrentPrinterSelected();
-                notifyDataSetChanged();
-            }
-
-            @Override
-            public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
-                mPrinters.clear();
-                notifyDataSetInvalidated();
-            }
-
-
-            private PrinterInfo createFakePdfPrinter() {
-                MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintJobConfigActivity.this);
-
-                PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
-
-                PrinterCapabilitiesInfo.Builder builder =
-                        new PrinterCapabilitiesInfo.Builder(printerId);
-
-                String[] mediaSizeIds = getResources().getStringArray(
-                        R.array.pdf_printer_media_sizes);
-                final int mediaSizeIdCount = mediaSizeIds.length;
-                for (int i = 0; i < mediaSizeIdCount; i++) {
-                    String id = mediaSizeIds[i];
-                    MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
-                    builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
-                }
-
-                builder.addResolution(new Resolution("PDF resolution", "PDF resolution",
-                            300, 300), true);
-                builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
-                        | PrintAttributes.COLOR_MODE_MONOCHROME,
-                        PrintAttributes.COLOR_MODE_COLOR);
-
-                return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
-                        PrinterInfo.STATUS_IDLE)
-                    .setCapabilities(builder.build())
-                    .build();
-            }
-        }
-    }
-
-    /**
-     * 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[] ourRanges, PageRange[] otherRanges) {
-            if (ourRanges == null || otherRanges == null) {
-                return false;
-            }
-
-            if (ourRanges.length == 1
-                    && PageRange.ALL_PAGES.equals(ourRanges[0])) {
-                return true;
-            }
-
-            ourRanges = normalize(ourRanges);
-            otherRanges = normalize(otherRanges);
-
-            // Note that the code below relies on the ranges being normalized
-            // which is they contain monotonically increasing non-intersecting
-            // subranges whose start is less that or equal to the end.
-            int otherRangeIdx = 0;
-            final int ourRangeCount = ourRanges.length;
-            final int otherRangeCount = otherRanges.length;
-            for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
-                PageRange ourRange = ourRanges[ourRangeIdx];
-                for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
-                    PageRange otherRange = otherRanges[otherRangeIdx];
-                    if (otherRange.getStart() > ourRange.getEnd()) {
-                        break;
-                    }
-                    if (otherRange.getStart() < ourRange.getStart()
-                            || otherRange.getEnd() > ourRange.getEnd()) {
-                        return false;
-                    }
-                }
-            }
-            if (otherRangeIdx < otherRangeCount) {
-                return false;
-            }
-            return true;
-        }
-
-        public static PageRange[] normalize(PageRange[] pageRanges) {
-            if (pageRanges == null) {
-                return null;
-            }
-            final int oldRangeCount = pageRanges.length;
-            if (oldRangeCount <= 1) {
-                return pageRanges;
-            }
-            Arrays.sort(pageRanges, sComparator);
-            int newRangeCount = 1;
-            for (int i = 0; i < oldRangeCount - 1; i++) {
-                newRangeCount++;
-                PageRange currentRange = pageRanges[i];
-                PageRange nextRange = pageRanges[i + 1];
-                if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
-                    newRangeCount--;
-                    pageRanges[i] = null;
-                    pageRanges[i + 1] = new PageRange(currentRange.getStart(),
-                            Math.max(currentRange.getEnd(), nextRange.getEnd()));
-                }
-            }
-            if (newRangeCount == oldRangeCount) {
-                return pageRanges;
-            }
-            return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
-                    oldRangeCount);
-        }
-
-        public static void offset(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);
-            }
-        }
-    }
-
-    private static final class AutoCancellingAnimator
-            implements OnAttachStateChangeListener, Runnable {
-
-        private ViewPropertyAnimator mAnimator;
-
-        private boolean mCancelled;
-        private Runnable mEndCallback;
-
-        public static AutoCancellingAnimator animate(View view) {
-            ViewPropertyAnimator animator = view.animate();
-            AutoCancellingAnimator cancellingWrapper =
-                    new AutoCancellingAnimator(animator);
-            view.addOnAttachStateChangeListener(cancellingWrapper);
-            return cancellingWrapper;
-        }
-
-        private AutoCancellingAnimator(ViewPropertyAnimator animator) {
-            mAnimator = animator;
-        }
-
-        public AutoCancellingAnimator alpha(float alpha) {
-            mAnimator = mAnimator.alpha(alpha);
-            return this;
-        }
-
-        public void cancel() {
-            mAnimator.cancel();
-        }
-
-        public AutoCancellingAnimator withLayer() {
-            mAnimator = mAnimator.withLayer();
-            return this;
-        }
-
-        public AutoCancellingAnimator withEndAction(Runnable callback) {
-            mEndCallback = callback;
-            mAnimator = mAnimator.withEndAction(this);
-            return this;
-        }
-
-        public AutoCancellingAnimator scaleY(float scale) {
-            mAnimator = mAnimator.scaleY(scale);
-            return this;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            /* do nothing */
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            cancel();
-        }
-
-        @Override
-        public void run() {
-            if (!mCancelled) {
-                mEndCallback.run();
-            }
-        }
-    }
-
-    private static final class PrintSpoolerProvider implements ServiceConnection {
-        private final Context mContext;
-        private final Runnable mCallback;
-
-        private PrintSpoolerService mSpooler;
-
-        public PrintSpoolerProvider(Context context, Runnable callback) {
-            mContext = context;
-            mCallback = callback;
-            Intent intent = new Intent(mContext, PrintSpoolerService.class);
-            mContext.bindService(intent, this, 0);
-        }
-
-        public PrintSpoolerService getSpooler() {
-            return mSpooler;
-        }
-
-        public void destroy() {
-            if (mSpooler != null) {
-                mContext.unbindService(this);
-            }
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
-            if (mSpooler != null) {
-                mCallback.run();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            /* do noting - we are in the same process */
-        }
-    }
-
-    private static final class PrintDocumentAdapterObserver
-            extends IPrintDocumentAdapterObserver.Stub {
-        private final WeakReference<PrintJobConfigActivity> mWeakActvity;
-
-        public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) {
-            mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity);
-        }
-
-        @Override
-        public void onDestroy() {
-            final PrintJobConfigActivity activity = mWeakActvity.get();
-            if (activity != null) {
-                activity.mController.mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (activity.mController != null) {
-                            activity.mController.cancel();
-                        }
-                        if (activity.mEditor != null) {
-                            activity.mEditor.cancel();
-                        }
-                        activity.finish();
-                    }
-                });
-            }
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
deleted file mode 100644
index d9ccb5d..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ /dev/null
@@ -1,151 +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.printspooler;
-
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.print.ILayoutResultCallback;
-import android.print.IPrintDocumentAdapter;
-import android.print.IWriteResultCallback;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.util.Log;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * This class represents a remote print document adapter instance.
- */
-final class RemotePrintDocumentAdapter {
-    private static final String LOG_TAG = "RemotePrintDocumentAdapter";
-
-    private static final boolean DEBUG = false;
-
-    private final IPrintDocumentAdapter mRemoteInterface;
-
-    private final File mFile;
-
-    public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
-        mRemoteInterface = printAdatper;
-        mFile = file;
-    }
-
-    public void start()  {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "start()");
-        }
-        try {
-            mRemoteInterface.start();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling start()", re);
-        }
-    }
-
-    public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
-            ILayoutResultCallback callback, Bundle metadata, int sequence) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "layout()");
-        }
-        try {
-            mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling layout()", re);
-        }
-    }
-
-    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) {
-                InputStream in = null;
-                OutputStream out = null;
-                ParcelFileDescriptor source = null;
-                ParcelFileDescriptor sink = null;
-                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(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 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;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    public void finish() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "finish()");
-        }
-        try {
-            mRemoteInterface.finish();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling finish()", re);
-        }
-    }
-
-    public void cancel() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "cancel()");
-        }
-        try {
-            mRemoteInterface.cancel();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling cancel()", re);
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
deleted file mode 100644
index 141dbd1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
+++ /dev/null
@@ -1,41 +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.printspooler;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.print.PrinterId;
-
-import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
-
-public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.select_printer_activity);
-    }
-
-    @Override
-    public void onPrinterSelected(PrinterId printer) {
-        Intent intent = new Intent();
-        intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
-        setResult(RESULT_OK, intent);
-        finish();
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
similarity index 99%
rename from packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
rename to packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 968a8bf..929f0fc 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.model;
 
 import android.app.Notification;
 import android.app.Notification.InboxStyle;
@@ -38,6 +38,8 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.printspooler.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -45,7 +47,7 @@
  * This class is responsible for updating the print notifications
  * based on print job state transitions.
  */
-public class NotificationController {
+final class NotificationController {
     public static final boolean DEBUG = false;
 
     public static final String LOG_TAG = "NotificationController";
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
new file mode 100644
index 0000000..06723c3
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.printspooler.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+public class PrintSpoolerProvider implements ServiceConnection {
+    private final Context mContext;
+    private final Runnable mCallback;
+
+    private PrintSpoolerService mSpooler;
+
+    public PrintSpoolerProvider(Context context, Runnable callback) {
+        mContext = context;
+        mCallback = callback;
+        Intent intent = new Intent(mContext, PrintSpoolerService.class);
+        mContext.bindService(intent, this, 0);
+    }
+
+    public PrintSpoolerService getSpooler() {
+        return mSpooler;
+    }
+
+    public void destroy() {
+        if (mSpooler != null) {
+            mContext.unbindService(this);
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
+        if (mSpooler != null) {
+            mCallback.run();
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        /* do nothing - we are in the same process */
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
similarity index 95%
rename from packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
rename to packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 615d667..045a2f9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.model;
 
 import android.app.Service;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -38,7 +39,6 @@
 import android.print.PrintJobInfo;
 import android.print.PrintManager;
 import android.print.PrinterId;
-import android.print.PrinterInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
@@ -48,6 +48,7 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.R;
 
 import libcore.io.IoUtils;
 
@@ -89,7 +90,7 @@
 
     private final Object mLock = new Object();
 
-    private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+    private final List<PrintJobInfo> mPrintJobs = new ArrayList<>();
 
     private static PrintSpoolerService sInstance;
 
@@ -274,7 +275,7 @@
                             && isScheduledState(printJob.getState()));
                 if (sameComponent && sameAppId && sameState) {
                     if (foundPrintJobs == null) {
-                        foundPrintJobs = new ArrayList<PrintJobInfo>();
+                        foundPrintJobs = new ArrayList<>();
                     }
                     foundPrintJobs.add(printJob);
                 }
@@ -400,7 +401,7 @@
                 FileOutputStream out = null;
                 try {
                     if (printJob != null) {
-                        File file = generateFileForPrintJob(printJobId);
+                        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
                         in = new FileInputStream(file);
                         out = new FileOutputStream(fd.getFileDescriptor());
                     }
@@ -427,8 +428,8 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
 
-    public File generateFileForPrintJob(PrintJobId printJobId) {
-        return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
+    public static File generateFileForPrintJob(Context context, PrintJobId printJobId) {
+        return new File(context.getFilesDir(), PRINT_JOB_FILE_PREFIX
                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
     }
 
@@ -461,7 +462,7 @@
     }
 
     private void removePrintJobFileLocked(PrintJobId printJobId) {
-        File file = generateFileForPrintJob(printJobId);
+        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
         if (file.exists()) {
             file.delete();
             if (DEBUG_PRINT_JOB_LIFECYCLE) {
@@ -557,7 +558,7 @@
     }
 
     private boolean isObsoleteState(int printJobState) {
-        return (isTeminalState(printJobState)
+        return (isTerminalState(printJobState)
                 || printJobState == PrintJobInfo.STATE_QUEUED);
     }
 
@@ -574,7 +575,7 @@
                 || printJobState == PrintJobInfo.STATE_BLOCKED;
     }
 
-    private boolean isTeminalState(int printJobState) {
+    private boolean isTerminalState(int printJobState) {
         return printJobState == PrintJobInfo.STATE_COMPLETED
                 || printJobState == PrintJobInfo.STATE_CANCELED;
     }
@@ -619,61 +620,23 @@
         }
     }
 
-    public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
+    public void updatePrintJobUserConfigurableOptionsNoPersistence(PrintJobInfo printJob) {
         synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setCopies(copies);
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo cachedPrintJob = mPrintJobs.get(i);
+                if (cachedPrintJob.getId().equals(printJob.getId())) {
+                    cachedPrintJob.setPrinterId(printJob.getPrinterId());
+                    cachedPrintJob.setPrinterName(printJob.getPrinterName());
+                    cachedPrintJob.setCopies(printJob.getCopies());
+                    cachedPrintJob.setDocumentInfo(printJob.getDocumentInfo());
+                    cachedPrintJob.setPages(printJob.getPages());
+                    cachedPrintJob.setAttributes(printJob.getAttributes());
+                    cachedPrintJob.setAdvancedOptions(printJob.getAdvancedOptions());
+                    return;
+                }
             }
-        }
-    }
-
-    public void setPrintJobAdvancedOptionsNoPersistence(PrintJobId printJobId,
-            Bundle advancedOptions) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setAdvancedOptions(advancedOptions);
-            }
-        }
-    }
-
-    public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId,
-            PrintDocumentInfo info) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setDocumentInfo(info);
-            }
-        }
-    }
-
-    public void setPrintJobAttributesNoPersistence(PrintJobId printJobId,
-            PrintAttributes attributes) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setAttributes(attributes);
-            }
-        }
-    }
-
-    public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPrinterId(printer.getId());
-                printJob.setPrinterName(printer.getName());
-            }
-        }
-    }
-
-    public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPages(pages);
-            }
+            throw new IllegalArgumentException("No print job with id:" + printJob.getId());
         }
     }
 
@@ -1250,7 +1213,7 @@
         }
     }
 
-    final class PrintSpooler extends IPrintSpooler.Stub {
+    public final class PrintSpooler extends IPrintSpooler.Stub {
         @Override
         public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
                 ComponentName componentName, int state, int appId, int sequence)
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
new file mode 100644
index 0000000..e70c361
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -0,0 +1,1148 @@
+/*
+ * Copyright (C) 2014 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.printspooler.model;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.ILayoutResultCallback;
+import android.print.IPrintDocumentAdapter;
+import android.print.IPrintDocumentAdapterObserver;
+import android.print.IWriteResultCallback;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentInfo;
+import android.util.Log;
+
+import com.android.printspooler.R;
+import com.android.printspooler.util.PageRangeUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+
+public final class RemotePrintDocument {
+    private static final String LOG_TAG = "RemotePrintDocument";
+
+    private static final boolean DEBUG = false;
+
+    private static final int STATE_INITIAL = 0;
+    private static final int STATE_STARTED = 1;
+    private static final int STATE_UPDATING = 2;
+    private static final int STATE_UPDATED = 3;
+    private static final int STATE_FAILED = 4;
+    private static final int STATE_FINISHED = 5;
+    private static final int STATE_CANCELING = 6;
+    private static final int STATE_CANCELED = 7;
+    private static final int STATE_DESTROYED = 8;
+
+    private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {
+            PageRange.ALL_PAGES
+    };
+
+    private final Context mContext;
+
+    private final RemotePrintDocumentInfo mDocumentInfo;
+    private final UpdateSpec mUpdateSpec = new UpdateSpec();
+
+    private final Looper mLooper;
+    private final IPrintDocumentAdapter mPrintDocumentAdapter;
+    private final DocumentObserver mDocumentObserver;
+
+    private final UpdateResultCallbacks mUpdateCallbacks;
+
+    private final CommandDoneCallback mCommandResultCallback =
+            new CommandDoneCallback() {
+        @Override
+        public void onDone() {
+            if (mCurrentCommand.isCompleted()) {
+                if (mCurrentCommand instanceof LayoutCommand) {
+                    // If there is a next command after a layout is done, then another
+                    // update was issued and the next command is another layout, so we
+                    // do nothing. However, if there is no next command we may need to
+                    // ask for some pages given we do not already have them or we do
+                    // but the content has changed.
+                    LayoutCommand layoutCommand = (LayoutCommand) mCurrentCommand;
+                    if (mNextCommand == null) {
+                        if (layoutCommand.isDocumentChanged() || !PageRangeUtils.contains(
+                                mDocumentInfo.writtenPages, mUpdateSpec.pages)) {
+                            mNextCommand = new WriteCommand(mContext, mLooper,
+                                    mPrintDocumentAdapter, mDocumentInfo,
+                                    mDocumentInfo.info.getPageCount(), mUpdateSpec.pages,
+                                    mDocumentInfo.file, mCommandResultCallback);
+                        } else {
+                            // If we have the requested pages just update that ones to be printed.
+                            mDocumentInfo.printedPages = computePrintedPages(mUpdateSpec.pages,
+                                    mDocumentInfo.writtenPages, mDocumentInfo.info.getPageCount());
+                            // Notify we are done.
+                            notifyUpdateCompleted();
+                        }
+                    }
+                } else {
+                    // We always notify after a write.
+                    notifyUpdateCompleted();
+                }
+                runPendingCommand();
+            } else if (mCurrentCommand.isFailed()) {
+                mState = STATE_FAILED;
+                CharSequence error = mCurrentCommand.getError();
+                mCurrentCommand = null;
+                mNextCommand = null;
+                mUpdateSpec.reset();
+                notifyUpdateFailed(error);
+            } else if (mCurrentCommand.isCanceled()) {
+                if (mState == STATE_CANCELING) {
+                    mState = STATE_CANCELED;
+                    notifyUpdateCanceled();
+                }
+                runPendingCommand();
+            }
+        }
+    };
+
+    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+        @Override
+        public void binderDied() {
+            finish();
+        }
+    };
+
+    private int mState = STATE_INITIAL;
+
+    private AsyncCommand mCurrentCommand;
+    private AsyncCommand mNextCommand;
+
+    public interface DocumentObserver {
+        public void onDestroy();
+    }
+
+    public interface UpdateResultCallbacks {
+        public void onUpdateCompleted(RemotePrintDocumentInfo document);
+        public void onUpdateCanceled();
+        public void onUpdateFailed(CharSequence error);
+    }
+
+    public RemotePrintDocument(Context context, IPrintDocumentAdapter adapter,
+            File file, DocumentObserver destroyListener, UpdateResultCallbacks callbacks) {
+        mPrintDocumentAdapter = adapter;
+        mLooper = context.getMainLooper();
+        mContext = context;
+        mDocumentObserver = destroyListener;
+        mDocumentInfo = new RemotePrintDocumentInfo();
+        mDocumentInfo.file = file;
+        mUpdateCallbacks = callbacks;
+        connectToRemoteDocument();
+    }
+
+    public void start() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] start()");
+        }
+        if (mState != STATE_INITIAL) {
+            throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
+        }
+        try {
+            mPrintDocumentAdapter.start();
+            mState = STATE_STARTED;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling start()", re);
+            mState = STATE_FAILED;
+            mDocumentObserver.onDestroy();
+        }
+    }
+
+    public boolean update(PrintAttributes attributes, PageRange[] pages, boolean preview) {
+        boolean willUpdate;
+
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] update()");
+        }
+
+        if (hasUpdateError()) {
+            throw new IllegalStateException("Cannot update without a clearing the failure");
+        }
+
+        if (mState == STATE_INITIAL || mState == STATE_FINISHED || mState == STATE_DESTROYED) {
+            throw new IllegalStateException("Cannot update in state:" + stateToString(mState));
+        }
+
+        // We schedule a layout if the constraints changed.
+        if (!mUpdateSpec.hasSameConstraints(attributes, preview)) {
+            willUpdate = true;
+
+            // If there is a current command that is running we ask for a
+            // cancellation and start over.
+            if (mCurrentCommand != null && (mCurrentCommand.isRunning()
+                    || mCurrentCommand.isPending())) {
+                mCurrentCommand.cancel();
+            }
+
+            // Schedule a layout command.
+            PrintAttributes oldAttributes = mDocumentInfo.attributes != null
+                    ? mDocumentInfo.attributes : new PrintAttributes.Builder().build();
+            AsyncCommand command = new LayoutCommand(mLooper, mPrintDocumentAdapter,
+                  mDocumentInfo, oldAttributes, attributes, preview, mCommandResultCallback);
+            scheduleCommand(command);
+
+            mState = STATE_UPDATING;
+        // If no layout in progress and we don't have all pages - schedule a write.
+        } else if ((!(mCurrentCommand instanceof LayoutCommand)
+                || (!mCurrentCommand.isPending() && !mCurrentCommand.isRunning()))
+                && !PageRangeUtils.contains(mUpdateSpec.pages, pages)) {
+            willUpdate = true;
+
+            // Cancel the current write as a new one is to be scheduled.
+            if (mCurrentCommand instanceof WriteCommand
+                    && (mCurrentCommand.isPending() || mCurrentCommand.isRunning())) {
+                mCurrentCommand.cancel();
+            }
+
+            // Schedule a write command.
+            AsyncCommand command = new WriteCommand(mContext, mLooper, mPrintDocumentAdapter,
+                    mDocumentInfo, mDocumentInfo.info.getPageCount(), pages,
+                    mDocumentInfo.file, mCommandResultCallback);
+            scheduleCommand(command);
+
+            mState = STATE_UPDATING;
+        } else {
+            willUpdate = false;
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[SKIPPING] No update needed");
+            }
+        }
+
+        // Keep track of what is requested.
+        mUpdateSpec.update(attributes, preview, pages);
+
+        runPendingCommand();
+
+        return willUpdate;
+    }
+
+    public void finish() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] finish()");
+        }
+        if (mState != STATE_STARTED && mState != STATE_UPDATED
+                && mState != STATE_FAILED && mState != STATE_CANCELING) {
+            throw new IllegalStateException("Cannot finish in state:"
+                    + stateToString(mState));
+        }
+        try {
+            mPrintDocumentAdapter.finish();
+            mState = STATE_FINISHED;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling finish()", re);
+            mState = STATE_FAILED;
+            mDocumentObserver.onDestroy();
+        }
+    }
+
+    public void cancel() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] cancel()");
+        }
+
+        if (mState == STATE_CANCELING) {
+            return;
+        }
+
+        if (mState != STATE_UPDATING) {
+            throw new IllegalStateException("Cannot cancel in state:" + stateToString(mState));
+        }
+
+        mState = STATE_CANCELING;
+
+        mCurrentCommand.cancel();
+    }
+
+    public void destroy() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] destroy()");
+        }
+        if (mState == STATE_DESTROYED) {
+            throw new IllegalStateException("Cannot destroy in state:" + stateToString(mState));
+        }
+
+        mState = STATE_DESTROYED;
+
+        disconnectFromRemoteDocument();
+        mDocumentObserver.onDestroy();
+    }
+
+    public boolean isUpdating() {
+        return mState == STATE_UPDATING || mState == STATE_CANCELING;
+    }
+
+    public boolean isDestroyed() {
+        return mState == STATE_DESTROYED;
+    }
+
+    public boolean hasUpdateError() {
+        return mState == STATE_FAILED;
+    }
+
+    public void clearUpdateError() {
+        if (!hasUpdateError()) {
+            throw new IllegalStateException("No update error to clear");
+        }
+        mState = STATE_STARTED;
+    }
+
+    public RemotePrintDocumentInfo getDocumentInfo() {
+        return mDocumentInfo;
+    }
+
+    public void writeContent(ContentResolver contentResolver, Uri uri) {
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = new FileInputStream(mDocumentInfo.file);
+            out = contentResolver.openOutputStream(uri);
+            final byte[] buffer = new byte[8192];
+            while (true) {
+                final int readByteCount = in.read(buffer);
+                if (readByteCount < 0) {
+                    break;
+                }
+                out.write(buffer, 0, readByteCount);
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error writing document content.", e);
+        } finally {
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private void notifyUpdateCanceled() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()");
+        }
+        mUpdateCallbacks.onUpdateCanceled();
+    }
+
+    private void notifyUpdateCompleted() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
+        }
+        mUpdateCallbacks.onUpdateCompleted(mDocumentInfo);
+    }
+
+    private void notifyUpdateFailed(CharSequence error) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
+        }
+        mUpdateCallbacks.onUpdateFailed(error);
+    }
+
+    private void connectToRemoteDocument() {
+        try {
+            mPrintDocumentAdapter.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "The printing process is dead.");
+            destroy();
+            return;
+        }
+
+        try {
+            mPrintDocumentAdapter.setObserver(new PrintDocumentAdapterObserver(this));
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error setting observer to the print adapter.");
+            destroy();
+        }
+    }
+
+    private void disconnectFromRemoteDocument() {
+        try {
+            mPrintDocumentAdapter.setObserver(null);
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error setting observer to the print adapter.");
+            // Keep going - best effort...
+        }
+
+        mPrintDocumentAdapter.asBinder().unlinkToDeath(mDeathRecipient, 0);
+    }
+
+    private void scheduleCommand(AsyncCommand command) {
+        if (mCurrentCommand == null) {
+            mCurrentCommand = command;
+        } else {
+            mNextCommand = command;
+        }
+    }
+
+    private void runPendingCommand() {
+        if (mCurrentCommand != null
+                && (mCurrentCommand.isCompleted()
+                        || mCurrentCommand.isCanceled())) {
+            mCurrentCommand = mNextCommand;
+            mNextCommand = null;
+        }
+
+        if (mCurrentCommand != null) {
+            if (mCurrentCommand.isPending()) {
+                mCurrentCommand.run();
+            }
+            mState = STATE_UPDATING;
+        } else {
+            mState = STATE_UPDATED;
+        }
+    }
+
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_FINISHED: {
+                return "STATE_FINISHED";
+            }
+            case STATE_FAILED: {
+                return "STATE_FAILED";
+            }
+            case STATE_STARTED: {
+                return "STATE_STARTED";
+            }
+            case STATE_UPDATING: {
+                return "STATE_UPDATING";
+            }
+            case STATE_UPDATED: {
+                return "STATE_UPDATED";
+            }
+            case STATE_CANCELING: {
+                return "STATE_CANCELING";
+            }
+            case STATE_CANCELED: {
+                return "STATE_CANCELED";
+            }
+            case STATE_DESTROYED: {
+                return "STATE_DESTROYED";
+            }
+            default: {
+                return "STATE_UNKNOWN";
+            }
+        }
+    }
+
+    private static PageRange[] computePrintedPages(PageRange[] requestedPages,
+                                                   PageRange[] writtenPages, int pageCount) {
+        // 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(writtenPages, requestedPages)) {
+            // We got a document with exactly the pages we wanted. Hence,
+            // the printer has to print all pages in the data.
+            return ALL_PAGES_ARRAY;
+        } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
+            // We requested specific pages but got all of them. Hence,
+            // the printer has to print only the requested pages.
+            return requestedPages;
+        } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
+            // We requested specific pages and got more but not all pages.
+            // Hence, we have to offset appropriately the printed pages to
+            // be based off the start of the written ones instead of zero.
+            // The written pages are always non-null and not empty.
+            final int offset = -writtenPages[0].getStart();
+            PageRangeUtils.offset(requestedPages, offset);
+            return requestedPages;
+        } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
+                && isAllPages(writtenPages, pageCount)) {
+            // 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.
+            return ALL_PAGES_ARRAY;
+        }
+
+        return null;
+    }
+
+    private static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
+        return pageRanges.length > 0 && pageRanges[0].getStart() == 0
+                && pageRanges[pageRanges.length - 1].getEnd() == pageCount - 1;
+    }
+
+    static final class UpdateSpec {
+        final PrintAttributes attributes = new PrintAttributes.Builder().build();
+        boolean preview;
+        PageRange[] pages;
+
+        public void update(PrintAttributes attributes, boolean preview,
+                PageRange[] pages) {
+            this.attributes.copyFrom(attributes);
+            this.preview = preview;
+            this.pages = Arrays.copyOf(pages, pages.length);
+        }
+
+        public void reset() {
+            attributes.clear();
+            preview = false;
+            pages = null;
+        }
+
+        public boolean hasSameConstraints(PrintAttributes attributes, boolean preview) {
+            return this.attributes.equals(attributes) && this.preview == preview;
+        }
+    }
+
+    public static final class RemotePrintDocumentInfo {
+        public PrintAttributes attributes;
+        public Bundle metadata;
+        public PrintDocumentInfo info;
+        public PageRange[] printedPages;
+        public PageRange[] writtenPages;
+        public File file;
+    }
+
+    private interface CommandDoneCallback {
+        public void onDone();
+    }
+
+    private static abstract class AsyncCommand implements Runnable {
+        private static final int STATE_PENDING = 0;
+        private static final int STATE_RUNNING = 1;
+        private static final int STATE_COMPLETED = 2;
+        private static final int STATE_CANCELED = 3;
+        private static final int STATE_CANCELING = 4;
+        private static final int STATE_FAILED = 5;
+
+        private static int sSequenceCounter;
+
+        protected final int mSequence = sSequenceCounter++;
+        protected final IPrintDocumentAdapter mAdapter;
+        protected final RemotePrintDocumentInfo mDocument;
+
+        protected final CommandDoneCallback mDoneCallback;
+
+        protected ICancellationSignal mCancellation;
+
+        private CharSequence mError;
+
+        private int mState = STATE_PENDING;
+
+        public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document,
+                CommandDoneCallback doneCallback) {
+            mAdapter = adapter;
+            mDocument = document;
+            mDoneCallback = doneCallback;
+        }
+
+        protected final boolean isCanceling() {
+            return mState == STATE_CANCELING;
+        }
+
+        public final boolean isCanceled() {
+            return mState == STATE_CANCELED;
+        }
+
+        public final void cancel() {
+            if (isRunning()) {
+                canceling();
+                if (mCancellation != null) {
+                    try {
+                        mCancellation.cancel();
+                    } catch (RemoteException re) {
+                        Log.w(LOG_TAG, "Error while canceling", re);
+                    }
+                }
+            } else {
+                canceled();
+
+                // Done.
+                mDoneCallback.onDone();
+            }
+        }
+
+        protected final void canceling() {
+            if (mState != STATE_PENDING && mState != STATE_RUNNING) {
+                throw new IllegalStateException("Command not pending or running.");
+            }
+            mState = STATE_CANCELING;
+        }
+
+        protected final void canceled() {
+            if (mState != STATE_CANCELING) {
+                throw new IllegalStateException("Not canceling.");
+            }
+            mState = STATE_CANCELED;
+        }
+
+        public final boolean isPending() {
+            return mState == STATE_PENDING;
+        }
+
+        protected final void running() {
+            if (mState != STATE_PENDING) {
+                throw new IllegalStateException("Not pending.");
+            }
+            mState = STATE_RUNNING;
+        }
+
+        public final boolean isRunning() {
+            return mState == STATE_RUNNING;
+        }
+
+        protected final void completed() {
+            if (mState != STATE_RUNNING && mState != STATE_CANCELING) {
+                throw new IllegalStateException("Not running.");
+            }
+            mState = STATE_COMPLETED;
+        }
+
+        public final boolean isCompleted() {
+            return mState == STATE_COMPLETED;
+        }
+
+        protected final void failed(CharSequence error) {
+            if (mState != STATE_RUNNING) {
+                throw new IllegalStateException("Not running.");
+            }
+            mState = STATE_FAILED;
+
+            mError = error;
+        }
+
+        public final boolean isFailed() {
+            return mState == STATE_FAILED;
+        }
+
+        public CharSequence getError() {
+            return mError;
+        }
+    }
+
+    private static final class LayoutCommand extends AsyncCommand {
+        private final PrintAttributes mOldAttributes = new PrintAttributes.Builder().build();
+        private final PrintAttributes mNewAttributes = new PrintAttributes.Builder().build();
+        private final Bundle mMetadata = new Bundle();
+
+        private final ILayoutResultCallback mRemoteResultCallback;
+
+        private final Handler mHandler;
+
+        private boolean mDocumentChanged;
+
+        public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter,
+                RemotePrintDocumentInfo document, PrintAttributes oldAttributes,
+                PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) {
+            super(adapter, document, callback);
+            mHandler = new LayoutHandler(looper);
+            mRemoteResultCallback = new LayoutResultCallback(mHandler);
+            mOldAttributes.copyFrom(oldAttributes);
+            mNewAttributes.copyFrom(newAttributes);
+            mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview);
+        }
+
+        public boolean isDocumentChanged() {
+            return mDocumentChanged;
+        }
+
+        @Override
+        public void run() {
+            running();
+
+            try {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "[PERFORMING] layout");
+                }
+                mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback,
+                        mMetadata, mSequence);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error calling layout", re);
+                handleOnLayoutFailed(null, mSequence);
+            }
+        }
+
+        private void handleOnLayoutStarted(ICancellationSignal cancellation, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutStarted");
+            }
+
+            if (isCanceling()) {
+                try {
+                    cancellation.cancel();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error cancelling", re);
+                    handleOnLayoutFailed(null, mSequence);
+                }
+            } else {
+                mCancellation = cancellation;
+            }
+        }
+
+        private void handleOnLayoutFinished(PrintDocumentInfo info,
+                boolean changed, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutFinished");
+            }
+
+            completed();
+
+            // If the document description changed or the content in the
+            // document changed, the we need to invalidate the pages.
+            if (changed || !equalsIgnoreSize(mDocument.info, info)) {
+                // If the content changed we throw away all pages as
+                // we will request them again with the new content.
+                mDocument.writtenPages = null;
+                mDocument.printedPages = null;
+                mDocumentChanged = true;
+            }
+
+            // Update the document with data from the layout pass.
+            mDocument.attributes = mNewAttributes;
+            mDocument.metadata = mMetadata;
+            mDocument.info = info;
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnLayoutFailed(CharSequence error, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed");
+            }
+
+            failed(error);
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Failed.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnLayoutCanceled(int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutCanceled");
+            }
+
+            canceled();
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
+            if (lhs == rhs) {
+                return true;
+            }
+            if (lhs == null) {
+                return false;
+            } else {
+                if (rhs == null) {
+                    return false;
+                }
+                if (lhs.getContentType() != rhs.getContentType()
+                        || lhs.getPageCount() != rhs.getPageCount()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private final class LayoutHandler extends Handler {
+            public static final int MSG_ON_LAYOUT_STARTED = 1;
+            public static final int MSG_ON_LAYOUT_FINISHED = 2;
+            public static final int MSG_ON_LAYOUT_FAILED = 3;
+            public static final int MSG_ON_LAYOUT_CANCELED = 4;
+
+            public LayoutHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_ON_LAYOUT_STARTED: {
+                        ICancellationSignal cancellation = (ICancellationSignal) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnLayoutStarted(cancellation, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_FINISHED: {
+                        PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
+                        final boolean changed = (message.arg1 == 1);
+                        final int sequence = message.arg2;
+                        handleOnLayoutFinished(info, changed, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnLayoutFailed(error, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_CANCELED: {
+                        final int sequence = message.arg1;
+                        handleOnLayoutCanceled(sequence);
+                    } break;
+                }
+            }
+        }
+
+        private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
+            private final WeakReference<Handler> mWeakHandler;
+
+            public LayoutResultCallback(Handler handler) {
+                mWeakHandler = new WeakReference<>(handler);
+            }
+
+            @Override
+            public void onLayoutStarted(ICancellationSignal cancellation, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_STARTED,
+                            sequence, 0, cancellation).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FINISHED,
+                            changed ? 1 : 0, sequence, info).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutFailed(CharSequence error, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FAILED,
+                            sequence, 0, error).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutCanceled(int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_CANCELED,
+                            sequence, 0).sendToTarget();
+                }
+            }
+        }
+    }
+
+    private static final class WriteCommand extends AsyncCommand {
+        private final int mPageCount;
+        private final PageRange[] mPages;
+        private final File mContentFile;
+
+        private final IWriteResultCallback mRemoteResultCallback;
+        private final CommandDoneCallback mDoneCallback;
+
+        private final Context mContext;
+        private final Handler mHandler;
+
+        public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter,
+                RemotePrintDocumentInfo document, int pageCount, PageRange[] pages,
+                File contentFile, CommandDoneCallback callback) {
+            super(adapter, document, callback);
+            mContext = context;
+            mHandler = new WriteHandler(looper);
+            mRemoteResultCallback = new WriteResultCallback(mHandler);
+            mPageCount = pageCount;
+            mPages = Arrays.copyOf(pages, pages.length);
+            mContentFile = contentFile;
+            mDoneCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            running();
+
+            // This is a long running operation as we will be reading fully
+            // the written data. In case of a cancellation, we ask the client
+            // to stop writing data and close the file descriptor after
+            // which we will reach the end of the stream, thus stop reading.
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    InputStream in = null;
+                    OutputStream out = null;
+                    ParcelFileDescriptor source = null;
+                    ParcelFileDescriptor sink = null;
+                    try {
+                        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                        source = pipe[0];
+                        sink = pipe[1];
+
+                        in = new FileInputStream(source.getFileDescriptor());
+                        out = new FileOutputStream(mContentFile);
+
+                        // Async call to initiate the other process writing the data.
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "[PERFORMING] write");
+                        }
+                        mAdapter.write(mPages, sink, mRemoteResultCallback, mSequence);
+
+                        // 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 | IOException e) {
+                        Log.e(LOG_TAG, "Error calling write()", e);
+                    } finally {
+                        IoUtils.closeQuietly(in);
+                        IoUtils.closeQuietly(out);
+                        IoUtils.closeQuietly(sink);
+                        IoUtils.closeQuietly(source);
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        }
+
+        private void handleOnWriteStarted(ICancellationSignal cancellation, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteStarted");
+            }
+
+            if (isCanceling()) {
+                try {
+                    cancellation.cancel();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error cancelling", re);
+                    handleOnWriteFailed(null, sequence);
+                }
+            } else {
+                mCancellation = cancellation;
+            }
+        }
+
+        private void handleOnWriteFinished(PageRange[] pages, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteFinished");
+            }
+
+            PageRange[] writtenPages = PageRangeUtils.normalize(pages);
+            PageRange[] printedPages = computePrintedPages(mPages, writtenPages, mPageCount);
+
+            // Handle if we got invalid pages
+            if (printedPages != null) {
+                mDocument.writtenPages = writtenPages;
+                mDocument.printedPages = printedPages;
+                completed();
+            } else {
+                mDocument.writtenPages = null;
+                mDocument.printedPages = null;
+                failed(mContext.getString(R.string.print_error_default_message));
+            }
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnWriteFailed(CharSequence error, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteFailed");
+            }
+
+            failed(error);
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnWriteCanceled(int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteCanceled");
+            }
+
+            canceled();
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private final class WriteHandler extends Handler {
+            public static final int MSG_ON_WRITE_STARTED = 1;
+            public static final int MSG_ON_WRITE_FINISHED = 2;
+            public static final int MSG_ON_WRITE_FAILED = 3;
+            public static final int MSG_ON_WRITE_CANCELED = 4;
+
+            public WriteHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_ON_WRITE_STARTED: {
+                        ICancellationSignal cancellation = (ICancellationSignal) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteStarted(cancellation, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_FINISHED: {
+                        PageRange[] pages = (PageRange[]) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteFinished(pages, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteFailed(error, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_CANCELED: {
+                        final int sequence = message.arg1;
+                        handleOnWriteCanceled(sequence);
+                    } break;
+                }
+            }
+        }
+
+        private static final class WriteResultCallback extends IWriteResultCallback.Stub {
+            private final WeakReference<Handler> mWeakHandler;
+
+            public WriteResultCallback(Handler handler) {
+                mWeakHandler = new WeakReference<>(handler);
+            }
+
+            @Override
+            public void onWriteStarted(ICancellationSignal cancellation, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_STARTED,
+                            sequence, 0, cancellation).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteFinished(PageRange[] pages, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FINISHED,
+                            sequence, 0, pages).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteFailed(CharSequence error, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FAILED,
+                        sequence, 0, error).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteCanceled(int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_CANCELED,
+                        sequence, 0).sendToTarget();
+                }
+            }
+        }
+    }
+
+    private static final class PrintDocumentAdapterObserver
+            extends IPrintDocumentAdapterObserver.Stub {
+        private final WeakReference<RemotePrintDocument> mWeakDocument;
+
+        public PrintDocumentAdapterObserver(RemotePrintDocument document) {
+            mWeakDocument = new WeakReference<>(document);
+        }
+
+        @Override
+        public void onDestroy() {
+            final RemotePrintDocument document = mWeakDocument.get();
+            if (document != null) {
+                new Handler(document.mLooper).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        document.mDocumentObserver.onDestroy();
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
similarity index 98%
rename from packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
rename to packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 9831839..d802cd8 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.ui;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -57,7 +57,7 @@
  * This class is responsible for loading printers by doing discovery
  * and merging the discovered printers with the previously used ones.
  */
-public class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
     private static final String LOG_TAG = "FusedPrintersProvider";
 
     private static final boolean DEBUG = false;
@@ -192,7 +192,7 @@
             for (int i = 0; i < favoriteCount; i++) {
                 printerIds.add(mFavoritePrinters.get(i).getId());
             }
-            mDiscoverySession.startPrinterDisovery(printerIds);
+            mDiscoverySession.startPrinterDiscovery(printerIds);
             List<PrinterInfo> printers = mDiscoverySession.getPrinters();
             if (!printers.isEmpty()) {
                 updatePrinters(printers, mFavoritePrinters);
@@ -281,7 +281,9 @@
                 mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
             }
             mTrackedPrinter = printerId;
-            mDiscoverySession.startPrinterStateTracking(printerId);
+            if (printerId != null) {
+                mDiscoverySession.startPrinterStateTracking(printerId);
+            }
         }
     }
 
@@ -514,7 +516,7 @@
             }
 
             private List<PrinterInfo> doReadPrinterHistory() {
-                FileInputStream in = null;
+                final FileInputStream in;
                 try {
                     in = mStatePersistFile.openRead();
                 } catch (FileNotFoundException fnfe) {
@@ -641,7 +643,7 @@
                 }
                 return true;
             }
-        };
+        }
 
         private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
             @Override
@@ -703,6 +705,6 @@
                     IoUtils.closeQuietly(out);
                 }
             }
-        };
+        }
     }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
new file mode 100644
index 0000000..f71cafe
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -0,0 +1,1953 @@
+/*
+ * Copyright (C) 2014 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.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.print.IPrintDocumentAdapter;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentInfo;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintService;
+import android.provider.DocumentsContract;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.text.TextWatcher;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.printspooler.R;
+import com.android.printspooler.model.PrintSpoolerProvider;
+import com.android.printspooler.model.PrintSpoolerService;
+import com.android.printspooler.model.RemotePrintDocument;
+import com.android.printspooler.util.MediaSizeUtils;
+import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
+import com.android.printspooler.util.PageRangeUtils;
+import com.android.printspooler.util.PrintOptionUtils;
+import com.android.printspooler.widget.ContentView;
+import com.android.printspooler.widget.ContentView.OptionsStateChangeListener;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
+        PrintErrorFragment.OnActionListener, PrintProgressFragment.OnCancelRequestListener {
+    private static final String LOG_TAG = "PrintActivity";
+
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+    private static final int ORIENTATION_PORTRAIT = 0;
+    private static final int ORIENTATION_LANDSCAPE = 1;
+
+    private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
+    private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
+    private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
+
+    private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
+
+    private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
+    private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+
+    private static final int STATE_CONFIGURING = 0;
+    private static final int STATE_PRINT_CONFIRMED = 1;
+    private static final int STATE_PRINT_CANCELED = 2;
+    private static final int STATE_UPDATE_FAILED = 3;
+    private static final int STATE_CREATE_FILE_FAILED = 4;
+    private static final int STATE_PRINTER_UNAVAILABLE = 5;
+
+    private static final int UI_STATE_PREVIEW = 0;
+    private static final int UI_STATE_ERROR = 1;
+    private static final int UI_STATE_PROGRESS = 2;
+
+    private static final int MIN_COPIES = 1;
+    private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
+
+    private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
+
+    private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
+            "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
+
+    private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
+            "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
+            + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
+
+    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
+
+    private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
+            new PrinterAvailabilityDetector();
+
+    private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
+
+    private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
+
+    private PrintSpoolerProvider mSpoolerProvider;
+
+    private PrintJobInfo mPrintJob;
+    private RemotePrintDocument mPrintedDocument;
+    private PrinterRegistry mPrinterRegistry;
+
+    private EditText mCopiesEditText;
+
+    private TextView mPageRangeOptionsTitle;
+    private TextView mPageRangeTitle;
+    private EditText mPageRangeEditText;
+
+    private Spinner mDestinationSpinner;
+    private DestinationAdapter mDestinationSpinnerAdapter;
+
+    private Spinner mMediaSizeSpinner;
+    private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+
+    private Spinner mColorModeSpinner;
+    private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+
+    private Spinner mOrientationSpinner;
+    private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+
+    private Spinner mRangeOptionsSpinner;
+
+    private ContentView mOptionsContent;
+
+    private TextView mSummaryCopies;
+    private TextView mSummaryPaperSize;
+
+    private View mAdvancedPrintOptionsContainer;
+
+    private Button mMoreOptionsButton;
+
+    private ImageView mPrintButton;
+
+    private ProgressMessageController mProgressMessageController;
+
+    private MediaSizeComparator mMediaSizeComparator;
+
+    private PageRange[] mRequestedPages;
+
+    private PrinterInfo mOldCurrentPrinter;
+
+    private String mCallingPackageName;
+
+    private int mState = STATE_CONFIGURING;
+
+    private int mUiState = UI_STATE_PREVIEW;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Bundle extras = getIntent().getExtras();
+
+        mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
+        if (mPrintJob == null) {
+            throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
+                    + " cannot be null");
+        }
+        mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+
+        final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
+        if (adapter == null) {
+            throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
+                    + " cannot be null");
+        }
+
+        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
+
+        // This will take just a few milliseconds, so just wait to
+        // bind to the local service before showing the UI.
+        mSpoolerProvider = new PrintSpoolerProvider(this,
+                new Runnable() {
+            @Override
+            public void run() {
+                // Now that we are bound to the print spooler service,
+                // create the printer registry and wait for it to get
+                // the first batch of results which will be delivered
+                // after reading historical data. This should be pretty
+                // fast, so just wait before showing the UI.
+                mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
+                        new Runnable() {
+                    @Override
+                    public void run() {
+                        setTitle(R.string.print_dialog);
+                        setContentView(R.layout.print_activity);
+
+                        mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
+                                IPrintDocumentAdapter.Stub.asInterface(adapter),
+                                PrintSpoolerService.generateFileForPrintJob(PrintActivity.this,
+                                        mPrintJob.getId()),
+                                new RemotePrintDocument.DocumentObserver() {
+                                    @Override
+                                    public void onDestroy() {
+                                        finish();
+                                    }
+                                }, PrintActivity.this);
+
+                        mProgressMessageController = new ProgressMessageController(PrintActivity.this);
+
+                        mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
+
+                        mDestinationSpinnerAdapter = new DestinationAdapter();
+
+                        bindUi();
+
+                        updateOptionsUi();
+
+                        // Now show the updated UI to avoid flicker.
+                        mOptionsContent.setVisibility(View.VISIBLE);
+
+                        mRequestedPages = computeRequestedPages();
+
+                        mPrintedDocument.start();
+
+                        ensurePreviewUiShown();
+                    }
+                });
+            }
+        });
+    }
+
+    @Override
+    public void onPause() {
+        if (isFinishing()) {
+            PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
+            if (mState == STATE_PRINT_CONFIRMED) {
+                spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
+                spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
+            } else {
+                spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+            }
+            mProgressMessageController.cancel();
+            mPrinterRegistry.setTrackedPrinter(null);
+            mSpoolerProvider.destroy();
+            mPrintedDocument.finish();
+            mPrintedDocument.destroy();
+        }
+
+        mPrinterAvailabilityDetector.cancel();
+
+        super.onPause();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            event.startTracking();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK
+                && event.isTracking() && !event.isCanceled()) {
+            cancelPrint();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public void onActionPerformed() {
+        switch (mState) {
+            case STATE_UPDATE_FAILED: {
+                if (canUpdateDocument()) {
+                    updateDocument(true, true);
+                    ensurePreviewUiShown();
+                    mState = STATE_CONFIGURING;
+                    updateOptionsUi();
+                }
+            } break;
+
+            case STATE_CREATE_FILE_FAILED: {
+                mState = STATE_CONFIGURING;
+                ensurePreviewUiShown();
+                updateOptionsUi();
+            } break;
+        }
+    }
+
+    @Override
+    public void onCancelRequest() {
+        if (mPrintedDocument.isUpdating()) {
+            mPrintedDocument.cancel();
+        }
+    }
+
+    public void onUpdateCanceled() {
+        mProgressMessageController.cancel();
+        ensurePreviewUiShown();
+        finishIfConfirmedOrCanceled();
+        updateOptionsUi();
+    }
+
+    @Override
+    public void onUpdateCompleted(RemotePrintDocument.RemotePrintDocumentInfo document) {
+        mProgressMessageController.cancel();
+        ensurePreviewUiShown();
+
+        // Update the print job with the info for the written document. The page
+        // count we get from the remote document is the pages in the document from
+        // the app perspective but the print job should contain the page count from
+        // print service perspective which is the pages in the written PDF not the
+        // pages in the printed document.
+        PrintDocumentInfo info = document.info;
+        if (info == null) {
+            return;
+        }
+        final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
+                info.getPageCount());
+        PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
+                .setContentType(info.getContentType())
+                .setPageCount(pageCount)
+                .build();
+        mPrintJob.setDocumentInfo(adjustedInfo);
+        mPrintJob.setPages(document.printedPages);
+        finishIfConfirmedOrCanceled();
+        updateOptionsUi();
+    }
+
+    @Override
+    public void onUpdateFailed(CharSequence error) {
+        mState = STATE_UPDATE_FAILED;
+        ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
+        updateOptionsUi();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case ACTIVITY_REQUEST_CREATE_FILE: {
+                onStartCreateDocumentActivityResult(resultCode, data);
+            } break;
+
+            case ACTIVITY_REQUEST_SELECT_PRINTER: {
+                onSelectPrinterActivityResult(resultCode, data);
+            } break;
+
+            case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
+                onAdvancedPrintOptionsActivityResult(resultCode, data);
+            } break;
+        }
+    }
+
+    private void startCreateDocumentActivity() {
+        PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+        if (info == null) {
+            return;
+        }
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        intent.setType("application/pdf");
+        intent.putExtra(Intent.EXTRA_TITLE, info.getName());
+        intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
+        startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+    }
+
+    private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && data != null) {
+            Uri uri = data.getData();
+            mPrintedDocument.writeContent(getContentResolver(), uri);
+            finish();
+        } else if (resultCode == RESULT_CANCELED) {
+            mState = STATE_CONFIGURING;
+            updateOptionsUi();
+        } else {
+            ensureErrorUiShown(getString(R.string.print_write_error_message),
+                    PrintErrorFragment.ACTION_CONFIRM);
+            mState = STATE_CREATE_FILE_FAILED;
+            updateOptionsUi();
+        }
+    }
+
+    private void startSelectPrinterActivity() {
+        Intent intent = new Intent(this, SelectPrinterActivity.class);
+        startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
+    }
+
+    private void onSelectPrinterActivityResult(int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && data != null) {
+            PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
+            if (printerId != null) {
+                mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
+                final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+                if (index != AdapterView.INVALID_POSITION) {
+                    mDestinationSpinner.setSelection(index);
+                    return;
+                }
+            }
+        }
+
+        PrinterId printerId = mOldCurrentPrinter.getId();
+        final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+        mDestinationSpinner.setSelection(index);
+    }
+
+    private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
+        ComponentName serviceName = printer.getId().getServiceName();
+
+        String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName);
+        if (TextUtils.isEmpty(activityName)) {
+            return;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName));
+
+        List<ResolveInfo> resolvedActivities = getPackageManager()
+                .queryIntentActivities(intent, 0);
+        if (resolvedActivities.isEmpty()) {
+            return;
+        }
+
+        // The activity is a component name, therefore it is one or none.
+        if (resolvedActivities.get(0).activityInfo.exported) {
+            intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob);
+            intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
+
+            // This is external activity and may not be there.
+            try {
+                startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
+            } catch (ActivityNotFoundException anfe) {
+                Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
+            }
+        }
+    }
+
+    private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
+        if (resultCode != RESULT_OK || data == null) {
+            return;
+        }
+
+        PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
+
+        if (printJobInfo == null) {
+            return;
+        }
+
+        // Take the advanced options without interpretation.
+        mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
+
+        // Take copies without interpretation as the advanced print dialog
+        // cannot create a print job info with invalid copies.
+        mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
+        mPrintJob.setCopies(printJobInfo.getCopies());
+
+        PrintAttributes currAttributes = mPrintJob.getAttributes();
+        PrintAttributes newAttributes = printJobInfo.getAttributes();
+
+        // Take the media size only if the current printer supports is.
+        MediaSize oldMediaSize = currAttributes.getMediaSize();
+        MediaSize newMediaSize = newAttributes.getMediaSize();
+        if (!oldMediaSize.equals(newMediaSize)) {
+            final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
+            MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i).value.asPortrait();
+                if (supportedSizePortrait.equals(newMediaSizePortrait)) {
+                    currAttributes.setMediaSize(newMediaSize);
+                    mMediaSizeSpinner.setSelection(i);
+                    if (currAttributes.getMediaSize().isPortrait()) {
+                        if (mOrientationSpinner.getSelectedItemPosition() != 0) {
+                            mOrientationSpinner.setSelection(0);
+                        }
+                    } else {
+                        if (mOrientationSpinner.getSelectedItemPosition() != 1) {
+                            mOrientationSpinner.setSelection(1);
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+        // Take the color mode only if the current printer supports it.
+        final int currColorMode = currAttributes.getColorMode();
+        final int newColorMode = newAttributes.getColorMode();
+        if (currColorMode != newColorMode) {
+            final int colorModeCount = mColorModeSpinner.getCount();
+            for (int i = 0; i < colorModeCount; i++) {
+                final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
+                if (supportedColorMode == newColorMode) {
+                    currAttributes.setColorMode(newColorMode);
+                    mColorModeSpinner.setSelection(i);
+                    break;
+                }
+            }
+        }
+
+        // Take the page range only if it is valid.
+        PageRange[] pageRanges = printJobInfo.getPages();
+        if (pageRanges != null && pageRanges.length > 0) {
+            pageRanges = PageRangeUtils.normalize(pageRanges);
+
+            PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+            final int pageCount = (info != null) ? info.getPageCount() : 0;
+
+            // Handle the case where all pages are specified explicitly
+            // instead of the *all pages* constant.
+            if (pageRanges.length == 1) {
+                if (pageRanges[0].getStart() == 0 && pageRanges[0].getEnd() == pageCount - 1) {
+                    pageRanges[0] = PageRange.ALL_PAGES;
+                }
+            }
+
+            if (Arrays.equals(pageRanges, ALL_PAGES_ARRAY)) {
+                mPrintJob.setPages(pageRanges);
+
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                    mRangeOptionsSpinner.setSelection(0);
+                }
+            } else if (pageRanges[0].getStart() >= 0
+                    && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
+                mPrintJob.setPages(pageRanges);
+
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
+                    mRangeOptionsSpinner.setSelection(1);
+                }
+
+                StringBuilder builder = new StringBuilder();
+                final int pageRangeCount = pageRanges.length;
+                for (int i = 0; i < pageRangeCount; i++) {
+                    if (builder.length() > 0) {
+                         builder.append(',');
+                    }
+
+                    final int shownStartPage;
+                    final int shownEndPage;
+                    PageRange pageRange = pageRanges[i];
+                    if (pageRange.equals(PageRange.ALL_PAGES)) {
+                        shownStartPage = 1;
+                        shownEndPage = pageCount;
+                    } else {
+                        shownStartPage = pageRange.getStart() + 1;
+                        shownEndPage = pageRange.getEnd() + 1;
+                    }
+
+                    builder.append(shownStartPage);
+
+                    if (shownStartPage != shownEndPage) {
+                        builder.append('-');
+                        builder.append(shownEndPage);
+                    }
+                }
+                mPageRangeEditText.setText(builder.toString());
+            }
+        }
+
+        // Update the content if needed.
+        if (canUpdateDocument()) {
+            updateDocument(true, false);
+        }
+    }
+
+    private void ensureProgressUiShown() {
+        if (mUiState != UI_STATE_PROGRESS) {
+            mUiState = UI_STATE_PROGRESS;
+            Fragment fragment = PrintProgressFragment.newInstance();
+            showFragment(fragment);
+        }
+    }
+
+    private void ensurePreviewUiShown() {
+        if (mUiState != UI_STATE_PREVIEW) {
+            mUiState = UI_STATE_PREVIEW;
+            Fragment fragment = PrintPreviewFragment.newInstance();
+            showFragment(fragment);
+        }
+    }
+
+    private void ensureErrorUiShown(CharSequence message, int action) {
+        if (mUiState != UI_STATE_ERROR) {
+            mUiState = UI_STATE_ERROR;
+            Fragment fragment = PrintErrorFragment.newInstance(message, action);
+            showFragment(fragment);
+        }
+    }
+
+    private void showFragment(Fragment fragment) {
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        Fragment oldFragment = getFragmentManager().findFragmentById(
+                R.id.embedded_content_container);
+        if (oldFragment != null) {
+            transaction.remove(oldFragment);
+        }
+        transaction.add(R.id.embedded_content_container, fragment);
+        transaction.commit();
+    }
+
+    private void requestCreatePdfFileOrFinish() {
+        if (getCurrentPrinter() == mDestinationSpinnerAdapter.getPdfPrinter()) {
+            startCreateDocumentActivity();
+        } else {
+            finish();
+        }
+    }
+
+    private void finishIfConfirmedOrCanceled() {
+        if (mState == STATE_PRINT_CONFIRMED) {
+            requestCreatePdfFileOrFinish();
+        } else if (mState == STATE_PRINT_CANCELED) {
+            finish();
+        }
+    }
+
+    private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
+        PrintAttributes defaults = capabilities.getDefaults();
+
+        // Sort the media sizes based on the current locale.
+        List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
+        Collections.sort(sortedMediaSizes, mMediaSizeComparator);
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        // Media size.
+        MediaSize currMediaSize = attributes.getMediaSize();
+        if (currMediaSize == null) {
+            attributes.setMediaSize(defaults.getMediaSize());
+        } else {
+            boolean foundCurrentMediaSize = false;
+            // Try to find the current media size in the capabilities as
+            // it may be in a different orientation.
+            MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
+            final int mediaSizeCount = sortedMediaSizes.size();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize mediaSize = sortedMediaSizes.get(i);
+                if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
+                    attributes.setMediaSize(currMediaSize);
+                    foundCurrentMediaSize = true;
+                    break;
+                }
+            }
+            // If we did not find the current media size fall back to default.
+            if (!foundCurrentMediaSize) {
+                attributes.setMediaSize(defaults.getMediaSize());
+            }
+        }
+
+        // Color mode.
+        final int colorMode = attributes.getColorMode();
+        if ((capabilities.getColorModes() & colorMode) == 0) {
+            attributes.setColorMode(defaults.getColorMode());
+        }
+
+        // Resolution
+        Resolution resolution = attributes.getResolution();
+        if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
+            attributes.setResolution(defaults.getResolution());
+        }
+
+        // Margins.
+        attributes.setMinMargins(defaults.getMinMargins());
+    }
+
+    private boolean updateDocument(boolean preview, boolean clearLastError) {
+        if (!clearLastError && mPrintedDocument.hasUpdateError()) {
+            return false;
+        }
+
+        if (clearLastError && mPrintedDocument.hasUpdateError()) {
+            mPrintedDocument.clearUpdateError();
+        }
+
+        if (mRequestedPages != null && mRequestedPages.length > 0) {
+            final PageRange[] pages;
+            if (preview) {
+                final int firstPage = mRequestedPages[0].getStart();
+                pages = new PageRange[]{new PageRange(firstPage, firstPage)};
+            } else {
+                pages = mRequestedPages;
+            }
+            final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
+                    pages, preview);
+
+            if (willUpdate) {
+                mProgressMessageController.post();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void addCurrentPrinterToHistory() {
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter != null) {
+            PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
+            if (!currentPrinter.getId().equals(fakePdfPrinterId)) {
+                mPrinterRegistry.addHistoricalPrinter(currentPrinter);
+            }
+        }
+    }
+
+    private PrinterInfo getCurrentPrinter() {
+        return ((PrinterHolder) mDestinationSpinner.getSelectedItem()).printer;
+    }
+
+    private void cancelPrint() {
+        mState = STATE_PRINT_CANCELED;
+        updateOptionsUi();
+        if (mPrintedDocument.isUpdating()) {
+            mPrintedDocument.cancel();
+        }
+        finish();
+    }
+
+    private void confirmPrint() {
+        mState = STATE_PRINT_CONFIRMED;
+        updateOptionsUi();
+        if (canUpdateDocument()) {
+            updateDocument(false, false);
+        }
+        addCurrentPrinterToHistory();
+        if (!mPrintedDocument.isUpdating()) {
+            requestCreatePdfFileOrFinish();
+        }
+    }
+
+    private void bindUi() {
+        // Summary
+        mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
+        mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
+
+        // Options container
+        mOptionsContent = (ContentView) findViewById(R.id.options_content);
+        mOptionsContent.setOptionsStateChangeListener(new OptionsStateChangeListener() {
+            @Override
+            public void onOptionsOpened() {
+                // TODO: Update preview.
+            }
+
+            @Override
+            public void onOptionsClosed() {
+                // TODO: Update preview.
+            }
+        });
+
+        OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
+        OnClickListener clickListener = new MyClickListener();
+
+        // Copies
+        mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+        mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
+        mCopiesEditText.setText(MIN_COPIES_STRING);
+        mCopiesEditText.setSelection(mCopiesEditText.getText().length());
+        mCopiesEditText.addTextChangedListener(new EditTextWatcher());
+
+        // Destination.
+        mDestinationSpinnerAdapter.registerDataSetObserver(new PrintersObserver());
+        mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+        mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+        mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
+        mDestinationSpinner.setSelection(0);
+
+        // Media size.
+        mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
+        mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+        mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Color mode.
+        mColorModeSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
+        mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+        mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Orientation
+        mOrientationSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        String[] orientationLabels = getResources().getStringArray(
+              R.array.orientation_labels);
+        mOrientationSpinnerAdapter.add(new SpinnerItem<>(
+                ORIENTATION_PORTRAIT, orientationLabels[0]));
+        mOrientationSpinnerAdapter.add(new SpinnerItem<>(
+                ORIENTATION_LANDSCAPE, orientationLabels[1]));
+        mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+        mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+        mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Range options
+        ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
+                new ArrayAdapter<>(this, R.layout.spinner_dropdown_item, R.id.title);
+        final int[] rangeOptionsValues = getResources().getIntArray(
+                R.array.page_options_values);
+        String[] rangeOptionsLabels = getResources().getStringArray(
+                R.array.page_options_labels);
+        final int rangeOptionsCount = rangeOptionsLabels.length;
+        for (int i = 0; i < rangeOptionsCount; i++) {
+            rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
+                    rangeOptionsValues[i], rangeOptionsLabels[i]));
+        }
+        mPageRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title);
+        mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
+        mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
+        mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Page range
+        mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
+        mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+        mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
+        mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
+
+        // Advanced options button.
+        mAdvancedPrintOptionsContainer = findViewById(R.id.more_options_container);
+        mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
+        mMoreOptionsButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(currentPrinter);
+                }
+            }
+        });
+
+        // Print button
+        mPrintButton = (ImageView) findViewById(R.id.print_button);
+        mPrintButton.setOnClickListener(clickListener);
+    }
+
+    private final class MyClickListener implements OnClickListener {
+        @Override
+        public void onClick(View view) {
+            if (view == mPrintButton) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    confirmPrint();
+                } else {
+                    cancelPrint();
+                }
+            } else if (view == mMoreOptionsButton) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(currentPrinter);
+                }
+            }
+        }
+    }
+
+    private static boolean canPrint(PrinterInfo printer) {
+        return printer.getCapabilities() != null
+                && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+    }
+
+    private void updateOptionsUi() {
+        // Always update the summary.
+        if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
+            mSummaryCopies.setText(mCopiesEditText.getText());
+        }
+
+        final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
+        if (selectedMediaIndex >= 0) {
+            SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
+            mSummaryPaperSize.setText(mediaItem.label);
+        }
+
+        if (mState == STATE_PRINT_CONFIRMED
+                || mState == STATE_PRINT_CANCELED
+                || mState == STATE_UPDATE_FAILED
+                || mState == STATE_CREATE_FILE_FAILED
+                || mState == STATE_PRINTER_UNAVAILABLE) {
+            if (mState != STATE_PRINTER_UNAVAILABLE) {
+                mDestinationSpinner.setEnabled(false);
+            }
+            mCopiesEditText.setEnabled(false);
+            mMediaSizeSpinner.setEnabled(false);
+            mColorModeSpinner.setEnabled(false);
+            mOrientationSpinner.setEnabled(false);
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeEditText.setEnabled(false);
+            mPrintButton.setEnabled(false);
+            mMoreOptionsButton.setEnabled(false);
+            return;
+        }
+
+        // If no current printer, or it has no capabilities, or it is not
+        // available, we disable all print options except the destination.
+        PrinterInfo currentPrinter =  getCurrentPrinter();
+        if (currentPrinter == null || !canPrint(currentPrinter)) {
+            mCopiesEditText.setEnabled(false);
+            mMediaSizeSpinner.setEnabled(false);
+            mColorModeSpinner.setEnabled(false);
+            mOrientationSpinner.setEnabled(false);
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeEditText.setEnabled(false);
+            mPrintButton.setEnabled(false);
+            mMoreOptionsButton.setEnabled(false);
+            return;
+        }
+
+        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrintAttributes defaultAttributes = capabilities.getDefaults();
+
+        // Media size.
+        mMediaSizeSpinner.setEnabled(true);
+
+        List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
+        // Sort the media sizes based on the current locale.
+        Collections.sort(mediaSizes, mMediaSizeComparator);
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        // If the media sizes changed, we update the adapter and the spinner.
+        boolean mediaSizesChanged = false;
+        final int mediaSizeCount = mediaSizes.size();
+        if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
+            mediaSizesChanged = true;
+        } else {
+            for (int i = 0; i < mediaSizeCount; i++) {
+                if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
+                    mediaSizesChanged = true;
+                    break;
+                }
+            }
+        }
+        if (mediaSizesChanged) {
+            // Remember the old media size to try selecting it again.
+            int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
+            MediaSize oldMediaSize = attributes.getMediaSize();
+
+            // Rebuild the adapter data.
+            mMediaSizeSpinnerAdapter.clear();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize mediaSize = mediaSizes.get(i);
+                if (oldMediaSize != null
+                        && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
+                    // Update the index of the old selection.
+                    oldMediaSizeNewIndex = i;
+                }
+                mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
+                        mediaSize, mediaSize.getLabel(getPackageManager())));
+            }
+
+            if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
+                // Select the old media size - nothing really changed.
+                if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
+                    mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
+                }
+            } else {
+                // Select the first or the default.
+                final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
+                        defaultAttributes.getMediaSize()), 0);
+                if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
+                    mMediaSizeSpinner.setSelection(mediaSizeIndex);
+                }
+                // Respect the orientation of the old selection.
+                if (oldMediaSize != null) {
+                    if (oldMediaSize.isPortrait()) {
+                        attributes.setMediaSize(mMediaSizeSpinnerAdapter
+                                .getItem(mediaSizeIndex).value.asPortrait());
+                    } else {
+                        attributes.setMediaSize(mMediaSizeSpinnerAdapter
+                                .getItem(mediaSizeIndex).value.asLandscape());
+                    }
+                }
+            }
+        }
+
+        // Color mode.
+        mColorModeSpinner.setEnabled(true);
+        final int colorModes = capabilities.getColorModes();
+
+        // If the color modes changed, we update the adapter and the spinner.
+        boolean colorModesChanged = false;
+        if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
+            colorModesChanged = true;
+        } else {
+            int remainingColorModes = colorModes;
+            int adapterIndex = 0;
+            while (remainingColorModes != 0) {
+                final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
+                final int colorMode = 1 << colorBitOffset;
+                remainingColorModes &= ~colorMode;
+                if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
+                    colorModesChanged = true;
+                    break;
+                }
+                adapterIndex++;
+            }
+        }
+        if (colorModesChanged) {
+            // Remember the old color mode to try selecting it again.
+            int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
+            final int oldColorMode = attributes.getColorMode();
+
+            // Rebuild the adapter data.
+            mColorModeSpinnerAdapter.clear();
+            String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
+            int remainingColorModes = colorModes;
+            while (remainingColorModes != 0) {
+                final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
+                final int colorMode = 1 << colorBitOffset;
+                if (colorMode == oldColorMode) {
+                    // Update the index of the old selection.
+                    oldColorModeNewIndex = colorBitOffset;
+                }
+                remainingColorModes &= ~colorMode;
+                mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
+                        colorModeLabels[colorBitOffset]));
+            }
+            if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
+                // Select the old color mode - nothing really changed.
+                if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
+                    mColorModeSpinner.setSelection(oldColorModeNewIndex);
+                }
+            } else {
+                // Select the default.
+                final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
+                final int itemCount = mColorModeSpinnerAdapter.getCount();
+                for (int i = 0; i < itemCount; i++) {
+                    SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
+                    if (selectedColorMode == item.value) {
+                        if (mColorModeSpinner.getSelectedItemPosition() != i) {
+                            mColorModeSpinner.setSelection(i);
+                        }
+                        attributes.setColorMode(selectedColorMode);
+                    }
+                }
+            }
+        }
+
+        // Orientation
+        mOrientationSpinner.setEnabled(true);
+        MediaSize mediaSize = attributes.getMediaSize();
+        if (mediaSize != null) {
+            if (mediaSize.isPortrait()
+                    && mOrientationSpinner.getSelectedItemPosition() != 0) {
+                mOrientationSpinner.setSelection(0);
+            } else if (!mediaSize.isPortrait()
+                    && mOrientationSpinner.getSelectedItemPosition() != 1) {
+                mOrientationSpinner.setSelection(1);
+            }
+        }
+
+        // Range options
+        PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+        if (info != null && info.getPageCount() > 0) {
+            if (info.getPageCount() == 1) {
+                mRangeOptionsSpinner.setEnabled(false);
+            } else {
+                mRangeOptionsSpinner.setEnabled(true);
+                if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+                    if (!mPageRangeEditText.isEnabled()) {
+                        mPageRangeEditText.setEnabled(true);
+                        mPageRangeEditText.setVisibility(View.VISIBLE);
+                        mPageRangeTitle.setVisibility(View.VISIBLE);
+                        mPageRangeEditText.requestFocus();
+                        InputMethodManager imm = (InputMethodManager)
+                                getSystemService(Context.INPUT_METHOD_SERVICE);
+                        imm.showSoftInput(mPageRangeEditText, 0);
+                    }
+                } else {
+                    mPageRangeEditText.setEnabled(false);
+                    mPageRangeEditText.setVisibility(View.INVISIBLE);
+                    mPageRangeTitle.setVisibility(View.INVISIBLE);
+                }
+            }
+            String title = (info.getPageCount() != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+                    ? getString(R.string.label_pages, String.valueOf(info.getPageCount()))
+                    : getString(R.string.page_count_unknown);
+            mPageRangeOptionsTitle.setText(title);
+        } else {
+            if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                mRangeOptionsSpinner.setSelection(0);
+            }
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
+            mPageRangeEditText.setEnabled(false);
+            mPageRangeEditText.setVisibility(View.INVISIBLE);
+            mPageRangeTitle.setVisibility(View.INVISIBLE);
+        }
+
+        // Advanced print options
+        ComponentName serviceName = currentPrinter.getId().getServiceName();
+        if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
+                this, serviceName))) {
+            mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
+            mMoreOptionsButton.setEnabled(true);
+        } else {
+            mAdvancedPrintOptionsContainer.setVisibility(View.GONE);
+            mMoreOptionsButton.setEnabled(false);
+        }
+
+        // Print
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+            mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
+        } else {
+            mPrintButton.setImageResource(com.android.internal.R.drawable.ic_menu_save);
+        }
+        if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
+                && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
+            || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+                && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
+            mPrintButton.setEnabled(false);
+        } else {
+            mPrintButton.setEnabled(true);
+        }
+
+        // Copies
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+            mCopiesEditText.setEnabled(true);
+        } else {
+            mCopiesEditText.setEnabled(false);
+        }
+        if (mCopiesEditText.getError() == null
+                && TextUtils.isEmpty(mCopiesEditText.getText())) {
+            mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+            mCopiesEditText.requestFocus();
+        }
+    }
+
+    private PageRange[] computeRequestedPages() {
+        if (hasErrors()) {
+            return null;
+        }
+
+        if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+            List<PageRange> pageRanges = new ArrayList<>();
+            mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
+
+            while (mStringCommaSplitter.hasNext()) {
+                String range = mStringCommaSplitter.next().trim();
+                if (TextUtils.isEmpty(range)) {
+                    continue;
+                }
+                final int dashIndex = range.indexOf('-');
+                final int fromIndex;
+                final int toIndex;
+
+                if (dashIndex > 0) {
+                    fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
+                    // It is possible that the dash is at the end since the input
+                    // verification can has to allow the user to keep entering if
+                    // this would lead to a valid input. So we handle this.
+                    if (dashIndex < range.length() - 1) {
+                        String fromString = range.substring(dashIndex + 1, range.length()).trim();
+                        toIndex = Integer.parseInt(fromString) - 1;
+                    } else {
+                        toIndex = fromIndex;
+                    }
+                } else {
+                    fromIndex = toIndex = Integer.parseInt(range) - 1;
+                }
+
+                PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
+                        Math.max(fromIndex, toIndex));
+                pageRanges.add(pageRange);
+            }
+
+            PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+            pageRanges.toArray(pageRangesArray);
+
+            return PageRangeUtils.normalize(pageRangesArray);
+        }
+
+        return ALL_PAGES_ARRAY;
+    }
+
+    private boolean hasErrors() {
+        return (mCopiesEditText.getError() != null)
+                || (mPageRangeEditText.getVisibility() == View.VISIBLE
+                    && mPageRangeEditText.getError() != null);
+    }
+
+    public void onPrinterAvailable(PrinterInfo printer) {
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter.equals(printer)) {
+            mState = STATE_CONFIGURING;
+            if (canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+            ensurePreviewUiShown();
+            updateOptionsUi();
+        }
+    }
+
+    public void onPrinterUnavailable(PrinterInfo printer) {
+        if (getCurrentPrinter().getId().equals(printer.getId())) {
+            mState = STATE_PRINTER_UNAVAILABLE;
+            if (mPrintedDocument.isUpdating()) {
+                mPrintedDocument.cancel();
+            }
+            ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
+                    PrintErrorFragment.ACTION_NONE);
+            updateOptionsUi();
+        }
+    }
+
+    private final class SpinnerItem<T> {
+        final T value;
+        final CharSequence label;
+
+        public SpinnerItem(T value, CharSequence label) {
+            this.value = value;
+            this.label = label;
+        }
+
+        public String toString() {
+            return label.toString();
+        }
+    }
+
+    private final class PrinterAvailabilityDetector implements Runnable {
+        private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
+
+        private boolean mPosted;
+
+        private boolean mPrinterUnavailable;
+
+        private PrinterInfo mPrinter;
+
+        public void updatePrinter(PrinterInfo printer) {
+            if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
+                return;
+            }
+
+            final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
+                    && printer.getCapabilities() != null;
+            final boolean notifyIfAvailable;
+
+            if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
+                notifyIfAvailable = true;
+                unpostIfNeeded();
+                mPrinterUnavailable = false;
+                mPrinter = new PrinterInfo.Builder(printer).build();
+            } else {
+                notifyIfAvailable =
+                     (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
+                        && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
+                    || (mPrinter.getCapabilities() == null
+                        && printer.getCapabilities() != null);
+                mPrinter.copyFrom(printer);
+            }
+
+            if (available) {
+                unpostIfNeeded();
+                mPrinterUnavailable = false;
+                if (notifyIfAvailable) {
+                    onPrinterAvailable(mPrinter);
+                }
+            } else {
+                if (!mPrinterUnavailable) {
+                    postIfNeeded();
+                }
+            }
+        }
+
+        public void cancel() {
+            unpostIfNeeded();
+            mPrinterUnavailable = false;
+        }
+
+        private void postIfNeeded() {
+            if (!mPosted) {
+                mPosted = true;
+                mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
+            }
+        }
+
+        private void unpostIfNeeded() {
+            if (mPosted) {
+                mPosted = false;
+                mDestinationSpinner.removeCallbacks(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            mPosted = false;
+            mPrinterUnavailable = true;
+            onPrinterUnavailable(mPrinter);
+        }
+    }
+
+    private static final class PrinterHolder {
+        PrinterInfo printer;
+        boolean removed;
+
+        public PrinterHolder(PrinterInfo printer) {
+            this.printer = printer;
+        }
+    }
+
+    private final class DestinationAdapter extends BaseAdapter
+            implements PrinterRegistry.OnPrintersChangeListener {
+        private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
+
+        private final PrinterHolder mFakePdfPrinterHolder;
+
+        public DestinationAdapter() {
+            addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            mPrinterRegistry.setOnPrintersChangeListener(this);
+            mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
+        }
+
+        public PrinterInfo getPdfPrinter() {
+            return mFakePdfPrinterHolder.printer;
+        }
+
+        public int getPrinterIndex(PrinterId printerId) {
+            for (int i = 0; i < getCount(); i++) {
+                PrinterHolder printerHolder = (PrinterHolder) getItem(i);
+                if (printerHolder != null && !printerHolder.removed
+                        && printerHolder.printer.getId().equals(printerId)) {
+                    return i;
+                }
+            }
+            return AdapterView.INVALID_POSITION;
+        }
+
+        public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
+            final int printerCount = mPrinterHolders.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                if (printerHolder.printer.getId().equals(printerId)) {
+                    // If already in the list - do nothing.
+                    if (i < getCount() - 2) {
+                        return;
+                    }
+                    // Else replace the last one (two items are not printers).
+                    final int lastPrinterIndex = getCount() - 3;
+                    mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
+                    mPrinterHolders.set(lastPrinterIndex, printerHolder);
+                    notifyDataSetChanged();
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public int getCount() {
+            return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            Object item = getItem(position);
+            if (item instanceof PrinterHolder) {
+                PrinterHolder printerHolder = (PrinterHolder) item;
+                return !printerHolder.removed
+                        && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+            }
+            return true;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0) {
+                    return mFakePdfPrinterHolder;
+                }
+            } else {
+                if (position < 1) {
+                    return mPrinterHolders.get(position);
+                }
+                if (position == 1) {
+                    return mFakePdfPrinterHolder;
+                }
+                if (position < getCount() - 1) {
+                    return mPrinterHolders.get(position - 1);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0) {
+                    return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                } else if (position == 1) {
+                    return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                }
+            } else {
+                if (position == 1) {
+                    return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                }
+                if (position == getCount() - 1) {
+                    return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                }
+            }
+            return position;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            View view = getView(position, convertView, parent);
+            view.setEnabled(isEnabled(position));
+            return view;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(
+                        R.layout.printer_dropdown_item, parent, false);
+            }
+
+            CharSequence title = null;
+            CharSequence subtitle = null;
+            Drawable icon = null;
+
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0 && getPdfPrinter() != null) {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    icon = getResources().getDrawable(com.android.internal.R.drawable.ic_menu_save);
+                } else if (position == 1) {
+                    title = getString(R.string.all_printers);
+                }
+            } else {
+                if (position == 1 && getPdfPrinter() != null) {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    icon = getResources().getDrawable(com.android.internal.R.drawable.ic_menu_save);
+                } else if (position == getCount() - 1) {
+                    title = getString(R.string.all_printers);
+                } else {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    try {
+                        PackageInfo packageInfo = getPackageManager().getPackageInfo(
+                                printerHolder.printer.getId().getServiceName().getPackageName(), 0);
+                        subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+                        icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
+                    } catch (NameNotFoundException nnfe) {
+                        /* ignore */
+                    }
+                }
+            }
+
+            TextView titleView = (TextView) convertView.findViewById(R.id.title);
+            titleView.setText(title);
+
+            TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+            if (!TextUtils.isEmpty(subtitle)) {
+                subtitleView.setText(subtitle);
+                subtitleView.setVisibility(View.VISIBLE);
+            } else {
+                subtitleView.setText(null);
+                subtitleView.setVisibility(View.GONE);
+            }
+
+            ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
+            if (icon != null) {
+                iconView.setImageDrawable(icon);
+                iconView.setVisibility(View.VISIBLE);
+            } else {
+                iconView.setVisibility(View.INVISIBLE);
+            }
+
+            return convertView;
+        }
+
+        @Override
+        public void onPrintersChanged(List<PrinterInfo> printers) {
+            // We rearrange the printers if the user selects a printer
+            // not shown in the initial short list. Therefore, we have
+            // to keep the printer order.
+
+            // No old printers - do not bother keeping their position.
+            if (mPrinterHolders.isEmpty()) {
+                addPrinters(mPrinterHolders, printers);
+                notifyDataSetChanged();
+                return;
+            }
+
+            // Add the new printers to a map.
+            ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
+            final int printerCount = printers.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = printers.get(i);
+                newPrintersMap.put(printer.getId(), printer);
+            }
+
+            List<PrinterHolder> newPrinterHolders = new ArrayList<>();
+
+            // Update printers we already have which are either updated or removed.
+            // We do not remove printers if the currently selected printer is removed
+            // to prevent the user printing to a wrong printer.
+            final int oldPrinterCount = mPrinterHolders.size();
+            for (int i = 0; i < oldPrinterCount; i++) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                PrinterId oldPrinterId = printerHolder.printer.getId();
+                PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
+                if (updatedPrinter != null) {
+                    printerHolder.printer = updatedPrinter;
+                } else {
+                    printerHolder.removed = true;
+                }
+                newPrinterHolders.add(printerHolder);
+            }
+
+            // Add the rest of the new printers, i.e. what is left.
+            addPrinters(newPrinterHolders, newPrintersMap.values());
+
+            mPrinterHolders.clear();
+            mPrinterHolders.addAll(newPrinterHolders);
+
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onPrintersInvalid() {
+            mPrinterHolders.clear();
+            notifyDataSetInvalidated();
+        }
+
+        public PrinterHolder getPrinterHolder(PrinterId printerId) {
+            final int itemCount = getCount();
+            for (int i = 0; i < itemCount; i++) {
+                Object item = getItem(i);
+                if (item instanceof PrinterHolder) {
+                    PrinterHolder printerHolder = (PrinterHolder) item;
+                    if (printerId.equals(printerHolder.printer.getId())) {
+                        return printerHolder;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public void pruneRemovedPrinters() {
+            final int holderCounts = mPrinterHolders.size();
+            for (int i = holderCounts - 1; i >= 0; i--) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                if (printerHolder.removed) {
+                    mPrinterHolders.remove(i);
+                }
+            }
+        }
+
+        private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
+            for (PrinterInfo printer : printers) {
+                PrinterHolder printerHolder = new PrinterHolder(printer);
+                list.add(printerHolder);
+            }
+        }
+
+        private PrinterInfo createFakePdfPrinter() {
+            MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
+
+            PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
+
+            PrinterCapabilitiesInfo.Builder builder =
+                    new PrinterCapabilitiesInfo.Builder(printerId);
+
+            String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
+            final int mediaSizeIdCount = mediaSizeIds.length;
+            for (int i = 0; i < mediaSizeIdCount; i++) {
+                String id = mediaSizeIds[i];
+                MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
+                builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
+            }
+
+            builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
+                    true);
+            builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
+                    | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
+
+            return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
+                    PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
+        }
+    }
+
+    private final class PrintersObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            PrinterInfo oldPrinterState = mOldCurrentPrinter;
+            if (oldPrinterState == null) {
+                return;
+            }
+
+            PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
+                    oldPrinterState.getId());
+            if (printerHolder == null) {
+                return;
+            }
+            PrinterInfo newPrinterState = printerHolder.printer;
+
+            if (!printerHolder.removed) {
+                mDestinationSpinnerAdapter.pruneRemovedPrinters();
+            } else {
+                onPrinterUnavailable(newPrinterState);
+            }
+
+            if (oldPrinterState.equals(newPrinterState)) {
+                return;
+            }
+
+            PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
+            PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
+
+            final boolean hasCapab = newCapab != null;
+            final boolean gotCapab = oldCapab == null && newCapab != null;
+            final boolean lostCapab = oldCapab != null && newCapab == null;
+            final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
+
+            final int oldStatus = oldPrinterState.getStatus();
+            final int newStatus = newPrinterState.getStatus();
+
+            final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
+            final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
+                    && oldStatus != newStatus);
+            final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
+                    && oldStatus != newStatus);
+
+            mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
+
+            oldPrinterState.copyFrom(newPrinterState);
+
+            if ((isActive && gotCapab) || (becameActive && hasCapab)) {
+                onPrinterAvailable(newPrinterState);
+            } else if ((becameInactive && hasCapab)|| (isActive && lostCapab)) {
+                onPrinterUnavailable(newPrinterState);
+            }
+
+            if (hasCapab && capabChanged) {
+                updatePrintAttributesFromCapabilities(newCapab);
+            }
+
+            final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
+                    || (becameActive && hasCapab) || (isActive && gotCapab));
+
+            if (updateNeeded && canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+
+            updateOptionsUi();
+        }
+
+        private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
+                PrinterCapabilitiesInfo newCapabilities) {
+            if (oldCapabilities == null) {
+                if (newCapabilities != null) {
+                    return true;
+                }
+            } else if (!oldCapabilities.equals(newCapabilities)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
+        @Override
+        public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+            if (spinner == mDestinationSpinner) {
+                if (position == AdapterView.INVALID_POSITION) {
+                    return;
+                }
+
+                if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+                    startSelectPrinterActivity();
+                    return;
+                }
+
+                PrinterInfo currentPrinter = getCurrentPrinter();
+
+                // Why on earth item selected is called if no selection changed.
+                if (mOldCurrentPrinter == currentPrinter) {
+                    return;
+                }
+                mOldCurrentPrinter = currentPrinter;
+
+                PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
+                        currentPrinter.getId());
+                if (!printerHolder.removed) {
+                    mDestinationSpinnerAdapter.pruneRemovedPrinters();
+                    ensurePreviewUiShown();
+                }
+
+                mPrintJob.setPrinterId(currentPrinter.getId());
+                mPrintJob.setPrinterName(currentPrinter.getName());
+
+                mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
+
+                PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+                if (capabilities != null) {
+                   updatePrintAttributesFromCapabilities(capabilities);
+                }
+
+                mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
+            } else if (spinner == mMediaSizeSpinner) {
+                SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
+                if (mOrientationSpinner.getSelectedItemPosition() == 0) {
+                    mPrintJob.getAttributes().setMediaSize(mediaItem.value.asPortrait());
+                } else {
+                    mPrintJob.getAttributes().setMediaSize(mediaItem.value.asLandscape());
+                }
+            } else if (spinner == mColorModeSpinner) {
+                SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
+                mPrintJob.getAttributes().setColorMode(colorModeItem.value);
+            } else if (spinner == mOrientationSpinner) {
+                SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
+                PrintAttributes attributes = mPrintJob.getAttributes();
+                if (orientationItem.value == ORIENTATION_PORTRAIT) {
+                    attributes.copyFrom(attributes.asPortrait());
+                } else {
+                    attributes.copyFrom(attributes.asLandscape());
+                }
+            }
+
+            if (canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+
+            updateOptionsUi();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+            /* do nothing*/
+        }
+    }
+
+    private boolean canUpdateDocument() {
+        if (mPrintedDocument.isDestroyed()) {
+            return false;
+        }
+
+        if (hasErrors()) {
+            return false;
+        }
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        final int colorMode = attributes.getColorMode();
+        if (colorMode != PrintAttributes.COLOR_MODE_COLOR
+                && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
+            return false;
+        }
+        if (attributes.getMediaSize() == null) {
+            return false;
+        }
+        if (attributes.getMinMargins() == null) {
+            return false;
+        }
+        if (attributes.getResolution() == null) {
+            return false;
+        }
+
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter == null) {
+            return false;
+        }
+        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        if (capabilities == null) {
+            return false;
+        }
+        if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private final class SelectAllOnFocusListener implements OnFocusChangeListener {
+        @Override
+        public void onFocusChange(View view, boolean hasFocus) {
+            EditText editText = (EditText) view;
+            if (!TextUtils.isEmpty(editText.getText())) {
+                editText.setSelection(editText.getText().length());
+            }
+        }
+    }
+
+    private final class RangeTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            /* do nothing */
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            /* do nothing */
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final boolean hadErrors = hasErrors();
+
+            String text = editable.toString();
+
+            if (TextUtils.isEmpty(text)) {
+                mPageRangeEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
+            if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
+                mPageRangeEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+            final int pageCount = (info != null) ? info.getPageCount() : 0;
+
+            // The range
+            Matcher matcher = PATTERN_DIGITS.matcher(text);
+            while (matcher.find()) {
+                String numericString = text.substring(matcher.start(), matcher.end()).trim();
+                if (TextUtils.isEmpty(numericString)) {
+                    continue;
+                }
+                final int pageIndex = Integer.parseInt(numericString);
+                if (pageIndex < 1 || pageIndex > pageCount) {
+                    mPageRangeEditText.setError("");
+                    updateOptionsUi();
+                    return;
+                }
+            }
+
+            // We intentionally do not catch the case of the from page being
+            // greater than the to page. When computing the requested pages
+            // we just swap them if necessary.
+
+            // Keep the print job up to date with the selected pages if we
+            // know how many pages are there in the document.
+            mRequestedPages = computeRequestedPages();
+
+            mPageRangeEditText.setError(null);
+            mPrintButton.setEnabled(true);
+            updateOptionsUi();
+
+            if (hadErrors && !hasErrors()) {
+                updateOptionsUi();
+            }
+        }
+    }
+
+    private final class EditTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            /* do nothing */
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            /* do nothing */
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final boolean hadErrors = hasErrors();
+
+            if (editable.length() == 0) {
+                mCopiesEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            int copies = 0;
+            try {
+                copies = Integer.parseInt(editable.toString());
+            } catch (NumberFormatException nfe) {
+                /* ignore */
+            }
+
+            if (copies < MIN_COPIES) {
+                mCopiesEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            mPrintJob.setCopies(copies);
+
+            mCopiesEditText.setError(null);
+
+            updateOptionsUi();
+
+            if (hadErrors && canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+        }
+    }
+
+    private final class ProgressMessageController implements Runnable {
+        private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
+
+        private final Handler mHandler;
+
+        private boolean mPosted;
+
+        public ProgressMessageController(Context context) {
+            mHandler = new Handler(context.getMainLooper(), null, false);
+        }
+
+        public void post() {
+            if (mPosted) {
+                return;
+            }
+            mPosted = true;
+            mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
+        }
+
+        public void cancel() {
+            if (!mPosted) {
+                return;
+            }
+            mPosted = false;
+            mHandler.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            ensureProgressUiShown();
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
new file mode 100644
index 0000000..b708356
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 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.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.printspooler.R;
+
+/**
+ * Fragment for showing an error UI.
+ */
+public final class PrintErrorFragment extends Fragment {
+    public static final int ACTION_NONE = 0;
+    public static final int ACTION_RETRY = 1;
+    public static final int ACTION_CONFIRM = 2;
+
+    public interface OnActionListener {
+        public void onActionPerformed();
+    }
+
+    private static final String EXTRA_ERROR_MESSAGE = "EXTRA_ERROR_MESSAGE";
+    private static final String EXTRA_ACTION = "EXTRA_ACTION";
+
+    public static PrintErrorFragment newInstance(CharSequence errorMessage, int action) {
+        PrintErrorFragment instance = new PrintErrorFragment();
+        Bundle arguments = new Bundle();
+        arguments.putCharSequence(EXTRA_ERROR_MESSAGE, errorMessage);
+        arguments.putInt(EXTRA_ACTION, action);
+        instance.setArguments(arguments);
+        return instance;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.print_error_fragment, root, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        Bundle arguments = getArguments();
+
+        CharSequence error = arguments.getString(EXTRA_ERROR_MESSAGE);
+        if (!TextUtils.isEmpty(error)) {
+            TextView message = (TextView) view.findViewById(R.id.message);
+            message.setText(error);
+        }
+
+        Button actionButton = (Button) view.findViewById(R.id.action_button);
+
+        final int action = getArguments().getInt(EXTRA_ACTION);
+        switch (action) {
+            case ACTION_RETRY: {
+                actionButton.setVisibility(View.VISIBLE);
+                actionButton.setText(R.string.print_error_retry);
+            } break;
+
+            case ACTION_CONFIRM: {
+                actionButton.setVisibility(View.VISIBLE);
+                actionButton.setText(android.R.string.ok);
+            } break;
+
+            case ACTION_NONE: {
+                actionButton.setVisibility(View.GONE);
+            } break;
+        }
+
+        actionButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Activity activity = getActivity();
+                if (activity instanceof OnActionListener) {
+                    ((OnActionListener) getActivity()).onActionPerformed();
+                }
+            }
+        });
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
new file mode 100644
index 0000000..d68a6aa
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
@@ -0,0 +1,12 @@
+package com.android.printspooler.ui;
+
+import android.app.Fragment;
+
+public class PrintPreviewFragment extends Fragment {
+
+    public static PrintPreviewFragment newInstance() {
+        return new PrintPreviewFragment();
+    }
+
+    // TODO: Implement
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
new file mode 100644
index 0000000..96aa153d
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 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.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import android.widget.TextView;
+import com.android.printspooler.R;
+
+/**
+ * Fragment for showing a work in progress UI.
+ */
+public final class PrintProgressFragment extends Fragment {
+
+    public interface OnCancelRequestListener {
+        public void onCancelRequest();
+    }
+
+    public static PrintProgressFragment newInstance() {
+        return new PrintProgressFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.print_progress_fragment, root, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        final Button cancelButton = (Button) view.findViewById(R.id.cancel_button);
+        final TextView message = (TextView) view.findViewById(R.id.message);
+
+        cancelButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Activity activity = getActivity();
+                if (activity instanceof OnCancelRequestListener) {
+                    ((OnCancelRequestListener) getActivity()).onCancelRequest();
+                }
+                cancelButton.setVisibility(View.GONE);
+                message.setVisibility(View.VISIBLE);
+            }
+        });
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
new file mode 100644
index 0000000..7816d66
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 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.printspooler.ui;
+
+import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Loader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrinterRegistry {
+
+    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+    private final Activity mActivity;
+
+    private final List<PrinterInfo> mPrinters = new ArrayList<>();
+
+    private final Runnable mReadyCallback;
+
+    private final Handler mHandler;
+
+    private boolean mReady;
+
+    private OnPrintersChangeListener mOnPrintersChangeListener;
+
+    public interface OnPrintersChangeListener {
+        public void onPrintersChanged(List<PrinterInfo> printers);
+        public void onPrintersInvalid();
+    }
+
+    public PrinterRegistry(Activity activity, Runnable readyCallback) {
+        mActivity = activity;
+        mReadyCallback = readyCallback;
+        mHandler = new MyHandler(activity.getMainLooper());
+        activity.getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER,
+                null, mLoaderCallbacks);
+    }
+
+    public void setOnPrintersChangeListener(OnPrintersChangeListener listener) {
+        mOnPrintersChangeListener = listener;
+    }
+
+    public List<PrinterInfo> getPrinters() {
+        return mPrinters;
+    }
+
+    public void addHistoricalPrinter(PrinterInfo printer) {
+        getPrinterProvider().addHistoricalPrinter(printer);
+    }
+
+    public void forgetFavoritePrinter(PrinterId printerId) {
+        getPrinterProvider().forgetFavoritePrinter(printerId);
+    }
+
+    public boolean isFavoritePrinter(PrinterId printerId) {
+        return getPrinterProvider().isFavoritePrinter(printerId);
+    }
+
+    public void setTrackedPrinter(PrinterId printerId) {
+        getPrinterProvider().setTrackedPrinter(printerId);
+    }
+
+    private FusedPrintersProvider getPrinterProvider() {
+        Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
+        return (FusedPrintersProvider) loader;
+    }
+
+    private final LoaderCallbacks<List<PrinterInfo>> mLoaderCallbacks =
+            new LoaderCallbacks<List<PrinterInfo>>() {
+        @Override
+        public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+            if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
+                mPrinters.clear();
+                if (mOnPrintersChangeListener != null) {
+                    // Post a message as we are in onLoadFinished and certain operations
+                    // are not allowed in this callback, such as fragment transactions.
+                    // Clients should not handle this explicitly.
+                    mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID,
+                            mOnPrintersChangeListener).sendToTarget();
+                }
+            }
+        }
+
+        // LoaderCallbacks#onLoadFinished
+        @Override
+        public void onLoadFinished(Loader<List<PrinterInfo>> loader, List<PrinterInfo> printers) {
+            if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
+                mPrinters.clear();
+                mPrinters.addAll(printers);
+                if (mOnPrintersChangeListener != null) {
+                    // Post a message as we are in onLoadFinished and certain operations
+                    // are not allowed in this callback, such as fragment transactions.
+                    // Clients should not handle this explicitly.
+                    SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mOnPrintersChangeListener;
+                    args.arg2 = printers;
+                    mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget();
+                }
+                if (!mReady) {
+                    mReady = true;
+                    if (mReadyCallback != null) {
+                        mReadyCallback.run();
+                    }
+                }
+            }
+        }
+
+        // LoaderCallbacks#onCreateLoader
+        @Override
+        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+            if (id == LOADER_ID_PRINTERS_LOADER) {
+                return new FusedPrintersProvider(mActivity);
+            }
+            return null;
+        }
+    };
+
+    private static final class MyHandler extends Handler {
+        public static final int MSG_PRINTERS_CHANGED = 0;
+        public static final int MSG_PRINTERS_INVALID = 1;
+
+        public MyHandler(Looper looper) {
+            super(looper, null , false);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_PRINTERS_CHANGED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    OnPrintersChangeListener callback = (OnPrintersChangeListener) args.arg1;
+                    List<PrinterInfo> printers = (List<PrinterInfo>) args.arg2;
+                    args.recycle();
+                    callback.onPrintersChanged(printers);
+                } break;
+
+                case MSG_PRINTERS_INVALID: {
+                    OnPrintersChangeListener callback = (OnPrintersChangeListener) message.obj;
+                    callback.onPrintersInvalid();
+                } break;
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
similarity index 73%
rename from packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
rename to packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index fe5920c..7715579 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.ui;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -22,13 +22,11 @@
 import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
-import android.app.LoaderManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.Loader;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -48,9 +46,7 @@
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
@@ -66,63 +62,60 @@
 import android.widget.SearchView;
 import android.widget.TextView;
 
+import com.android.printspooler.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * This is a fragment for selecting a printer.
+ * This is an activity for selecting a printer.
  */
-public final class SelectPrinterFragment extends Fragment {
+public final class SelectPrinterActivity extends Activity {
 
     private static final String LOG_TAG = "SelectPrinterFragment";
 
-    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
 
-    private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
-            "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
+    private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG =
+            "FRAGMENT_TAG_ADD_PRINTER_DIALOG";
 
-    private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
-            "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
+    private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS =
+            "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS";
 
     private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
 
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
-            new ArrayList<PrintServiceInfo>();
+            new ArrayList<>();
+
+    private PrinterRegistry mPrinterRegistry;
 
     private ListView mListView;
 
     private AnnounceFilterResult mAnnounceFilterResult;
 
-    public static interface OnPrinterSelectedListener {
-        public void onPrinterSelected(PrinterId printerId);
-    }
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setHasOptionsMenu(true);
-        getActivity().getActionBar().setIcon(R.drawable.ic_menu_print);
-    }
+        getActionBar().setIcon(R.drawable.ic_menu_print);
 
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View content = inflater.inflate(R.layout.select_printer_fragment, container, false);
+        setContentView(R.layout.select_printer_activity);
+
+        mPrinterRegistry = new PrinterRegistry(this, null);
 
         // Hook up the list view.
-        mListView = (ListView) content.findViewById(android.R.id.list);
+        mListView = (ListView) findViewById(android.R.id.list);
         final DestinationAdapter adapter = new DestinationAdapter();
         adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
             public void onChanged() {
-                if (!getActivity().isFinishing() && adapter.getCount() <= 0) {
+                if (!isFinishing() && adapter.getCount() <= 0) {
                     updateEmptyView(adapter);
                 }
             }
 
             @Override
             public void onInvalidated() {
-                if (!getActivity().isFinishing()) {
+                if (!isFinishing()) {
                     updateEmptyView(adapter);
                 }
             }
@@ -135,26 +128,20 @@
                 if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) {
                     return;
                 }
+
                 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
-                Activity activity = getActivity();
-                if (activity instanceof OnPrinterSelectedListener) {
-                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
-                } else {
-                    throw new IllegalStateException("the host activity must implement"
-                            + " OnPrinterSelectedListener");
-                }
+                onPrinterSelected(printer.getId());
             }
         });
 
         registerForContextMenu(mListView);
-
-        return content;
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        inflater.inflate(R.menu.select_printer_activity, menu);
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        getMenuInflater().inflate(R.menu.select_printer_activity, menu);
 
         MenuItem searchItem = menu.findItem(R.id.action_search);
         SearchView searchView = (SearchView) searchItem.getActionView();
@@ -173,16 +160,15 @@
         searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
-                if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+                if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) {
                     view.announceForAccessibility(getString(
                             R.string.print_search_box_shown_utterance));
                 }
             }
             @Override
             public void onViewDetachedFromWindow(View view) {
-                Activity activity = getActivity();
-                if (activity != null && !activity.isFinishing()
-                        && AccessibilityManager.getInstance(activity).isEnabled()) {
+                if (!isFinishing() && AccessibilityManager.getInstance(
+                        SelectPrinterActivity.this).isEnabled()) {
                     view.announceForAccessibility(getString(
                             R.string.print_search_box_hidden_utterance));
                 }
@@ -192,6 +178,8 @@
         if (mAddPrinterServices.isEmpty()) {
             menu.removeItem(R.id.action_add_printer);
         }
+
+        return true;
     }
 
     @Override
@@ -212,9 +200,7 @@
             }
 
             // Add the forget menu item if applicable.
-            FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
-                    getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
-            if (provider.isFavoritePrinter(printer.getId())) {
+            if (mPrinterRegistry.isFavoritePrinter(printer.getId())) {
                 MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer,
                         Menu.NONE, R.string.print_forget_printer);
                 Intent intent = new Intent();
@@ -228,23 +214,13 @@
     public boolean onContextItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.string.print_select_printer: {
-                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
-                        EXTRA_PRINTER_ID);
-                Activity activity = getActivity();
-                if (activity instanceof OnPrinterSelectedListener) {
-                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId);
-                } else {
-                    throw new IllegalStateException("the host activity must implement"
-                            + " OnPrinterSelectedListener");
-                }
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                onPrinterSelected(printerId);
             } return true;
 
             case R.string.print_forget_printer: {
-                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
-                        EXTRA_PRINTER_ID);
-                FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
-                        getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
-                provider.forgetFavoritePrinter(printerId);
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                mPrinterRegistry.forgetFavoritePrinter(printerId);
             } return true;
         }
         return false;
@@ -252,9 +228,9 @@
 
     @Override
     public void onResume() {
-        updateAddPrintersAdapter();
-        getActivity().invalidateOptionsMenu();
         super.onResume();
+        updateServicesWithAddPrinterActivity();
+        invalidateOptionsMenu();
     }
 
     @Override
@@ -274,12 +250,18 @@
         return super.onOptionsItemSelected(item);
     }
 
-    private void updateAddPrintersAdapter() {
+    private void onPrinterSelected(PrinterId printerId) {
+        Intent intent = new Intent();
+        intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    private void updateServicesWithAddPrinterActivity() {
         mAddPrinterServices.clear();
 
         // Get all enabled print services.
-        PrintManager printManager = (PrintManager) getActivity()
-                .getSystemService(Context.PRINT_SERVICE);
+        PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
         List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
 
         // No enabled print services - done.
@@ -292,7 +274,7 @@
         for (int i = 0; i < enabledServiceCount; i++) {
             PrintServiceInfo enabledService = enabledServices.get(i);
 
-            // No add printers activity declared - done.
+            // No add printers activity declared - next.
             if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
                 continue;
             }
@@ -304,15 +286,14 @@
                 .setComponent(addPrintersComponentName);
 
             // The add printers activity is valid - add it.
-            PackageManager pm = getActivity().getPackageManager();
+            PackageManager pm = getPackageManager();
             List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0);
             if (!resolvedActivities.isEmpty()) {
                 // The activity is a component name, therefore it is one or none.
                 ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo;
                 if (activityInfo.exported
                         && (activityInfo.permission == null
-                                || pm.checkPermission(activityInfo.permission,
-                                        getActivity().getPackageName())
+                                || pm.checkPermission(activityInfo.permission, getPackageName())
                                         == PackageManager.PERMISSION_GRANTED)) {
                     mAddPrinterServices.add(enabledService);
                 }
@@ -323,26 +304,26 @@
     private void showAddPrinterSelectionDialog() {
         FragmentTransaction transaction = getFragmentManager().beginTransaction();
         Fragment oldFragment = getFragmentManager().findFragmentByTag(
-                FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+                FRAGMENT_TAG_ADD_PRINTER_DIALOG);
         if (oldFragment != null) {
             transaction.remove(oldFragment);
         }
         AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
         Bundle arguments = new Bundle();
-        arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
+        arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS,
                 mAddPrinterServices);
         newFragment.setArguments(arguments);
-        transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+        transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG);
         transaction.commit();
     }
 
     public void updateEmptyView(DestinationAdapter adapter) {
         if (mListView.getEmptyView() == null) {
-            View emptyView = getActivity().findViewById(R.id.empty_print_state);
+            View emptyView = findViewById(R.id.empty_print_state);
             mListView.setEmptyView(emptyView);
         }
-        TextView titleView = (TextView) getActivity().findViewById(R.id.title);
-        View progressBar = getActivity().findViewById(R.id.progress_bar);
+        TextView titleView = (TextView) findViewById(R.id.title);
+        View progressBar = findViewById(R.id.progress_bar);
         if (adapter.getUnfilteredCount() <= 0) {
             titleView.setText(R.string.print_searching_for_printers);
             progressBar.setVisibility(View.VISIBLE);
@@ -353,7 +334,7 @@
     }
 
     private void announceSearchResultIfNeeded() {
-        if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+        if (AccessibilityManager.getInstance(this).isEnabled()) {
             if (mAnnounceFilterResult == null) {
                 mAnnounceFilterResult = new AnnounceFilterResult();
             }
@@ -372,9 +353,9 @@
                     .setTitle(R.string.choose_print_service);
 
             final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
-                    getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
+                    getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS);
 
-            final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+            final ArrayAdapter<String> adapter = new ArrayAdapter<>(
                     getActivity(), android.R.layout.simple_list_item_1);
             final int printServiceCount = printServices.size();
             for (int i = 0; i < printServiceCount; i++) {
@@ -382,32 +363,33 @@
                 adapter.add(printService.getResolveInfo().loadLabel(
                         getActivity().getPackageManager()).toString());
             }
+
             final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
                     Settings.Secure.PRINT_SERVICE_SEARCH_URI);
-            final Intent marketIntent;
+            final Intent viewIntent;
             if (!TextUtils.isEmpty(searchUri)) {
                 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
                 if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
-                    marketIntent = intent;
+                    viewIntent = intent;
                     mAddPrintServiceItem = getString(R.string.add_print_service_label);
                     adapter.add(mAddPrintServiceItem);
                 } else {
-                    marketIntent = null;
+                    viewIntent = null;
                 }
             } else {
-                marketIntent = null;
+                viewIntent = null;
             }
 
             builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                     String item = adapter.getItem(which);
-                    if (item == mAddPrintServiceItem) {
+                    if (item.equals(mAddPrintServiceItem)) {
                         try {
-                          startActivity(marketIntent);
-                      } catch (ActivityNotFoundException anfe) {
-                          Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
-                      }
+                            startActivity(viewIntent);
+                        } catch (ActivityNotFoundException anfe) {
+                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
+                        }
                     } else {
                         PrintServiceInfo printService = printServices.get(which);
                         ComponentName componentName = new ComponentName(
@@ -418,7 +400,7 @@
                         try {
                             startActivity(intent);
                         } catch (ActivityNotFoundException anfe) {
-                            Log.w(LOG_TAG, "Couldn't start settings activity", anfe);
+                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
                         }
                     }
                 }
@@ -428,19 +410,41 @@
         }
     }
 
-    private final class DestinationAdapter extends BaseAdapter
-            implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
+    private final class DestinationAdapter extends BaseAdapter implements Filterable {
 
         private final Object mLock = new Object();
 
-        private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+        private final List<PrinterInfo> mPrinters = new ArrayList<>();
 
-        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
+        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>();
 
         private CharSequence mLastSearchString;
 
         public DestinationAdapter() {
-            getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+            mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() {
+                @Override
+                public void onPrintersChanged(List<PrinterInfo> printers) {
+                    synchronized (mLock) {
+                        mPrinters.clear();
+                        mPrinters.addAll(printers);
+                        mFilteredPrinters.clear();
+                        mFilteredPrinters.addAll(printers);
+                        if (!TextUtils.isEmpty(mLastSearchString)) {
+                            getFilter().filter(mLastSearchString);
+                        }
+                    }
+                    notifyDataSetChanged();
+                }
+
+                @Override
+                public void onPrintersInvalid() {
+                    synchronized (mLock) {
+                        mPrinters.clear();
+                        mFilteredPrinters.clear();
+                    }
+                    notifyDataSetInvalidated();
+                }
+            });
         }
 
         @Override
@@ -453,7 +457,7 @@
                             return null;
                         }
                         FilterResults results = new FilterResults();
-                        List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
+                        List<PrinterInfo> filteredPrinters = new ArrayList<>();
                         String constraintLowerCase = constraint.toString().toLowerCase();
                         final int printerCount = mPrinters.size();
                         for (int i = 0; i < printerCount; i++) {
@@ -518,28 +522,27 @@
         }
 
         @Override
-        public View getDropDownView(int position, View convertView,
-                ViewGroup parent) {
+        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 = getActivity().getLayoutInflater().inflate(
+                convertView = getLayoutInflater().inflate(
                         R.layout.printer_list_item, parent, false);
             }
 
             convertView.setEnabled(isActionable(position));
 
-            CharSequence title = null;
+            PrinterInfo printer = (PrinterInfo) getItem(position);
+
+            CharSequence title = printer.getName();
             CharSequence subtitle = null;
             Drawable icon = null;
 
-            PrinterInfo printer = (PrinterInfo) getItem(position);
-            title = printer.getName();
             try {
-                PackageManager pm = getActivity().getPackageManager();
+                PackageManager pm = getPackageManager();
                 PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
                         .getServiceName().getPackageName(), 0);
                 subtitle = packageInfo.applicationInfo.loadLabel(pm);
@@ -576,38 +579,6 @@
             PrinterInfo printer =  (PrinterInfo) getItem(position);
             return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
         }
-
-        @Override
-        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_PRINTERS_LOADER) {
-                return new FusedPrintersProvider(getActivity());
-            }
-            return null;
-        }
-
-        @Override
-        public void onLoadFinished(Loader<List<PrinterInfo>> loader,
-                List<PrinterInfo> printers) {
-            synchronized (mLock) {
-                mPrinters.clear();
-                mPrinters.addAll(printers);
-                mFilteredPrinters.clear();
-                mFilteredPrinters.addAll(printers);
-                if (!TextUtils.isEmpty(mLastSearchString)) {
-                    getFilter().filter(mLastSearchString);
-                }
-            }
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
-            synchronized (mLock) {
-                mPrinters.clear();
-                mFilteredPrinters.clear();
-            }
-            notifyDataSetInvalidated();
-        }
     }
 
     private final class AnnounceFilterResult implements Runnable {
@@ -629,7 +600,7 @@
             if (count <= 0) {
                 text = getString(R.string.print_no_printers);
             } else {
-                text = getActivity().getResources().getQuantityString(
+                text = getResources().getQuantityString(
                     R.plurals.print_search_result_count_utterance, count, count);
             }
             mListView.announceForAccessibility(text);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
similarity index 95%
rename from packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
rename to packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
index ac27562..912ee1d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
@@ -14,22 +14,28 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.util;
 
 import android.content.Context;
 import android.print.PrintAttributes.MediaSize;
 import android.util.ArrayMap;
 
+import com.android.printspooler.R;
+
 import java.util.Comparator;
 import java.util.Map;
 
 /**
  * Utility functions and classes for dealing with media sizes.
  */
-public class MediaSizeUtils {
+public final class MediaSizeUtils {
 
     private static Map<MediaSize, String> sMediaSizeToStandardMap;
 
+    private MediaSizeUtils() {
+        /* do nothing - hide constructor */
+    }
+
     /**
      * Gets the default media size for the current locale.
      *
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
new file mode 100644
index 0000000..33b294f
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 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.printspooler.util;
+
+import android.print.PageRange;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * This class contains utility functions for working with page ranges.
+ */
+public final class PageRangeUtils {
+
+    private static final PageRange[] ALL_PAGES_RANGE = new PageRange[] {PageRange.ALL_PAGES};
+
+    private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
+        @Override
+        public int compare(PageRange lhs, PageRange rhs) {
+            return lhs.getStart() - rhs.getStart();
+        }
+    };
+
+    private PageRangeUtils() {
+        /* do nothing - hide constructor */
+    }
+
+    /**
+     * Checks whether one page range array contains another one.
+     *
+     * @param ourRanges The container page ranges.
+     * @param otherRanges The contained page ranges.
+     * @return Whether the container page ranges contains the contained ones.
+     */
+    public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) {
+        if (ourRanges == null || otherRanges == null) {
+            return false;
+        }
+
+        if (Arrays.equals(ourRanges, ALL_PAGES_RANGE)) {
+            return true;
+        }
+
+        ourRanges = normalize(ourRanges);
+        otherRanges = normalize(otherRanges);
+
+        // Note that the code below relies on the ranges being normalized
+        // which is they contain monotonically increasing non-intersecting
+        // sub-ranges whose start is less that or equal to the end.
+        int otherRangeIdx = 0;
+        final int ourRangeCount = ourRanges.length;
+        final int otherRangeCount = otherRanges.length;
+        for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
+            PageRange ourRange = ourRanges[ourRangeIdx];
+            for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
+                PageRange otherRange = otherRanges[otherRangeIdx];
+                if (otherRange.getStart() > ourRange.getEnd()) {
+                    break;
+                }
+                if (otherRange.getStart() < ourRange.getStart()
+                        || otherRange.getEnd() > ourRange.getEnd()) {
+                    return false;
+                }
+            }
+        }
+        if (otherRangeIdx < otherRangeCount) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Normalizes a page range, which is the resulting page ranges are
+     * non-overlapping with the start lesser than or equal to the end
+     * and ordered in an ascending order.
+     *
+     * @param pageRanges The page ranges to normalize.
+     * @return The normalized page ranges.
+     */
+    public static PageRange[] normalize(PageRange[] pageRanges) {
+        if (pageRanges == null) {
+            return null;
+        }
+        final int oldRangeCount = pageRanges.length;
+        if (oldRangeCount <= 1) {
+            return pageRanges;
+        }
+        Arrays.sort(pageRanges, sComparator);
+        int newRangeCount = 1;
+        for (int i = 0; i < oldRangeCount - 1; i++) {
+            newRangeCount++;
+            PageRange currentRange = pageRanges[i];
+            PageRange nextRange = pageRanges[i + 1];
+            if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
+                newRangeCount--;
+                pageRanges[i] = null;
+                pageRanges[i + 1] = new PageRange(currentRange.getStart(),
+                        Math.max(currentRange.getEnd(), nextRange.getEnd()));
+            }
+        }
+        if (newRangeCount == oldRangeCount) {
+            return pageRanges;
+        }
+        return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
+                oldRangeCount);
+    }
+
+    /**
+     * Offsets a the start and end of page ranges with the given value.
+     *
+     * @param pageRanges The page ranges to offset.
+     * @param offset The offset value.
+     */
+    public static void offset(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);
+        }
+    }
+
+    /**
+     * Gets the number of pages in a normalized range array.
+     *
+     * @param pageRanges Normalized page ranges.
+     * @param layoutPageCount Page count after reported after layout pass.
+     * @return The page count in the ranges.
+     */
+    public static int getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount) {
+        int pageCount = 0;
+        final int pageRangeCount = pageRanges.length;
+        for (int i = 0; i < pageRangeCount; i++) {
+            PageRange pageRange = pageRanges[i];
+            if (PageRange.ALL_PAGES.equals(pageRange)) {
+                return layoutPageCount;
+            }
+            pageCount += pageRange.getEnd() - pageRange.getStart() + 1;
+        }
+        return pageCount;
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java
new file mode 100644
index 0000000..446952d
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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.printspooler.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.print.PrintManager;
+import android.printservice.PrintServiceInfo;
+
+import java.util.List;
+
+public class PrintOptionUtils {
+
+    private PrintOptionUtils() {
+        /* ignore - hide constructor */
+    }
+
+    /**
+     * Gets the advanced options activity name for a print service.
+     *
+     * @param context Context for accessing system resources.
+     * @param serviceName The print service name.
+     * @return The advanced options activity name or null.
+     */
+    public static String getAdvancedOptionsActivityName(Context context,
+            ComponentName serviceName) {
+        PrintManager printManager = (PrintManager) context.getSystemService(
+                Context.PRINT_SERVICE);
+        List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
+        final int printServiceCount = printServices.size();
+        for (int i = 0; i < printServiceCount; i ++) {
+            PrintServiceInfo printServiceInfo = printServices.get(i);
+            ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
+            if (serviceInfo.name.equals(serviceName.getClassName())
+                    && serviceInfo.packageName.equals(serviceName.getPackageName())) {
+                return printServiceInfo.getAdvancedOptionsActivityName();
+            }
+        }
+        return null;
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java
new file mode 100644
index 0000000..77ca541
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 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.printspooler.widget;
+
+import android.content.Context;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.printspooler.R;
+
+/**
+ * This class is a layout manager for the print screen. It has a sliding
+ * area that contains the print options. If the sliding area is open the
+ * print options are visible and if it is closed a summary of the print
+ * job is shown. Under the sliding area there is a place for putting
+ * arbitrary content such as preview, error message, progress indicator,
+ * etc. The sliding area is covering the content holder under it when
+ * the former is opened.
+ */
+@SuppressWarnings("unused")
+public final class ContentView extends ViewGroup implements View.OnClickListener {
+    private static final int FIRST_POINTER_ID = 0;
+
+    private final ViewDragHelper mDragger;
+
+    private View mStaticContent;
+    private ViewGroup mSummaryContent;
+    private View mDynamicContent;
+
+    private View mDraggableContent;
+    private ViewGroup mMoreOptionsContainer;
+    private ViewGroup mOptionsContainer;
+
+    private View mEmbeddedContentContainer;
+
+    private View mExpandCollapseHandle;
+    private View mExpandCollapseIcon;
+
+    private int mClosedOptionsOffsetY;
+    private int mCurrentOptionsOffsetY;
+
+    private OptionsStateChangeListener mOptionsStateChangeListener;
+
+    private int mOldDraggableHeight;
+
+    public interface OptionsStateChangeListener {
+        public void onOptionsOpened();
+        public void onOptionsClosed();
+    }
+
+    public ContentView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDragger = ViewDragHelper.create(this, new DragCallbacks());
+
+        // The options view is sliding under the static header but appears
+        // after it in the layout, so we will draw in opposite order.
+        setChildrenDrawingOrderEnabled(true);
+    }
+
+    public void setOptionsStateChangeListener(OptionsStateChangeListener listener) {
+        mOptionsStateChangeListener = listener;
+    }
+
+    private boolean isOptionsOpened() {
+        return mCurrentOptionsOffsetY == 0;
+    }
+
+    private boolean isOptionsClosed() {
+        return mCurrentOptionsOffsetY == mClosedOptionsOffsetY;
+    }
+
+    private void openOptions() {
+        if (isOptionsOpened()) {
+            return;
+        }
+        mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
+                getOpenedOptionsY());
+        invalidate();
+    }
+
+    private void closeOptions() {
+        if (isOptionsClosed()) {
+            return;
+        }
+        mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
+                getClosedOptionsY());
+        invalidate();
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        return childCount - i - 1;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mStaticContent = findViewById(R.id.static_content);
+        mSummaryContent = (ViewGroup) findViewById(R.id.summary_content);
+        mDynamicContent = findViewById(R.id.dynamic_content);
+        mDraggableContent = findViewById(R.id.draggable_content);
+        mMoreOptionsContainer = (ViewGroup) findViewById(R.id.more_options_container);
+        mOptionsContainer = (ViewGroup) findViewById(R.id.options_container);
+        mEmbeddedContentContainer = findViewById(R.id.embedded_content_container);
+        mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
+        mExpandCollapseHandle = findViewById(R.id.expand_collapse_handle);
+
+        mExpandCollapseIcon.setOnClickListener(this);
+        mExpandCollapseHandle.setOnClickListener(this);
+
+        // Make sure we start in a closed options state.
+        onDragProgress(1.0f);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mExpandCollapseHandle || view == mExpandCollapseIcon) {
+            if (isOptionsClosed()) {
+                openOptions();
+            } else if (isOptionsOpened()) {
+                closeOptions();
+            } // else in open/close progress do nothing.
+        }
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        /* do nothing */
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        mDragger.processTouchEvent(event);
+        return true;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return mDragger.shouldInterceptTouchEvent(event)
+                || super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mDragger.continueSettling(true)) {
+            postInvalidateOnAnimation();
+        }
+    }
+
+    private int getOpenedOptionsY() {
+        return mStaticContent.getBottom();
+    }
+
+    private int getClosedOptionsY() {
+        return getOpenedOptionsY() + mClosedOptionsOffsetY;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChild(mStaticContent, widthMeasureSpec, heightMeasureSpec);
+
+        if (mSummaryContent.getVisibility() != View.GONE) {
+            measureChild(mSummaryContent, widthMeasureSpec, heightMeasureSpec);
+        }
+
+        measureChild(mDynamicContent, widthMeasureSpec, heightMeasureSpec);
+
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+//        // The height of the draggable content may change and if that happens
+//        // we have to adjust the current offset to ensure the sliding area is
+//        // at the same position.
+//        mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
+//                - oldDraggableHeight;
+
+        if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) {
+            mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
+                    - mOldDraggableHeight;
+            mOldDraggableHeight = mDraggableContent.getMeasuredHeight();
+        }
+
+        // The height of the draggable content may change and if that happens
+        // we have to adjust the sliding area closed state offset.
+        mClosedOptionsOffsetY = mSummaryContent.getMeasuredHeight()
+                - mDraggableContent.getMeasuredHeight();
+
+        // The content host must be maximally large size that fits entirely
+        // on the screen when the options are collapsed.
+        ViewGroup.LayoutParams params = mEmbeddedContentContainer.getLayoutParams();
+        if (params.height == 0) {
+            params.height = heightSize - mStaticContent.getMeasuredHeight()
+                    - mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight()
+                    + mDraggableContent.getMeasuredHeight();
+
+            mCurrentOptionsOffsetY = mClosedOptionsOffsetY;
+        }
+
+        // The content host can grow vertically as much as needed - we will be covering it.
+        final int hostHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
+        measureChild(mEmbeddedContentContainer, widthMeasureSpec, hostHeightMeasureSpec);
+
+        setMeasuredDimension(resolveSize(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec),
+                resolveSize(heightSize, heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mStaticContent.layout(left, top, right, mStaticContent.getMeasuredHeight());
+
+        if (mSummaryContent.getVisibility() != View.GONE) {
+            mSummaryContent.layout(left, mStaticContent.getMeasuredHeight(), right,
+                    mStaticContent.getMeasuredHeight() + mSummaryContent.getMeasuredHeight());
+        }
+
+        final int dynContentTop = mStaticContent.getMeasuredHeight() + mCurrentOptionsOffsetY;
+        final int dynContentBottom = dynContentTop + mDynamicContent.getMeasuredHeight();
+
+        mDynamicContent.layout(left, dynContentTop, right, dynContentBottom);
+
+        final int embContentTop = mStaticContent.getMeasuredHeight() + mClosedOptionsOffsetY
+                + mDynamicContent.getMeasuredHeight();
+        final int embContentBottom = embContentTop + mEmbeddedContentContainer.getMeasuredHeight();
+
+        mEmbeddedContentContainer.layout(left, embContentTop, right, embContentBottom);
+    }
+
+    private void onDragProgress(float progress) {
+        final int summaryCount = mSummaryContent.getChildCount();
+        for (int i = 0; i < summaryCount; i++) {
+            View child = mSummaryContent.getChildAt(i);
+            child.setAlpha(progress);
+        }
+
+        if (progress == 0) {
+            if (mOptionsStateChangeListener != null) {
+                mOptionsStateChangeListener.onOptionsOpened();
+            }
+            mSummaryContent.setVisibility(View.GONE);
+            mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_less);
+        } else {
+            mSummaryContent.setVisibility(View.VISIBLE);
+        }
+
+        final float inverseAlpha = 1.0f - progress;
+
+        final int optionCount = mOptionsContainer.getChildCount();
+        for (int i = 0; i < optionCount; i++) {
+            View child = mOptionsContainer.getChildAt(i);
+            child.setAlpha(inverseAlpha);
+        }
+
+        if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+            final int moreOptionCount = mMoreOptionsContainer.getChildCount();
+            for (int i = 0; i < moreOptionCount; i++) {
+                View child = mMoreOptionsContainer.getChildAt(i);
+                child.setAlpha(inverseAlpha);
+            }
+        }
+
+        if (inverseAlpha == 0) {
+            if (mOptionsStateChangeListener != null) {
+                mOptionsStateChangeListener.onOptionsClosed();
+            }
+            if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+                mMoreOptionsContainer.setVisibility(View.INVISIBLE);
+            }
+            mDraggableContent.setVisibility(View.INVISIBLE);
+            mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more);
+        } else {
+            if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+                mMoreOptionsContainer.setVisibility(View.VISIBLE);
+            }
+            mDraggableContent.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private final class DragCallbacks extends ViewDragHelper.Callback {
+        @Override
+        public boolean tryCaptureView(View child, int pointerId) {
+            return child == mDynamicContent && pointerId == FIRST_POINTER_ID;
+        }
+
+        @Override
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+            mCurrentOptionsOffsetY += dy;
+            final float progress = ((float) top - getOpenedOptionsY())
+                    / (getClosedOptionsY() - getOpenedOptionsY());
+
+            mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
+
+            onDragProgress(progress);
+        }
+
+        public void onViewReleased(View child, float velocityX, float velocityY) {
+            final int childTop = child.getTop();
+
+            final int openedOptionsY = getOpenedOptionsY();
+            final int closedOptionsY = getClosedOptionsY();
+
+            if (childTop == openedOptionsY || childTop == closedOptionsY) {
+                return;
+            }
+
+            final int halfRange = closedOptionsY + (openedOptionsY - closedOptionsY) / 2;
+            if (childTop < halfRange) {
+                mDragger.smoothSlideViewTo(child, child.getLeft(), closedOptionsY);
+            } else {
+                mDragger.smoothSlideViewTo(child, child.getLeft(), openedOptionsY);
+            }
+
+            invalidate();
+        }
+
+        public int getViewVerticalDragRange(View child) {
+            return mDraggableContent.getHeight();
+        }
+
+        public int clampViewPositionVertical(View child, int top, int dy) {
+            final int staticOptionBottom = mStaticContent.getBottom();
+            return Math.max(Math.min(top, getOpenedOptionsY()), getClosedOptionsY());
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
new file mode 100644
index 0000000..d6bb7c8
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 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.printspooler.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/**
+ * 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 final class FirstFocusableEditText extends EditText {
+    private boolean mClickedBeforeFocus;
+    private CharSequence mError;
+
+    public FirstFocusableEditText(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);
+    }
+}
\ No newline at end of file
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
new file mode 100644
index 0000000..23c8d08
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 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.printspooler.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.printspooler.R;
+
+/**
+ * This class is a layout manager for the print options. The options are
+ * arranged in a configurable number of columns and enough rows to fit all
+ * the options given the column count.
+ */
+@SuppressWarnings("unused")
+public final class PrintOptionsLayout extends ViewGroup {
+
+    private final int mColumnCount;
+
+    public PrintOptionsLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray typedArray = context.obtainStyledAttributes(attrs,
+                R.styleable.PrintOptionsLayout);
+        mColumnCount = typedArray.getInteger(R.styleable.PrintOptionsLayout_columnCount, 0);
+        typedArray.recycle();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        final int columnWidth = (widthSize != 0)
+                ? (widthSize - mPaddingLeft - mPaddingRight) / mColumnCount : 0;
+
+        int width = 0;
+        int height = 0;
+        int childState = 0;
+
+        final int childCount = getChildCount();
+        final int rowCount = childCount / mColumnCount + childCount % mColumnCount;
+
+        for (int row = 0; row < rowCount; row++) {
+            int rowWidth = 0;
+            int rowHeight = 0;
+
+            for (int col = 0; col < mColumnCount; col++) {
+                final int childIndex = row * mColumnCount + col;
+
+                if (childIndex >= childCount) {
+                    break;
+                }
+
+                View child = getChildAt(childIndex);
+
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+
+                MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
+
+                final int childWidthMeasureSpec;
+                if (columnWidth > 0) {
+                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            columnWidth - childParams.getMarginStart() - childParams.getMarginEnd(),
+                            MeasureSpec.EXACTLY);
+                } else {
+                    childWidthMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                            getPaddingStart() + getPaddingEnd() + width, childParams.width);
+                }
+
+                final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                        getPaddingTop() + getPaddingBottom() + height, childParams.height);
+
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+                childState = combineMeasuredStates(childState, child.getMeasuredState());
+
+                rowWidth += child.getMeasuredWidth() + childParams.getMarginStart()
+                        + childParams.getMarginEnd();
+
+                rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + childParams.topMargin
+                        + childParams.bottomMargin);
+            }
+
+            width = Math.max(width, rowWidth);
+            height += rowHeight;
+        }
+
+        width += getPaddingStart() + getPaddingEnd();
+        width = Math.max(width, getMinimumWidth());
+
+        height += getPaddingTop() + getPaddingBottom();
+        height = Math.max(height, getMinimumHeight());
+
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int childCount = getChildCount();
+        final int rowCount = childCount / mColumnCount + childCount % mColumnCount;
+
+        int cellStart = getPaddingStart();
+        int cellTop = getPaddingTop();
+
+        for (int row = 0; row < rowCount; row++) {
+            int rowHeight = 0;
+
+            for (int col = 0; col < mColumnCount; col++) {
+                final int childIndex = row * mColumnCount + col;
+
+                if (childIndex >= childCount) {
+                    break;
+                }
+
+                View child = getChildAt(childIndex);
+
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+
+                MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
+
+                final int childLeft = cellStart + childParams.getMarginStart();
+                final int childTop = cellTop + childParams.topMargin;
+                final int childRight = childLeft + child.getMeasuredWidth();
+                final int childBottom = childTop + child.getMeasuredHeight();
+
+                child.layout(childLeft, childTop, childRight, childBottom);
+
+                cellStart = childRight + childParams.getMarginEnd();
+
+                rowHeight = Math.max(rowHeight, child.getMeasuredHeight()
+                        + childParams.topMargin + childParams.bottomMargin);
+            }
+
+            cellStart = getPaddingStart();
+            cellTop += cellTop + rowHeight;
+        }
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+    }
+}
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 1bb61d2..a8c739c 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -121,15 +121,7 @@
         throwIfDestroyed();
 
         // Stop tracking printers.
-        if (mTrackedPrinterList != null) {
-            final int trackedPrinterCount = mTrackedPrinterList.size();
-            for (int i = 0; i < trackedPrinterCount; i++) {
-                PrinterId printerId = mTrackedPrinterList.get(i);
-                if (printerId.getServiceName().equals(mComponentName)) {
-                    handleStopPrinterStateTracking(printerId);
-                }
-            }
-        }
+        stopTrackingAllPrinters();
 
         // Stop printer discovery.
         if (mDiscoveryPriorityList != null) {
@@ -270,7 +262,7 @@
             try {
                 mPrintService.createPrinterDiscoverySession();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error creating printer dicovery session.", re);
+                Slog.e(LOG_TAG, "Error creating printer discovery session.", re);
             }
         }
     }
@@ -365,10 +357,14 @@
             if (DEBUG) {
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
             }
+
+            // Stop tracking printers.
+            stopTrackingAllPrinters();
+
             try {
                 mPrintService.stopPrinterDiscovery();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error stopping printer dicovery.", re);
+                Slog.e(LOG_TAG, "Error stopping printer discovery.", re);
             }
         }
     }
@@ -466,6 +462,19 @@
         }
     }
 
+    private void stopTrackingAllPrinters() {
+        if (mTrackedPrinterList == null) {
+            return;
+        }
+        final int trackedPrinterCount = mTrackedPrinterList.size();
+        for (int i = trackedPrinterCount - 1; i >= 0; i--) {
+            PrinterId printerId = mTrackedPrinterList.get(i);
+            if (printerId.getServiceName().equals(mComponentName)) {
+                handleStopPrinterStateTracking(printerId);
+            }
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         String tab = "  ";
         pw.append(prefix).append("service:").println();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index ffe9806..9496cae 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -44,7 +44,7 @@
 
 /**
  * This represents the remote print spooler as a local object to the
- * PrintManagerSerivce. It is responsible to connecting to the remote
+ * PrintManagerService. It is responsible to connecting to the remote
  * spooler if needed, to make the timed remote calls, to handle
  * remote exceptions, and to bind/unbind to the remote instance as
  * needed.
@@ -99,7 +99,7 @@
         mClient = new PrintSpoolerClient(this);
         mIntent = new Intent();
         mIntent.setComponent(new ComponentName("com.android.printspooler",
-                "com.android.printspooler.PrintSpoolerService"));
+                "com.android.printspooler.model.PrintSpoolerService"));
     }
 
     public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,