Home | History | Annotate | Download | only in printspooler
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.printspooler;
     18 
     19 import android.app.Activity;
     20 import android.app.Dialog;
     21 import android.app.LoaderManager;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.Loader;
     27 import android.content.ServiceConnection;
     28 import android.content.pm.PackageInfo;
     29 import android.content.pm.PackageManager.NameNotFoundException;
     30 import android.content.pm.ResolveInfo;
     31 import android.content.pm.ServiceInfo;
     32 import android.database.DataSetObserver;
     33 import android.graphics.Rect;
     34 import android.graphics.drawable.Drawable;
     35 import android.net.Uri;
     36 import android.os.AsyncTask;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.IBinder;
     40 import android.os.IBinder.DeathRecipient;
     41 import android.os.Looper;
     42 import android.os.Message;
     43 import android.os.RemoteException;
     44 import android.print.ILayoutResultCallback;
     45 import android.print.IPrintDocumentAdapter;
     46 import android.print.IPrintDocumentAdapterObserver;
     47 import android.print.IWriteResultCallback;
     48 import android.print.PageRange;
     49 import android.print.PrintAttributes;
     50 import android.print.PrintAttributes.Margins;
     51 import android.print.PrintAttributes.MediaSize;
     52 import android.print.PrintAttributes.Resolution;
     53 import android.print.PrintDocumentAdapter;
     54 import android.print.PrintDocumentInfo;
     55 import android.print.PrintJobId;
     56 import android.print.PrintJobInfo;
     57 import android.print.PrintManager;
     58 import android.print.PrinterCapabilitiesInfo;
     59 import android.print.PrinterId;
     60 import android.print.PrinterInfo;
     61 import android.printservice.PrintService;
     62 import android.printservice.PrintServiceInfo;
     63 import android.provider.DocumentsContract;
     64 import android.text.Editable;
     65 import android.text.TextUtils;
     66 import android.text.TextUtils.SimpleStringSplitter;
     67 import android.text.TextWatcher;
     68 import android.util.ArrayMap;
     69 import android.util.AttributeSet;
     70 import android.util.Log;
     71 import android.view.Gravity;
     72 import android.view.KeyEvent;
     73 import android.view.MotionEvent;
     74 import android.view.View;
     75 import android.view.View.MeasureSpec;
     76 import android.view.View.OnAttachStateChangeListener;
     77 import android.view.View.OnClickListener;
     78 import android.view.View.OnFocusChangeListener;
     79 import android.view.ViewConfiguration;
     80 import android.view.ViewGroup;
     81 import android.view.ViewGroup.LayoutParams;
     82 import android.view.ViewPropertyAnimator;
     83 import android.view.inputmethod.InputMethodManager;
     84 import android.widget.AdapterView;
     85 import android.widget.AdapterView.OnItemSelectedListener;
     86 import android.widget.ArrayAdapter;
     87 import android.widget.BaseAdapter;
     88 import android.widget.Button;
     89 import android.widget.EditText;
     90 import android.widget.FrameLayout;
     91 import android.widget.ImageView;
     92 import android.widget.Spinner;
     93 import android.widget.TextView;
     94 
     95 import com.android.printspooler.MediaSizeUtils.MediaSizeComparator;
     96 
     97 import libcore.io.IoUtils;
     98 
     99 import java.io.File;
    100 import java.io.FileInputStream;
    101 import java.io.FileNotFoundException;
    102 import java.io.IOException;
    103 import java.io.InputStream;
    104 import java.io.OutputStream;
    105 import java.lang.ref.WeakReference;
    106 import java.util.ArrayList;
    107 import java.util.Arrays;
    108 import java.util.Collections;
    109 import java.util.Comparator;
    110 import java.util.List;
    111 import java.util.concurrent.atomic.AtomicInteger;
    112 import java.util.regex.Matcher;
    113 import java.util.regex.Pattern;
    114 
    115 /**
    116  * Activity for configuring a print job.
    117  */
    118 public class PrintJobConfigActivity extends Activity {
    119 
    120     private static final String LOG_TAG = "PrintJobConfigActivity";
    121 
    122     private static final boolean DEBUG = false;
    123 
    124     public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
    125 
    126     private static final int LOADER_ID_PRINTERS_LOADER = 1;
    127 
    128     private static final int ORIENTATION_PORTRAIT = 0;
    129     private static final int ORIENTATION_LANDSCAPE = 1;
    130 
    131     private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
    132 
    133     private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
    134     private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
    135 
    136     private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
    137     private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
    138     private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
    139 
    140     private static final int CONTROLLER_STATE_FINISHED = 1;
    141     private static final int CONTROLLER_STATE_FAILED = 2;
    142     private static final int CONTROLLER_STATE_CANCELLED = 3;
    143     private static final int CONTROLLER_STATE_INITIALIZED = 4;
    144     private static final int CONTROLLER_STATE_STARTED = 5;
    145     private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
    146     private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
    147     private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
    148     private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
    149 
    150     private static final int EDITOR_STATE_INITIALIZED = 1;
    151     private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
    152     private static final int EDITOR_STATE_CANCELLED = 3;
    153 
    154     private static final int MIN_COPIES = 1;
    155     private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
    156 
    157     private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
    158 
    159     private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
    160             "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
    161 
    162     private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
    163             "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
    164             + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
    165 
    166     public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
    167 
    168     private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build();
    169     private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build();
    170 
    171     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
    172         @Override
    173         public void binderDied() {
    174             finish();
    175         }
    176     };
    177 
    178     private Editor mEditor;
    179     private Document mDocument;
    180     private PrintController mController;
    181 
    182     private PrintJobId mPrintJobId;
    183 
    184     private IBinder mIPrintDocumentAdapter;
    185 
    186     private Dialog mGeneratingPrintJobDialog;
    187 
    188     private PrintSpoolerProvider mSpoolerProvider;
    189 
    190     private String mCallingPackageName;
    191 
    192     @Override
    193     protected void onCreate(Bundle bundle) {
    194         super.onCreate(bundle);
    195 
    196         setTitle(R.string.print_dialog);
    197 
    198         Bundle extras = getIntent().getExtras();
    199 
    200         PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
    201         if (printJob == null) {
    202             throw new IllegalArgumentException("printJob cannot be null");
    203         }
    204 
    205         mPrintJobId = printJob.getId();
    206         mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
    207         if (mIPrintDocumentAdapter == null) {
    208             throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
    209         }
    210 
    211         try {
    212             IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter)
    213                     .setObserver(new PrintDocumentAdapterObserver(this));
    214         } catch (RemoteException re) {
    215             finish();
    216             return;
    217         }
    218 
    219         PrintAttributes attributes = printJob.getAttributes();
    220         if (attributes != null) {
    221             mCurrPrintAttributes.copyFrom(attributes);
    222         }
    223 
    224         mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
    225 
    226         setContentView(R.layout.print_job_config_activity_container);
    227 
    228         try {
    229             mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
    230         } catch (RemoteException re) {
    231             finish();
    232             return;
    233         }
    234 
    235         mDocument = new Document();
    236         mEditor = new Editor();
    237 
    238         mSpoolerProvider = new PrintSpoolerProvider(this,
    239                 new Runnable() {
    240             @Override
    241             public void run() {
    242                 // We got the spooler so unleash the UI.
    243                 mController = new PrintController(new RemotePrintDocumentAdapter(
    244                         IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
    245                         mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId)));
    246                 mController.initialize();
    247 
    248                 mEditor.initialize();
    249                 mEditor.postCreate();
    250             }
    251         });
    252     }
    253 
    254     @Override
    255     public void onResume() {
    256         super.onResume();
    257         if (mSpoolerProvider.getSpooler() != null) {
    258             mEditor.refreshCurrentPrinter();
    259         }
    260     }
    261 
    262     @Override
    263     public void onPause() {
    264        if (isFinishing()) {
    265            if (mController != null && mController.hasStarted()) {
    266                mController.finish();
    267            }
    268            if (mEditor != null && mEditor.isPrintConfirmed()
    269                    && mController != null && mController.isFinished()) {
    270                    mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
    271                            PrintJobInfo.STATE_QUEUED, null);
    272            } else {
    273                mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
    274                        PrintJobInfo.STATE_CANCELED, null);
    275            }
    276            if (mGeneratingPrintJobDialog != null) {
    277                mGeneratingPrintJobDialog.dismiss();
    278                mGeneratingPrintJobDialog = null;
    279            }
    280            mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
    281            mSpoolerProvider.destroy();
    282        }
    283         super.onPause();
    284     }
    285 
    286     public boolean onTouchEvent(MotionEvent event) {
    287         if (mController != null && mEditor != null &&
    288                 !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) {
    289             if (!mController.isWorking()) {
    290                 PrintJobConfigActivity.this.finish();
    291             }
    292             mEditor.cancel();
    293             return true;
    294         }
    295         return super.onTouchEvent(event);
    296     }
    297 
    298     public boolean onKeyDown(int keyCode, KeyEvent event) {
    299         if (keyCode == KeyEvent.KEYCODE_BACK) {
    300             event.startTracking();
    301         }
    302         return super.onKeyDown(keyCode, event);
    303     }
    304 
    305     public boolean onKeyUp(int keyCode, KeyEvent event) {
    306         if (mController != null && mEditor != null) {
    307             if (keyCode == KeyEvent.KEYCODE_BACK) {
    308                 if (mEditor.isShwoingGeneratingPrintJobUi()) {
    309                     return true;
    310                 }
    311                 if (event.isTracking() && !event.isCanceled()) {
    312                     if (!mController.isWorking()) {
    313                         PrintJobConfigActivity.this.finish();
    314                     }
    315                 }
    316                 mEditor.cancel();
    317                 return true;
    318             }
    319         }
    320         return super.onKeyUp(keyCode, event);
    321     }
    322 
    323     private boolean printAttributesChanged() {
    324         return !mOldPrintAttributes.equals(mCurrPrintAttributes);
    325     }
    326 
    327     private class PrintController {
    328         private final AtomicInteger mRequestCounter = new AtomicInteger();
    329 
    330         private final RemotePrintDocumentAdapter mRemotePrintAdapter;
    331 
    332         private final Bundle mMetadata;
    333 
    334         private final ControllerHandler mHandler;
    335 
    336         private final LayoutResultCallback mLayoutResultCallback;
    337 
    338         private final WriteResultCallback mWriteResultCallback;
    339 
    340         private int mControllerState = CONTROLLER_STATE_INITIALIZED;
    341 
    342         private boolean mHasStarted;
    343 
    344         private PageRange[] mRequestedPages;
    345 
    346         public PrintController(RemotePrintDocumentAdapter adapter) {
    347             mRemotePrintAdapter = adapter;
    348             mMetadata = new Bundle();
    349             mHandler = new ControllerHandler(getMainLooper());
    350             mLayoutResultCallback = new LayoutResultCallback(mHandler);
    351             mWriteResultCallback = new WriteResultCallback(mHandler);
    352         }
    353 
    354         public void initialize() {
    355             mHasStarted = false;
    356             mControllerState = CONTROLLER_STATE_INITIALIZED;
    357         }
    358 
    359         public void cancel() {
    360             if (isWorking()) {
    361                 mRemotePrintAdapter.cancel();
    362             }
    363             mControllerState = CONTROLLER_STATE_CANCELLED;
    364         }
    365 
    366         public boolean isCancelled() {
    367             return (mControllerState == CONTROLLER_STATE_CANCELLED);
    368         }
    369 
    370         public boolean isFinished() {
    371             return (mControllerState == CONTROLLER_STATE_FINISHED);
    372         }
    373 
    374         public boolean hasStarted() {
    375             return mHasStarted;
    376         }
    377 
    378         public boolean hasPerformedLayout() {
    379             return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
    380         }
    381 
    382         public boolean isPerformingLayout() {
    383             return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED;
    384         }
    385 
    386         public boolean isWorking() {
    387             return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
    388                     || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
    389         }
    390 
    391         public void start() {
    392             mControllerState = CONTROLLER_STATE_STARTED;
    393             mHasStarted = true;
    394             mRemotePrintAdapter.start();
    395         }
    396 
    397         public void update() {
    398             if (!mController.hasStarted()) {
    399                 mController.start();
    400             }
    401 
    402             // If the print attributes are the same and we are performing
    403             // a layout, then we have to wait for it to completed which will
    404             // trigger writing of the necessary pages.
    405             final boolean printAttributesChanged = printAttributesChanged();
    406             if (!printAttributesChanged && isPerformingLayout()) {
    407                 return;
    408             }
    409 
    410             // If print is confirmed we always do a layout since the previous
    411             // ones were for preview and this one is for printing.
    412             if (!printAttributesChanged && !mEditor.isPrintConfirmed()) {
    413                 if (mDocument.info == null) {
    414                     // We are waiting for the result of a layout, so do nothing.
    415                     return;
    416                 }
    417                 // If the attributes didn't change and we have done a layout, then
    418                 // we do not do a layout but may have to ask the app to write some
    419                 // pages. Hence, pretend layout completed and nothing changed, so
    420                 // we handle writing as usual.
    421                 handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
    422             } else {
    423                 mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence(
    424                         mPrintJobId, mCurrPrintAttributes);
    425 
    426                 mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW,
    427                         !mEditor.isPrintConfirmed());
    428 
    429                 mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
    430 
    431                 mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
    432                         mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
    433 
    434                 mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
    435             }
    436         }
    437 
    438         public void finish() {
    439             mControllerState = CONTROLLER_STATE_FINISHED;
    440             mRemotePrintAdapter.finish();
    441         }
    442 
    443         private void handleOnLayoutFinished(PrintDocumentInfo info,
    444                 boolean layoutChanged, int sequence) {
    445             if (mRequestCounter.get() != sequence) {
    446                 return;
    447             }
    448 
    449             if (isCancelled()) {
    450                 mEditor.updateUi();
    451                 if (mEditor.isDone()) {
    452                     PrintJobConfigActivity.this.finish();
    453                 }
    454                 return;
    455             }
    456 
    457             mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
    458 
    459             // For layout purposes we care only whether the type or the page
    460             // count changed. We still do not have the size since we did not
    461             // call write. We use "layoutChanged" set by the application to
    462             // know whether something else changed about the document.
    463             final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info);
    464             // If the info changed, we update the document and the print job.
    465             if (infoChanged) {
    466                 mDocument.info = info;
    467                 // Set the info.
    468                 mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
    469                         mPrintJobId, info);
    470             }
    471 
    472             // If the document info or the layout changed, then
    473             // drop the pages since we have to fetch them again.
    474             if (infoChanged || layoutChanged) {
    475                 mDocument.pages = null;
    476                 mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
    477                         mPrintJobId, null);
    478             }
    479 
    480             // No pages means that the user selected an invalid range while we
    481             // were doing a layout or the layout returned a document info for
    482             // which the selected range is invalid. In such a case we do not
    483             // write anything and wait for the user to fix the range which will
    484             // trigger an update.
    485             mRequestedPages = mEditor.getRequestedPages();
    486             if (mRequestedPages == null || mRequestedPages.length == 0) {
    487                 mEditor.updateUi();
    488                 if (mEditor.isDone()) {
    489                     PrintJobConfigActivity.this.finish();
    490                 }
    491                 return;
    492             } else {
    493                 // If print is not confirmed we just ask for the first of the
    494                 // selected pages to emulate a behavior that shows preview
    495                 // increasing the chances that apps will implement the APIs
    496                 // correctly.
    497                 if (!mEditor.isPrintConfirmed()) {
    498                     if (ALL_PAGES_ARRAY.equals(mRequestedPages)) {
    499                         mRequestedPages = new PageRange[] {new PageRange(0, 0)};
    500                     } else {
    501                         final int firstPage = mRequestedPages[0].getStart();
    502                         mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)};
    503                     }
    504                 }
    505             }
    506 
    507             // If the info and the layout did not change and we already have
    508             // the requested pages, then nothing else to do.
    509             if (!infoChanged && !layoutChanged
    510                     && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
    511                 // Nothing interesting changed and we have all requested pages.
    512                 // Then update the print jobs's pages as we will not do a write
    513                 // and we usually update the pages in the write complete callback.
    514                 updatePrintJobPages(mDocument.pages, mRequestedPages);
    515                 mEditor.updateUi();
    516                 if (mEditor.isDone()) {
    517                     requestCreatePdfFileOrFinish();
    518                 }
    519                 return;
    520             }
    521 
    522             mEditor.updateUi();
    523 
    524             // Request a write of the pages of interest.
    525             mControllerState = CONTROLLER_STATE_WRITE_STARTED;
    526             mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback,
    527                     mRequestCounter.incrementAndGet());
    528         }
    529 
    530         private void handleOnLayoutFailed(final CharSequence error, int sequence) {
    531             if (mRequestCounter.get() != sequence) {
    532                 return;
    533             }
    534             mControllerState = CONTROLLER_STATE_FAILED;
    535             mEditor.showUi(Editor.UI_ERROR, new Runnable() {
    536                 @Override
    537                 public void run() {
    538                     if (!TextUtils.isEmpty(error)) {
    539                         TextView messageView = (TextView) findViewById(R.id.message);
    540                         messageView.setText(error);
    541                     }
    542                 }
    543             });
    544         }
    545 
    546         private void handleOnWriteFinished(PageRange[] pages, int sequence) {
    547             if (mRequestCounter.get() != sequence) {
    548                 return;
    549             }
    550 
    551             if (isCancelled()) {
    552                 if (mEditor.isDone()) {
    553                     PrintJobConfigActivity.this.finish();
    554                 }
    555                 return;
    556             }
    557 
    558             mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
    559 
    560             // Update the document size.
    561             File file = mSpoolerProvider.getSpooler()
    562                     .generateFileForPrintJob(mPrintJobId);
    563             mDocument.info.setDataSize(file.length());
    564 
    565             // Update the print job with the updated info.
    566             mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
    567                     mPrintJobId, mDocument.info);
    568 
    569             // Update which pages we have fetched.
    570             mDocument.pages = PageRangeUtils.normalize(pages);
    571 
    572             if (DEBUG) {
    573                 Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
    574                         + " and got: " + Arrays.toString(mDocument.pages));
    575             }
    576 
    577             updatePrintJobPages(mDocument.pages, mRequestedPages);
    578 
    579             if (mEditor.isDone()) {
    580                 requestCreatePdfFileOrFinish();
    581             }
    582         }
    583 
    584         private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) {
    585             // Adjust the print job pages based on what was requested and written.
    586             // The cases are ordered in the most expected to the least expected.
    587             if (Arrays.equals(writtenPages, requestedPages)) {
    588                 // We got a document with exactly the pages we wanted. Hence,
    589                 // the printer has to print all pages in the data.
    590                 mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
    591                         ALL_PAGES_ARRAY);
    592             } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
    593                 // We requested specific pages but got all of them. Hence,
    594                 // the printer has to print only the requested pages.
    595                 mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
    596                         requestedPages);
    597             } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
    598                 // We requested specific pages and got more but not all pages.
    599                 // Hence, we have to offset appropriately the printed pages to
    600                 // be based off the start of the written ones instead of zero.
    601                 // The written pages are always non-null and not empty.
    602                 final int offset = -writtenPages[0].getStart();
    603                 PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length);
    604                 PageRangeUtils.offset(offsetPages, offset);
    605                 mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
    606                         offsetPages);
    607             } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
    608                     && writtenPages.length == 1 && writtenPages[0].getStart() == 0
    609                     && writtenPages[0].getEnd() == mDocument.info.getPageCount() - 1) {
    610                 // We requested all pages via the special constant and got all
    611                 // of them as an explicit enumeration. Hence, the printer has
    612                 // to print only the requested pages.
    613                 mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
    614                         writtenPages);
    615             } else {
    616                 // We did not get the pages we requested, then the application
    617                 // misbehaves, so we fail quickly.
    618                 mControllerState = CONTROLLER_STATE_FAILED;
    619                 Log.e(LOG_TAG, "Received invalid pages from the app");
    620                 mEditor.showUi(Editor.UI_ERROR, null);
    621             }
    622         }
    623 
    624         private void requestCreatePdfFileOrFinish() {
    625             if (mEditor.isPrintingToPdf()) {
    626                 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    627                 intent.setType("application/pdf");
    628                 intent.putExtra(Intent.EXTRA_TITLE, mDocument.info.getName());
    629                 intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
    630                 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
    631             } else {
    632                 PrintJobConfigActivity.this.finish();
    633             }
    634         }
    635 
    636         private void handleOnWriteFailed(final CharSequence error, int sequence) {
    637             if (mRequestCounter.get() != sequence) {
    638                 return;
    639             }
    640             mControllerState = CONTROLLER_STATE_FAILED;
    641             mEditor.showUi(Editor.UI_ERROR, new Runnable() {
    642                 @Override
    643                 public void run() {
    644                     if (!TextUtils.isEmpty(error)) {
    645                         TextView messageView = (TextView) findViewById(R.id.message);
    646                         messageView.setText(error);
    647                     }
    648                 }
    649             });
    650         }
    651 
    652         private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
    653             if (lhs == rhs) {
    654                 return true;
    655             }
    656             if (lhs == null) {
    657                 if (rhs != null) {
    658                     return false;
    659                 }
    660             } else {
    661                 if (rhs == null) {
    662                     return false;
    663                 }
    664                 if (lhs.getContentType() != rhs.getContentType()
    665                         || lhs.getPageCount() != rhs.getPageCount()) {
    666                     return false;
    667                 }
    668             }
    669             return true;
    670         }
    671 
    672         private final class ControllerHandler extends Handler {
    673             public static final int MSG_ON_LAYOUT_FINISHED = 1;
    674             public static final int MSG_ON_LAYOUT_FAILED = 2;
    675             public static final int MSG_ON_WRITE_FINISHED = 3;
    676             public static final int MSG_ON_WRITE_FAILED = 4;
    677 
    678             public ControllerHandler(Looper looper) {
    679                 super(looper, null, false);
    680             }
    681 
    682             @Override
    683             public void handleMessage(Message message) {
    684                 switch (message.what) {
    685                     case MSG_ON_LAYOUT_FINISHED: {
    686                         PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
    687                         final boolean changed = (message.arg1 == 1);
    688                         final int sequence = message.arg2;
    689                         handleOnLayoutFinished(info, changed, sequence);
    690                     } break;
    691 
    692                     case MSG_ON_LAYOUT_FAILED: {
    693                         CharSequence error = (CharSequence) message.obj;
    694                         final int sequence = message.arg1;
    695                         handleOnLayoutFailed(error, sequence);
    696                     } break;
    697 
    698                     case MSG_ON_WRITE_FINISHED: {
    699                         PageRange[] pages = (PageRange[]) message.obj;
    700                         final int sequence = message.arg1;
    701                         handleOnWriteFinished(pages, sequence);
    702                     } break;
    703 
    704                     case MSG_ON_WRITE_FAILED: {
    705                         CharSequence error = (CharSequence) message.obj;
    706                         final int sequence = message.arg1;
    707                         handleOnWriteFailed(error, sequence);
    708                     } break;
    709                 }
    710             }
    711         }
    712     }
    713 
    714     private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
    715         private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
    716 
    717         public LayoutResultCallback(PrintController.ControllerHandler handler) {
    718             mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
    719         }
    720 
    721         @Override
    722         public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
    723             Handler handler = mWeakHandler.get();
    724             if (handler != null) {
    725                 handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED,
    726                         changed ? 1 : 0, sequence, info).sendToTarget();
    727             }
    728         }
    729 
    730         @Override
    731         public void onLayoutFailed(CharSequence error, int sequence) {
    732             Handler handler = mWeakHandler.get();
    733             if (handler != null) {
    734                 handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED,
    735                         sequence, 0, error).sendToTarget();
    736             }
    737         }
    738     }
    739 
    740     private static final class WriteResultCallback extends IWriteResultCallback.Stub {
    741         private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
    742 
    743         public WriteResultCallback(PrintController.ControllerHandler handler) {
    744             mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
    745         }
    746 
    747         @Override
    748         public void onWriteFinished(PageRange[] pages, int sequence) {
    749             Handler handler = mWeakHandler.get();
    750             if (handler != null) {
    751                 handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED,
    752                         sequence, 0, pages).sendToTarget();
    753             }
    754         }
    755 
    756         @Override
    757         public void onWriteFailed(CharSequence error, int sequence) {
    758             Handler handler = mWeakHandler.get();
    759             if (handler != null) {
    760                 handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED,
    761                     sequence, 0, error).sendToTarget();
    762             }
    763         }
    764     }
    765 
    766     @Override
    767     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    768         switch (requestCode) {
    769             case ACTIVITY_REQUEST_CREATE_FILE: {
    770                 if (data != null) {
    771                     Uri uri = data.getData();
    772                     writePrintJobDataAndFinish(uri);
    773                 } else {
    774                     mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
    775                             new Runnable() {
    776                         @Override
    777                         public void run() {
    778                             mEditor.initialize();
    779                             mEditor.bindUi();
    780                             mEditor.reselectCurrentPrinter();
    781                             mEditor.updateUi();
    782                         }
    783                     });
    784                 }
    785             } break;
    786 
    787             case ACTIVITY_REQUEST_SELECT_PRINTER: {
    788                 if (resultCode == RESULT_OK) {
    789                     PrinterId printerId = (PrinterId) data.getParcelableExtra(
    790                             INTENT_EXTRA_PRINTER_ID);
    791                     if (printerId != null) {
    792                         mEditor.ensurePrinterSelected(printerId);
    793                         break;
    794                     }
    795                 }
    796                 mEditor.ensureCurrentPrinterSelected();
    797             } break;
    798 
    799             case ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS: {
    800                 if (resultCode == RESULT_OK) {
    801                     PrintJobInfo printJobInfo = (PrintJobInfo) data.getParcelableExtra(
    802                             PrintService.EXTRA_PRINT_JOB_INFO);
    803                     if (printJobInfo != null) {
    804                         mEditor.updateFromAdvancedOptions(printJobInfo);
    805                         break;
    806                     }
    807                 }
    808                 mEditor.cancel();
    809                 PrintJobConfigActivity.this.finish();
    810             } break;
    811         }
    812     }
    813 
    814     private void writePrintJobDataAndFinish(final Uri uri) {
    815         new AsyncTask<Void, Void, Void>() {
    816             @Override
    817             protected Void doInBackground(Void... params) {
    818                 InputStream in = null;
    819                 OutputStream out = null;
    820                 try {
    821                     PrintJobInfo printJob = mSpoolerProvider.getSpooler()
    822                             .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
    823                     if (printJob == null) {
    824                         return null;
    825                     }
    826                     File file = mSpoolerProvider.getSpooler()
    827                             .generateFileForPrintJob(mPrintJobId);
    828                     in = new FileInputStream(file);
    829                     out = getContentResolver().openOutputStream(uri);
    830                     final byte[] buffer = new byte[8192];
    831                     while (true) {
    832                         final int readByteCount = in.read(buffer);
    833                         if (readByteCount < 0) {
    834                             break;
    835                         }
    836                         out.write(buffer, 0, readByteCount);
    837                     }
    838                 } catch (FileNotFoundException fnfe) {
    839                     Log.e(LOG_TAG, "Error writing print job data!", fnfe);
    840                 } catch (IOException ioe) {
    841                     Log.e(LOG_TAG, "Error writing print job data!", ioe);
    842                 } finally {
    843                     IoUtils.closeQuietly(in);
    844                     IoUtils.closeQuietly(out);
    845                 }
    846                 return null;
    847             }
    848 
    849             @Override
    850             public void onPostExecute(Void result) {
    851                 mEditor.cancel();
    852                 PrintJobConfigActivity.this.finish();
    853             }
    854         }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
    855     }
    856 
    857     private final class Editor {
    858         private static final int UI_NONE = 0;
    859         private static final int UI_EDITING_PRINT_JOB = 1;
    860         private static final int UI_GENERATING_PRINT_JOB = 2;
    861         private static final int UI_ERROR = 3;
    862 
    863         private EditText mCopiesEditText;
    864 
    865         private TextView mRangeOptionsTitle;
    866         private TextView mPageRangeTitle;
    867         private EditText mPageRangeEditText;
    868 
    869         private Spinner mDestinationSpinner;
    870         private DestinationAdapter mDestinationSpinnerAdapter;
    871 
    872         private Spinner mMediaSizeSpinner;
    873         private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
    874 
    875         private Spinner mColorModeSpinner;
    876         private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
    877 
    878         private Spinner mOrientationSpinner;
    879         private  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
    880 
    881         private Spinner mRangeOptionsSpinner;
    882         private ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
    883 
    884         private SimpleStringSplitter mStringCommaSplitter =
    885                 new SimpleStringSplitter(',');
    886 
    887         private View mContentContainer;
    888 
    889         private View mAdvancedPrintOptionsContainer;
    890 
    891         private Button mAdvancedOptionsButton;
    892 
    893         private Button mPrintButton;
    894 
    895         private PrinterId mNextPrinterId;
    896 
    897         private PrinterInfo mCurrentPrinter;
    898 
    899         private MediaSizeComparator mMediaSizeComparator;
    900 
    901         private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() {
    902             @Override
    903             public void onFocusChange(View view, boolean hasFocus) {
    904                 EditText editText = (EditText) view;
    905                 if (!TextUtils.isEmpty(editText.getText())) {
    906                     editText.setSelection(editText.getText().length());
    907                 }
    908             }
    909         };
    910 
    911         private final OnItemSelectedListener mOnItemSelectedListener =
    912                 new AdapterView.OnItemSelectedListener() {
    913             @Override
    914             public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
    915                 if (spinner == mDestinationSpinner) {
    916                     if (mIgnoreNextDestinationChange) {
    917                         mIgnoreNextDestinationChange = false;
    918                         return;
    919                     }
    920 
    921                     if (position == AdapterView.INVALID_POSITION) {
    922                         updateUi();
    923                         return;
    924                     }
    925 
    926                     if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
    927                         startSelectPrinterActivity();
    928                         return;
    929                     }
    930 
    931                     mCapabilitiesTimeout.remove();
    932 
    933                     mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
    934                             .getItem(position);
    935 
    936                     mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence(
    937                             mPrintJobId, mCurrentPrinter);
    938 
    939                     if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
    940                         mCapabilitiesTimeout.post();
    941                         updateUi();
    942                         return;
    943                     }
    944 
    945                     PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
    946                     if (capabilities == null) {
    947                         mCapabilitiesTimeout.post();
    948                         updateUi();
    949                         refreshCurrentPrinter();
    950                     } else {
    951                         updatePrintAttributes(capabilities);
    952                         updateUi();
    953                         mController.update();
    954                         refreshCurrentPrinter();
    955                     }
    956                 } else if (spinner == mMediaSizeSpinner) {
    957                     if (mIgnoreNextMediaSizeChange) {
    958                         mIgnoreNextMediaSizeChange = false;
    959                         return;
    960                     }
    961                     if (mOldMediaSizeSelectionIndex
    962                             == mMediaSizeSpinner.getSelectedItemPosition()) {
    963                         mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION;
    964                         return;
    965                     }
    966                     SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
    967                     if (mOrientationSpinner.getSelectedItemPosition() == 0) {
    968                         mCurrPrintAttributes.setMediaSize(mediaItem.value.asPortrait());
    969                     } else {
    970                         mCurrPrintAttributes.setMediaSize(mediaItem.value.asLandscape());
    971                     }
    972                     if (!hasErrors()) {
    973                         mController.update();
    974                     }
    975                 } else if (spinner == mColorModeSpinner) {
    976                     if (mIgnoreNextColorChange) {
    977                         mIgnoreNextColorChange = false;
    978                         return;
    979                     }
    980                     if (mOldColorModeSelectionIndex
    981                             == mColorModeSpinner.getSelectedItemPosition()) {
    982                         mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION;
    983                         return;
    984                     }
    985                     SpinnerItem<Integer> colorModeItem =
    986                             mColorModeSpinnerAdapter.getItem(position);
    987                     mCurrPrintAttributes.setColorMode(colorModeItem.value);
    988                     if (!hasErrors()) {
    989                         mController.update();
    990                     }
    991                 } else if (spinner == mOrientationSpinner) {
    992                     if (mIgnoreNextOrientationChange) {
    993                         mIgnoreNextOrientationChange = false;
    994                         return;
    995                     }
    996                     SpinnerItem<Integer> orientationItem =
    997                             mOrientationSpinnerAdapter.getItem(position);
    998                     setCurrentPrintAttributesOrientation(orientationItem.value);
    999                     if (!hasErrors()) {
   1000                         mController.update();
   1001                     }
   1002                 } else if (spinner == mRangeOptionsSpinner) {
   1003                     if (mIgnoreNextRangeOptionChange) {
   1004                         mIgnoreNextRangeOptionChange = false;
   1005                         return;
   1006                     }
   1007                     updateUi();
   1008                     if (!hasErrors()) {
   1009                         mController.update();
   1010                     }
   1011                 }
   1012             }
   1013 
   1014             @Override
   1015             public void onNothingSelected(AdapterView<?> parent) {
   1016                 /* do nothing*/
   1017             }
   1018         };
   1019 
   1020         private void setCurrentPrintAttributesOrientation(int orientation) {
   1021             MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
   1022             if (orientation == ORIENTATION_PORTRAIT) {
   1023                 if (!mediaSize.isPortrait()) {
   1024                     // Rotate the media size.
   1025                     mCurrPrintAttributes.setMediaSize(mediaSize.asPortrait());
   1026 
   1027                     // Rotate the resolution.
   1028                     Resolution oldResolution = mCurrPrintAttributes.getResolution();
   1029                     Resolution newResolution = new Resolution(
   1030                             oldResolution.getId(),
   1031                             oldResolution.getLabel(),
   1032                             oldResolution.getVerticalDpi(),
   1033                             oldResolution.getHorizontalDpi());
   1034                     mCurrPrintAttributes.setResolution(newResolution);
   1035 
   1036                     // Rotate the physical margins.
   1037                     Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
   1038                     Margins newMinMargins = new Margins(
   1039                             oldMinMargins.getBottomMils(),
   1040                             oldMinMargins.getLeftMils(),
   1041                             oldMinMargins.getTopMils(),
   1042                             oldMinMargins.getRightMils());
   1043                     mCurrPrintAttributes.setMinMargins(newMinMargins);
   1044                 }
   1045             } else {
   1046                 if (mediaSize.isPortrait()) {
   1047                     // Rotate the media size.
   1048                     mCurrPrintAttributes.setMediaSize(mediaSize.asLandscape());
   1049 
   1050                     // Rotate the resolution.
   1051                     Resolution oldResolution = mCurrPrintAttributes.getResolution();
   1052                     Resolution newResolution = new Resolution(
   1053                             oldResolution.getId(),
   1054                             oldResolution.getLabel(),
   1055                             oldResolution.getVerticalDpi(),
   1056                             oldResolution.getHorizontalDpi());
   1057                     mCurrPrintAttributes.setResolution(newResolution);
   1058 
   1059                     // Rotate the physical margins.
   1060                     Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
   1061                     Margins newMargins = new Margins(
   1062                             oldMinMargins.getTopMils(),
   1063                             oldMinMargins.getRightMils(),
   1064                             oldMinMargins.getBottomMils(),
   1065                             oldMinMargins.getLeftMils());
   1066                     mCurrPrintAttributes.setMinMargins(newMargins);
   1067                 }
   1068             }
   1069         }
   1070 
   1071         private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) {
   1072             PrintAttributes defaults = capabilities.getDefaults();
   1073 
   1074             // Sort the media sizes based on the current locale.
   1075             List<MediaSize> sortedMediaSizes = new ArrayList<MediaSize>(
   1076                     capabilities.getMediaSizes());
   1077             Collections.sort(sortedMediaSizes, mMediaSizeComparator);
   1078 
   1079             // Media size.
   1080             MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize();
   1081             if (currMediaSize == null) {
   1082                 mCurrPrintAttributes.setMediaSize(defaults.getMediaSize());
   1083             } else {
   1084                 MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
   1085                 final int mediaSizeCount = sortedMediaSizes.size();
   1086                 for (int i = 0; i < mediaSizeCount; i++) {
   1087                     MediaSize mediaSize = sortedMediaSizes.get(i);
   1088                     if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
   1089                         mCurrPrintAttributes.setMediaSize(currMediaSize);
   1090                         break;
   1091                     }
   1092                 }
   1093             }
   1094 
   1095             // Color mode.
   1096             final int colorMode = mCurrPrintAttributes.getColorMode();
   1097             if ((capabilities.getColorModes() & colorMode) == 0) {
   1098                 mCurrPrintAttributes.setColorMode(colorMode);
   1099             }
   1100 
   1101             // Resolution
   1102             Resolution resolution = mCurrPrintAttributes.getResolution();
   1103             if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
   1104                 mCurrPrintAttributes.setResolution(defaults.getResolution());
   1105             }
   1106 
   1107             // Margins.
   1108             Margins margins = mCurrPrintAttributes.getMinMargins();
   1109             if (margins == null) {
   1110                 mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
   1111             } else {
   1112                 Margins minMargins = capabilities.getMinMargins();
   1113                 if (margins.getLeftMils() < minMargins.getLeftMils()
   1114                         || margins.getTopMils() < minMargins.getTopMils()
   1115                         || margins.getRightMils() > minMargins.getRightMils()
   1116                         || margins.getBottomMils() > minMargins.getBottomMils()) {
   1117                     mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
   1118                 }
   1119             }
   1120         }
   1121 
   1122         private final TextWatcher mCopiesTextWatcher = new TextWatcher() {
   1123             @Override
   1124             public void onTextChanged(CharSequence s, int start, int before, int count) {
   1125                 /* do nothing */
   1126             }
   1127 
   1128             @Override
   1129             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   1130                 /* do nothing */
   1131             }
   1132 
   1133             @Override
   1134             public void afterTextChanged(Editable editable) {
   1135                 if (mIgnoreNextCopiesChange) {
   1136                     mIgnoreNextCopiesChange = false;
   1137                     return;
   1138                 }
   1139 
   1140                 final boolean hadErrors = hasErrors();
   1141 
   1142                 if (editable.length() == 0) {
   1143                     mCopiesEditText.setError("");
   1144                     updateUi();
   1145                     return;
   1146                 }
   1147 
   1148                 int copies = 0;
   1149                 try {
   1150                     copies = Integer.parseInt(editable.toString());
   1151                 } catch (NumberFormatException nfe) {
   1152                     /* ignore */
   1153                 }
   1154 
   1155                 if (copies < MIN_COPIES) {
   1156                     mCopiesEditText.setError("");
   1157                     updateUi();
   1158                     return;
   1159                 }
   1160 
   1161                 mCopiesEditText.setError(null);
   1162                 mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
   1163                         mPrintJobId, copies);
   1164                 updateUi();
   1165 
   1166                 if (hadErrors && !hasErrors() && printAttributesChanged()) {
   1167                     mController.update();
   1168                 }
   1169             }
   1170         };
   1171 
   1172         private final TextWatcher mRangeTextWatcher = new TextWatcher() {
   1173             @Override
   1174             public void onTextChanged(CharSequence s, int start, int before, int count) {
   1175                 /* do nothing */
   1176             }
   1177 
   1178             @Override
   1179             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   1180                 /* do nothing */
   1181             }
   1182 
   1183             @Override
   1184             public void afterTextChanged(Editable editable) {
   1185                 if (mIgnoreNextRangeChange) {
   1186                     mIgnoreNextRangeChange = false;
   1187                     return;
   1188                 }
   1189 
   1190                 final boolean hadErrors = hasErrors();
   1191 
   1192                 String text = editable.toString();
   1193 
   1194                 if (TextUtils.isEmpty(text)) {
   1195                     mPageRangeEditText.setError("");
   1196                     updateUi();
   1197                     return;
   1198                 }
   1199 
   1200                 String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
   1201                 if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
   1202                     mPageRangeEditText.setError("");
   1203                     updateUi();
   1204                     return;
   1205                 }
   1206 
   1207                 // The range
   1208                 Matcher matcher = PATTERN_DIGITS.matcher(text);
   1209                 while (matcher.find()) {
   1210                     String numericString = text.substring(matcher.start(), matcher.end()).trim();
   1211                     if (TextUtils.isEmpty(numericString)) {
   1212                         continue;
   1213                     }
   1214                     final int pageIndex = Integer.parseInt(numericString);
   1215                     if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
   1216                         mPageRangeEditText.setError("");
   1217                         updateUi();
   1218                         return;
   1219                     }
   1220                 }
   1221 
   1222                 // We intentionally do not catch the case of the from page being
   1223                 // greater than the to page. When computing the requested pages
   1224                 // we just swap them if necessary.
   1225 
   1226                 // Keep the print job up to date with the selected pages if we
   1227                 // know how many pages are there in the document.
   1228                 PageRange[] requestedPages = getRequestedPages();
   1229                 if (requestedPages != null && requestedPages.length > 0
   1230                         && requestedPages[requestedPages.length - 1].getEnd()
   1231                                 < mDocument.info.getPageCount()) {
   1232                     mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
   1233                             mPrintJobId, requestedPages);
   1234                 }
   1235 
   1236                 mPageRangeEditText.setError(null);
   1237                 mPrintButton.setEnabled(true);
   1238                 updateUi();
   1239 
   1240                 if (hadErrors && !hasErrors() && printAttributesChanged()) {
   1241                     updateUi();
   1242                 }
   1243             }
   1244         };
   1245 
   1246         private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout =
   1247                 new WaitForPrinterCapabilitiesTimeout();
   1248 
   1249         private int mEditorState;
   1250 
   1251         private boolean mIgnoreNextDestinationChange;
   1252         private int mOldMediaSizeSelectionIndex;
   1253         private int mOldColorModeSelectionIndex;
   1254         private boolean mIgnoreNextOrientationChange;
   1255         private boolean mIgnoreNextRangeOptionChange;
   1256         private boolean mIgnoreNextCopiesChange;
   1257         private boolean mIgnoreNextRangeChange;
   1258         private boolean mIgnoreNextMediaSizeChange;
   1259         private boolean mIgnoreNextColorChange;
   1260 
   1261         private int mCurrentUi = UI_NONE;
   1262 
   1263         private boolean mFavoritePrinterSelected;
   1264 
   1265         public Editor() {
   1266             showUi(UI_EDITING_PRINT_JOB, null);
   1267         }
   1268 
   1269         public void postCreate() {
   1270             // Destination.
   1271             mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this);
   1272             mDestinationSpinnerAdapter = new DestinationAdapter();
   1273             mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
   1274                 @Override
   1275                 public void onChanged() {
   1276                     // Initially, we have only safe to PDF as a printer but after some
   1277                     // printers are loaded we want to select the user's favorite one
   1278                     // which is the first.
   1279                     if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 2) {
   1280                         mFavoritePrinterSelected = true;
   1281                         mDestinationSpinner.setSelection(0);
   1282                         // Workaround again the weird spinner behavior to notify for selection
   1283                         // change on the next layout pass as the current printer is used below.
   1284                         mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0);
   1285                     }
   1286 
   1287                     // If there is a next printer to select and we succeed selecting
   1288                     // it - done. Let the selection handling code make everything right.
   1289                     if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) {
   1290                         mNextPrinterId = null;
   1291                         return;
   1292                     }
   1293 
   1294                     // If the current printer properties changed, we update the UI.
   1295                     if (mCurrentPrinter != null) {
   1296                         final int printerCount = mDestinationSpinnerAdapter.getCount();
   1297                         for (int i = 0; i < printerCount; i++) {
   1298                             Object item = mDestinationSpinnerAdapter.getItem(i);
   1299                             // Some items are not printers
   1300                             if (item instanceof PrinterInfo) {
   1301                                 PrinterInfo printer = (PrinterInfo) item;
   1302                                 if (!printer.getId().equals(mCurrentPrinter.getId())) {
   1303                                     continue;
   1304                                 }
   1305 
   1306                                 // If nothing changed - done.
   1307                                 if (mCurrentPrinter.equals(printer)) {
   1308                                     return;
   1309                                 }
   1310 
   1311                                 // If the current printer became available and has no
   1312                                 // capabilities, we refresh it.
   1313                                 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
   1314                                         && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
   1315                                         && printer.getCapabilities() == null) {
   1316                                     if (!mCapabilitiesTimeout.isPosted()) {
   1317                                         mCapabilitiesTimeout.post();
   1318                                     }
   1319                                     mCurrentPrinter.copyFrom(printer);
   1320                                     refreshCurrentPrinter();
   1321                                     return;
   1322                                 }
   1323 
   1324                                 // If the current printer became unavailable or its
   1325                                 // capabilities go away, we update the UI and add a
   1326                                 // timeout to declare the printer as unavailable.
   1327                                 if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
   1328                                         && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE)
   1329                                     || (mCurrentPrinter.getCapabilities() != null
   1330                                         && printer.getCapabilities() == null)) {
   1331                                     if (!mCapabilitiesTimeout.isPosted()) {
   1332                                         mCapabilitiesTimeout.post();
   1333                                     }
   1334                                     mCurrentPrinter.copyFrom(printer);
   1335                                     updateUi();
   1336                                     return;
   1337                                 }
   1338 
   1339                                 // We just refreshed the current printer.
   1340                                 if (printer.getCapabilities() != null
   1341                                         && mCapabilitiesTimeout.isPosted()) {
   1342                                     mCapabilitiesTimeout.remove();
   1343                                     updatePrintAttributes(printer.getCapabilities());
   1344                                     updateUi();
   1345                                     mController.update();
   1346                                 }
   1347 
   1348                                 // Update the UI if capabilities changed.
   1349                                 boolean capabilitiesChanged = false;
   1350 
   1351                                 if (mCurrentPrinter.getCapabilities() == null) {
   1352                                     if (printer.getCapabilities() != null) {
   1353                                         capabilitiesChanged = true;
   1354                                     }
   1355                                 } else if (!mCurrentPrinter.getCapabilities().equals(
   1356                                         printer.getCapabilities())) {
   1357                                     capabilitiesChanged = true;
   1358                                 }
   1359 
   1360                                 // Update the UI if the status changed.
   1361                                 final boolean statusChanged = mCurrentPrinter.getStatus()
   1362                                         != printer.getStatus();
   1363 
   1364                                 // Update the printer with the latest info.
   1365                                 if (!mCurrentPrinter.equals(printer)) {
   1366                                     mCurrentPrinter.copyFrom(printer);
   1367                                 }
   1368 
   1369                                 if (capabilitiesChanged || statusChanged) {
   1370                                     // If something changed during update...
   1371                                     if (updateUi() || !mController.hasPerformedLayout()) {
   1372                                         // Update the document.
   1373                                         mController.update();
   1374                                     }
   1375                                 }
   1376 
   1377                                 break;
   1378                             }
   1379                         }
   1380                     }
   1381                 }
   1382 
   1383                 @Override
   1384                 public void onInvalidated() {
   1385                     /* do nothing - we always have one fake PDF printer */
   1386                 }
   1387             });
   1388 
   1389             // Media size.
   1390             mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
   1391                     PrintJobConfigActivity.this,
   1392                     R.layout.spinner_dropdown_item, R.id.title);
   1393 
   1394             // Color mode.
   1395             mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
   1396                     PrintJobConfigActivity.this,
   1397                     R.layout.spinner_dropdown_item, R.id.title);
   1398 
   1399             // Orientation
   1400             mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
   1401                     PrintJobConfigActivity.this,
   1402                     R.layout.spinner_dropdown_item, R.id.title);
   1403             String[] orientationLabels = getResources().getStringArray(
   1404                   R.array.orientation_labels);
   1405             mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
   1406                     ORIENTATION_PORTRAIT, orientationLabels[0]));
   1407             mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
   1408                     ORIENTATION_LANDSCAPE, orientationLabels[1]));
   1409 
   1410             // Range options
   1411             mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
   1412                     PrintJobConfigActivity.this,
   1413                     R.layout.spinner_dropdown_item, R.id.title);
   1414             final int[] rangeOptionsValues = getResources().getIntArray(
   1415                     R.array.page_options_values);
   1416             String[] rangeOptionsLabels = getResources().getStringArray(
   1417                     R.array.page_options_labels);
   1418             final int rangeOptionsCount = rangeOptionsLabels.length;
   1419             for (int i = 0; i < rangeOptionsCount; i++) {
   1420                 mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
   1421                         rangeOptionsValues[i], rangeOptionsLabels[i]));
   1422             }
   1423 
   1424             showUi(UI_EDITING_PRINT_JOB, null);
   1425             bindUi();
   1426             updateUi();
   1427         }
   1428 
   1429         public void reselectCurrentPrinter() {
   1430             if (mCurrentPrinter != null) {
   1431                 // TODO: While the data did not change and we set the adapter
   1432                 // to a newly inflated spinner, the latter does not show the
   1433                 // current item unless we poke the adapter. This requires more
   1434                 // investigation. Maybe an optimization in AdapterView does not
   1435                 // call into the adapter if the view is not visible which is the
   1436                 // case when we set the adapter.
   1437                 mDestinationSpinnerAdapter.notifyDataSetChanged();
   1438                 final int position = mDestinationSpinnerAdapter.getPrinterIndex(
   1439                         mCurrentPrinter.getId());
   1440                 mDestinationSpinner.setSelection(position);
   1441             }
   1442         }
   1443 
   1444         public void refreshCurrentPrinter() {
   1445             PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
   1446             if (printer != null) {
   1447                 FusedPrintersProvider printersLoader = (FusedPrintersProvider)
   1448                         (Loader<?>) getLoaderManager().getLoader(
   1449                                 LOADER_ID_PRINTERS_LOADER);
   1450                 if (printersLoader != null) {
   1451                     printersLoader.setTrackedPrinter(printer.getId());
   1452                 }
   1453             }
   1454         }
   1455 
   1456         public void addCurrentPrinterToHistory() {
   1457             PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
   1458             PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId();
   1459             if (printer != null && !printer.getId().equals(fakePdfPritnerId)) {
   1460                 FusedPrintersProvider printersLoader = (FusedPrintersProvider)
   1461                         (Loader<?>) getLoaderManager().getLoader(
   1462                                 LOADER_ID_PRINTERS_LOADER);
   1463                 if (printersLoader != null) {
   1464                     printersLoader.addHistoricalPrinter(printer);
   1465                 }
   1466             }
   1467         }
   1468 
   1469         public void updateFromAdvancedOptions(PrintJobInfo printJobInfo) {
   1470             boolean updateContent = false;
   1471 
   1472             // Copies.
   1473             mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
   1474 
   1475             // Media size and orientation
   1476             PrintAttributes attributes = printJobInfo.getAttributes();
   1477             if (!mCurrPrintAttributes.getMediaSize().equals(attributes.getMediaSize())) {
   1478                 final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
   1479                 for (int i = 0; i < mediaSizeCount; i++) {
   1480                     MediaSize mediaSize = mMediaSizeSpinnerAdapter.getItem(i).value;
   1481                     if (mediaSize.asPortrait().equals(attributes.getMediaSize().asPortrait())) {
   1482                         updateContent = true;
   1483                         mCurrPrintAttributes.setMediaSize(attributes.getMediaSize());
   1484                         mMediaSizeSpinner.setSelection(i);
   1485                         mIgnoreNextMediaSizeChange = true;
   1486                         if (attributes.getMediaSize().isPortrait()) {
   1487                             mOrientationSpinner.setSelection(0);
   1488                             mIgnoreNextOrientationChange = true;
   1489                         } else {
   1490                             mOrientationSpinner.setSelection(1);
   1491                             mIgnoreNextOrientationChange = true;
   1492                         }
   1493                         break;
   1494                     }
   1495                 }
   1496             }
   1497 
   1498             // Color mode.
   1499             final int colorMode = attributes.getColorMode();
   1500             if (mCurrPrintAttributes.getColorMode() != colorMode) {
   1501                 if (colorMode == PrintAttributes.COLOR_MODE_MONOCHROME) {
   1502                     updateContent = true;
   1503                     mColorModeSpinner.setSelection(0);
   1504                     mIgnoreNextColorChange = true;
   1505                     mCurrPrintAttributes.setColorMode(attributes.getColorMode());
   1506                 } else if (colorMode == PrintAttributes.COLOR_MODE_COLOR) {
   1507                     updateContent = true;
   1508                     mColorModeSpinner.setSelection(1);
   1509                     mIgnoreNextColorChange = true;
   1510                     mCurrPrintAttributes.setColorMode(attributes.getColorMode());
   1511                 }
   1512             }
   1513 
   1514             // Range.
   1515             PageRange[] pageRanges = printJobInfo.getPages();
   1516             if (pageRanges != null && pageRanges.length > 0) {
   1517                 pageRanges = PageRangeUtils.normalize(pageRanges);
   1518                 final int pageRangeCount = pageRanges.length;
   1519                 if (pageRangeCount == 1 && pageRanges[0] == PageRange.ALL_PAGES) {
   1520                     mRangeOptionsSpinner.setSelection(0);
   1521                 } else {
   1522                     final int pageCount = mDocument.info.getPageCount();
   1523                     if (pageRanges[0].getStart() >= 0
   1524                             && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
   1525                         mRangeOptionsSpinner.setSelection(1);
   1526                         StringBuilder builder = new StringBuilder();
   1527                         for (int i = 0; i < pageRangeCount; i++) {
   1528                             if (builder.length() > 0) {
   1529                                 builder.append(',');
   1530                             }
   1531                             PageRange pageRange = pageRanges[i];
   1532                             final int shownStartPage = pageRange.getStart() + 1;
   1533                             final int shownEndPage = pageRange.getEnd() + 1;
   1534                             builder.append(shownStartPage);
   1535                             if (shownStartPage != shownEndPage) {
   1536                                 builder.append('-');
   1537                                 builder.append(shownEndPage);
   1538                             }
   1539                         }
   1540                         mPageRangeEditText.setText(builder.toString());
   1541                     }
   1542                 }
   1543             }
   1544 
   1545             // Update the advanced options.
   1546             mSpoolerProvider.getSpooler().setPrintJobAdvancedOptionsNoPersistence(
   1547                     mPrintJobId, printJobInfo.getAdvancedOptions());
   1548 
   1549             // Update the content if needed.
   1550             if (updateContent) {
   1551                 mController.update();
   1552             }
   1553         }
   1554 
   1555         public void ensurePrinterSelected(PrinterId printerId) {
   1556             // If the printer is not present maybe the loader is not
   1557             // updated yet. In this case make a note and as soon as
   1558             // the printer appears will will select it.
   1559             if (!selectPrinter(printerId)) {
   1560                 mNextPrinterId = printerId;
   1561             }
   1562         }
   1563 
   1564         public boolean selectPrinter(PrinterId printerId) {
   1565             mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
   1566             final int position = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
   1567             if (position != AdapterView.INVALID_POSITION
   1568                     && position != mDestinationSpinner.getSelectedItemPosition()) {
   1569                 Object item = mDestinationSpinnerAdapter.getItem(position);
   1570                 mCurrentPrinter = (PrinterInfo) item;
   1571                 mDestinationSpinner.setSelection(position);
   1572                 return true;
   1573             }
   1574             return false;
   1575         }
   1576 
   1577         public void ensureCurrentPrinterSelected() {
   1578             if (mCurrentPrinter != null) {
   1579                 selectPrinter(mCurrentPrinter.getId());
   1580             }
   1581         }
   1582 
   1583         public boolean isPrintingToPdf() {
   1584             return mDestinationSpinner.getSelectedItem()
   1585                     == mDestinationSpinnerAdapter.mFakePdfPrinter;
   1586         }
   1587 
   1588         public boolean shouldCloseOnTouch(MotionEvent event) {
   1589             if (event.getAction() != MotionEvent.ACTION_DOWN) {
   1590                 return false;
   1591             }
   1592 
   1593             final int[] locationInWindow = new int[2];
   1594             mContentContainer.getLocationInWindow(locationInWindow);
   1595 
   1596             final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this)
   1597                     .getScaledWindowTouchSlop();
   1598             final int eventX = (int) event.getX();
   1599             final int eventY = (int) event.getY();
   1600             final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop;
   1601             final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth()
   1602                     + windowTouchSlop;
   1603             final int lenientWindowTop = locationInWindow[1] - windowTouchSlop;
   1604             final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight()
   1605                     + windowTouchSlop;
   1606 
   1607             if (eventX < lenientWindowLeft || eventX > lenientWindowRight
   1608                     || eventY < lenientWindowTop || eventY > lenientWindowBottom) {
   1609                 return true;
   1610             }
   1611             return false;
   1612         }
   1613 
   1614         public boolean isShwoingGeneratingPrintJobUi() {
   1615             return (mCurrentUi == UI_GENERATING_PRINT_JOB);
   1616         }
   1617 
   1618         public void showUi(int ui, final Runnable postSwitchCallback) {
   1619             if (ui == UI_NONE) {
   1620                 throw new IllegalStateException("cannot remove the ui");
   1621             }
   1622 
   1623             if (mCurrentUi == ui) {
   1624                 return;
   1625             }
   1626 
   1627             final int oldUi = mCurrentUi;
   1628             mCurrentUi = ui;
   1629 
   1630             switch (oldUi) {
   1631                 case UI_NONE: {
   1632                     switch (ui) {
   1633                         case UI_EDITING_PRINT_JOB: {
   1634                             doUiSwitch(R.layout.print_job_config_activity_content_editing);
   1635                             registerPrintButtonClickListener();
   1636                             if (postSwitchCallback != null) {
   1637                                 postSwitchCallback.run();
   1638                             }
   1639                         } break;
   1640 
   1641                         case UI_GENERATING_PRINT_JOB: {
   1642                             doUiSwitch(R.layout.print_job_config_activity_content_generating);
   1643                             registerCancelButtonClickListener();
   1644                             if (postSwitchCallback != null) {
   1645                                 postSwitchCallback.run();
   1646                             }
   1647                         } break;
   1648                     }
   1649                 } break;
   1650 
   1651                 case UI_EDITING_PRINT_JOB: {
   1652                     switch (ui) {
   1653                         case UI_GENERATING_PRINT_JOB: {
   1654                             animateUiSwitch(R.layout.print_job_config_activity_content_generating,
   1655                                     new Runnable() {
   1656                                 @Override
   1657                                 public void run() {
   1658                                     registerCancelButtonClickListener();
   1659                                     if (postSwitchCallback != null) {
   1660                                         postSwitchCallback.run();
   1661                                     }
   1662                                 }
   1663                             },
   1664                             new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   1665                                     ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
   1666                         } break;
   1667 
   1668                         case UI_ERROR: {
   1669                             animateUiSwitch(R.layout.print_job_config_activity_content_error,
   1670                                     new Runnable() {
   1671                                 @Override
   1672                                 public void run() {
   1673                                     registerOkButtonClickListener();
   1674                                     if (postSwitchCallback != null) {
   1675                                         postSwitchCallback.run();
   1676                                     }
   1677                                 }
   1678                             },
   1679                             new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   1680                                     ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
   1681                         } break;
   1682                     }
   1683                 } break;
   1684 
   1685                 case UI_GENERATING_PRINT_JOB: {
   1686                     switch (ui) {
   1687                         case UI_EDITING_PRINT_JOB: {
   1688                             animateUiSwitch(R.layout.print_job_config_activity_content_editing,
   1689                                     new Runnable() {
   1690                                 @Override
   1691                                 public void run() {
   1692                                     registerPrintButtonClickListener();
   1693                                     if (postSwitchCallback != null) {
   1694                                         postSwitchCallback.run();
   1695                                     }
   1696                                 }
   1697                             },
   1698                             new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   1699                                     ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
   1700                         } break;
   1701 
   1702                         case UI_ERROR: {
   1703                             animateUiSwitch(R.layout.print_job_config_activity_content_error,
   1704                                     new Runnable() {
   1705                                 @Override
   1706                                 public void run() {
   1707                                     registerOkButtonClickListener();
   1708                                     if (postSwitchCallback != null) {
   1709                                         postSwitchCallback.run();
   1710                                     }
   1711                                 }
   1712                             },
   1713                             new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   1714                                     ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
   1715                         } break;
   1716                     }
   1717                 } break;
   1718 
   1719                 case UI_ERROR: {
   1720                     switch (ui) {
   1721                         case UI_EDITING_PRINT_JOB: {
   1722                             animateUiSwitch(R.layout.print_job_config_activity_content_editing,
   1723                                     new Runnable() {
   1724                                 @Override
   1725                                 public void run() {
   1726                                     registerPrintButtonClickListener();
   1727                                     if (postSwitchCallback != null) {
   1728                                         postSwitchCallback.run();
   1729                                     }
   1730                                 }
   1731                             },
   1732                             new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   1733                                     ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
   1734                         } break;
   1735                     }
   1736                 } break;
   1737             }
   1738         }
   1739 
   1740         private void registerAdvancedPrintOptionsButtonClickListener() {
   1741             Button advancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
   1742             advancedOptionsButton.setOnClickListener(new OnClickListener() {
   1743                 @Override
   1744                 public void onClick(View v) {
   1745                     ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
   1746                     String activityName = getAdvancedOptionsActivityName(serviceName);
   1747                     if (TextUtils.isEmpty(activityName)) {
   1748                         return;
   1749                     }
   1750                     Intent intent = new Intent(Intent.ACTION_MAIN);
   1751                     intent.setComponent(new ComponentName(serviceName.getPackageName(),
   1752                             activityName));
   1753 
   1754                     List<ResolveInfo> resolvedActivities = getPackageManager()
   1755                             .queryIntentActivities(intent, 0);
   1756                     if (resolvedActivities.isEmpty()) {
   1757                         return;
   1758                     }
   1759                     // The activity is a component name, therefore it is one or none.
   1760                     if (resolvedActivities.get(0).activityInfo.exported) {
   1761                         PrintJobInfo printJobInfo = mSpoolerProvider.getSpooler().getPrintJobInfo(
   1762                                 mPrintJobId, PrintManager.APP_ID_ANY);
   1763                         intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobInfo);
   1764                         // TODO: Make this an API for the next release.
   1765                         intent.putExtra("android.intent.extra.print.EXTRA_PRINTER_INFO",
   1766                                 mCurrentPrinter);
   1767                         try {
   1768                             startActivityForResult(intent,
   1769                                     ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS);
   1770                         } catch (ActivityNotFoundException anfe) {
   1771                             Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
   1772                         }
   1773                     }
   1774                 }
   1775             });
   1776         }
   1777 
   1778         private void registerPrintButtonClickListener() {
   1779             Button printButton = (Button) findViewById(R.id.print_button);
   1780             printButton.setOnClickListener(new OnClickListener() {
   1781                 @Override
   1782                 public void onClick(View v) {
   1783                     PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
   1784                     if (printer != null) {
   1785                         mEditor.confirmPrint();
   1786                         mController.update();
   1787                         if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
   1788                             mEditor.refreshCurrentPrinter();
   1789                         }
   1790                     } else {
   1791                         mEditor.cancel();
   1792                         PrintJobConfigActivity.this.finish();
   1793                     }
   1794                 }
   1795             });
   1796         }
   1797 
   1798         private void registerCancelButtonClickListener() {
   1799             Button cancelButton = (Button) findViewById(R.id.cancel_button);
   1800             cancelButton.setOnClickListener(new OnClickListener() {
   1801                 @Override
   1802                 public void onClick(View v) {
   1803                     if (!mController.isWorking()) {
   1804                         PrintJobConfigActivity.this.finish();
   1805                     }
   1806                     mEditor.cancel();
   1807                 }
   1808             });
   1809         }
   1810 
   1811         private void registerOkButtonClickListener() {
   1812             Button okButton = (Button) findViewById(R.id.ok_button);
   1813             okButton.setOnClickListener(new OnClickListener() {
   1814                 @Override
   1815                 public void onClick(View v) {
   1816                     mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() {
   1817                         @Override
   1818                         public void run() {
   1819                             // Start over with a clean slate.
   1820                             mOldPrintAttributes.clear();
   1821                             mController.initialize();
   1822                             mEditor.initialize();
   1823                             mEditor.bindUi();
   1824                             mEditor.reselectCurrentPrinter();
   1825                             if (!mController.hasPerformedLayout()) {
   1826                                 mController.update();
   1827                             }
   1828                         }
   1829                     });
   1830                 }
   1831             });
   1832         }
   1833 
   1834         private void doUiSwitch(int showLayoutId) {
   1835             ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
   1836             contentContainer.removeAllViews();
   1837             getLayoutInflater().inflate(showLayoutId, contentContainer, true);
   1838         }
   1839 
   1840         private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction,
   1841                 final LayoutParams containerParams) {
   1842             // Find everything we will shuffle around.
   1843             final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
   1844             final View hidingView = contentContainer.getChildAt(0);
   1845             final View showingView = getLayoutInflater().inflate(showLayoutId,
   1846                     null, false);
   1847 
   1848             // First animation - fade out the old content.
   1849             AutoCancellingAnimator.animate(hidingView).alpha(0.0f)
   1850                     .withLayer().withEndAction(new Runnable() {
   1851                 @Override
   1852                 public void run() {
   1853                     hidingView.setVisibility(View.INVISIBLE);
   1854 
   1855                     // Prepare the new content with correct size and alpha.
   1856                     showingView.setMinimumWidth(contentContainer.getWidth());
   1857                     showingView.setAlpha(0.0f);
   1858 
   1859                     // Compute how to much shrink /stretch the content.
   1860                     final int widthSpec = MeasureSpec.makeMeasureSpec(
   1861                             contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
   1862                     final int heightSpec = MeasureSpec.makeMeasureSpec(
   1863                             contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
   1864                     showingView.measure(widthSpec, heightSpec);
   1865                     final float scaleY = (float) showingView.getMeasuredHeight()
   1866                             / (float) contentContainer.getHeight();
   1867 
   1868                     // Second animation - resize the container.
   1869                     AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY)
   1870                             .withEndAction(new Runnable() {
   1871                         @Override
   1872                         public void run() {
   1873                             // Swap the old and the new content.
   1874                             contentContainer.removeAllViews();
   1875                             contentContainer.setScaleY(1.0f);
   1876                             contentContainer.addView(showingView);
   1877 
   1878                             contentContainer.setLayoutParams(containerParams);
   1879 
   1880                             beforeShowNewUiAction.run();
   1881 
   1882                             // Third animation - show the new content.
   1883                             AutoCancellingAnimator.animate(showingView).alpha(1.0f);
   1884                         }
   1885                     });
   1886                 }
   1887             });
   1888         }
   1889 
   1890         public void initialize() {
   1891             mEditorState = EDITOR_STATE_INITIALIZED;
   1892         }
   1893 
   1894         public boolean isCancelled() {
   1895             return mEditorState == EDITOR_STATE_CANCELLED;
   1896         }
   1897 
   1898         public void cancel() {
   1899             mEditorState = EDITOR_STATE_CANCELLED;
   1900             mController.cancel();
   1901             updateUi();
   1902         }
   1903 
   1904         public boolean isDone() {
   1905             return isPrintConfirmed() || isCancelled();
   1906         }
   1907 
   1908         public boolean isPrintConfirmed() {
   1909             return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
   1910         }
   1911 
   1912         public void confirmPrint() {
   1913             addCurrentPrinterToHistory();
   1914             mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
   1915             showUi(UI_GENERATING_PRINT_JOB, null);
   1916         }
   1917 
   1918         public PageRange[] getRequestedPages() {
   1919             if (hasErrors()) {
   1920                 return null;
   1921             }
   1922             if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
   1923                 List<PageRange> pageRanges = new ArrayList<PageRange>();
   1924                 mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
   1925 
   1926                 while (mStringCommaSplitter.hasNext()) {
   1927                     String range = mStringCommaSplitter.next().trim();
   1928                     if (TextUtils.isEmpty(range)) {
   1929                         continue;
   1930                     }
   1931                     final int dashIndex = range.indexOf('-');
   1932                     final int fromIndex;
   1933                     final int toIndex;
   1934 
   1935                     if (dashIndex > 0) {
   1936                         fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
   1937                         // It is possible that the dash is at the end since the input
   1938                         // verification can has to allow the user to keep entering if
   1939                         // this would lead to a valid input. So we handle this.
   1940                         toIndex = (dashIndex < range.length() - 1)
   1941                                 ? Integer.parseInt(range.substring(dashIndex + 1,
   1942                                         range.length()).trim()) - 1 : fromIndex;
   1943                     } else {
   1944                         fromIndex = toIndex = Integer.parseInt(range) - 1;
   1945                     }
   1946 
   1947                     PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
   1948                             Math.max(fromIndex, toIndex));
   1949                     pageRanges.add(pageRange);
   1950                 }
   1951 
   1952                 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
   1953                 pageRanges.toArray(pageRangesArray);
   1954 
   1955                 return PageRangeUtils.normalize(pageRangesArray);
   1956             }
   1957 
   1958             return ALL_PAGES_ARRAY;
   1959         }
   1960 
   1961         private void bindUi() {
   1962             if (mCurrentUi != UI_EDITING_PRINT_JOB) {
   1963                 return;
   1964             }
   1965 
   1966             // Content container
   1967             mContentContainer = findViewById(R.id.content_container);
   1968 
   1969             // Copies
   1970             mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
   1971             mCopiesEditText.setOnFocusChangeListener(mFocusListener);
   1972             mCopiesEditText.setText(MIN_COPIES_STRING);
   1973             mCopiesEditText.setSelection(mCopiesEditText.getText().length());
   1974             mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
   1975             if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
   1976                 mIgnoreNextCopiesChange = true;
   1977             }
   1978             mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
   1979                     mPrintJobId, MIN_COPIES);
   1980 
   1981             // Destination.
   1982             mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
   1983             mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT);
   1984             mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
   1985             mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
   1986             if (mDestinationSpinnerAdapter.getCount() > 0) {
   1987                 mIgnoreNextDestinationChange = true;
   1988             }
   1989 
   1990             // Media size.
   1991             mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
   1992             mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
   1993             mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
   1994             if (mMediaSizeSpinnerAdapter.getCount() > 0) {
   1995                 mOldMediaSizeSelectionIndex = 0;
   1996             }
   1997 
   1998             // Color mode.
   1999             mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
   2000             mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
   2001             mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
   2002             if (mColorModeSpinnerAdapter.getCount() > 0) {
   2003                 mOldColorModeSelectionIndex = 0;
   2004             }
   2005 
   2006             // Orientation
   2007             mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
   2008             mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
   2009             mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
   2010             if (mOrientationSpinnerAdapter.getCount() > 0) {
   2011                 mIgnoreNextOrientationChange = true;
   2012             }
   2013 
   2014             // Range options
   2015             mRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title);
   2016             mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
   2017             mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
   2018             mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
   2019             if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
   2020                 mIgnoreNextRangeOptionChange = true;
   2021             }
   2022 
   2023             // Page range
   2024             mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
   2025             mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
   2026             mPageRangeEditText.setOnFocusChangeListener(mFocusListener);
   2027             mPageRangeEditText.addTextChangedListener(mRangeTextWatcher);
   2028 
   2029             // Advanced options button.
   2030             mAdvancedPrintOptionsContainer = findViewById(R.id.advanced_settings_container);
   2031             mAdvancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
   2032             registerAdvancedPrintOptionsButtonClickListener();
   2033 
   2034             // Print button
   2035             mPrintButton = (Button) findViewById(R.id.print_button);
   2036             registerPrintButtonClickListener();
   2037         }
   2038 
   2039         public boolean updateUi() {
   2040             if (mCurrentUi != UI_EDITING_PRINT_JOB) {
   2041                 return false;
   2042             }
   2043             if (isPrintConfirmed() || isCancelled()) {
   2044                 mDestinationSpinner.setEnabled(false);
   2045                 mCopiesEditText.setEnabled(false);
   2046                 mMediaSizeSpinner.setEnabled(false);
   2047                 mColorModeSpinner.setEnabled(false);
   2048                 mOrientationSpinner.setEnabled(false);
   2049                 mRangeOptionsSpinner.setEnabled(false);
   2050                 mPageRangeEditText.setEnabled(false);
   2051                 mPrintButton.setEnabled(false);
   2052                 mAdvancedOptionsButton.setEnabled(false);
   2053                 return false;
   2054             }
   2055 
   2056             // If a printer with capabilities is selected, then we enabled all options.
   2057             boolean allOptionsEnabled = false;
   2058             final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
   2059             if (selectedIndex >= 0) {
   2060                 Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
   2061                 if (item instanceof PrinterInfo) {
   2062                     PrinterInfo printer = (PrinterInfo) item;
   2063                     if (printer.getCapabilities() != null
   2064                             && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
   2065                         allOptionsEnabled = true;
   2066                     }
   2067                 }
   2068             }
   2069 
   2070             if (!allOptionsEnabled) {
   2071                 mCopiesEditText.setEnabled(false);
   2072                 mMediaSizeSpinner.setEnabled(false);
   2073                 mColorModeSpinner.setEnabled(false);
   2074                 mOrientationSpinner.setEnabled(false);
   2075                 mRangeOptionsSpinner.setEnabled(false);
   2076                 mPageRangeEditText.setEnabled(false);
   2077                 mPrintButton.setEnabled(false);
   2078                 mAdvancedOptionsButton.setEnabled(false);
   2079                 return false;
   2080             } else {
   2081                 boolean someAttributeSelectionChanged = false;
   2082 
   2083                 PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
   2084                 PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
   2085                 PrintAttributes defaultAttributes = printer.getCapabilities().getDefaults();
   2086 
   2087                 // Media size.
   2088                 // Sort the media sizes based on the current locale.
   2089                 List<MediaSize> mediaSizes = new ArrayList<MediaSize>(capabilities.getMediaSizes());
   2090                 Collections.sort(mediaSizes, mMediaSizeComparator);
   2091 
   2092                 // If the media sizes changed, we update the adapter and the spinner.
   2093                 boolean mediaSizesChanged = false;
   2094                 final int mediaSizeCount = mediaSizes.size();
   2095                 if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
   2096                     mediaSizesChanged = true;
   2097                 } else {
   2098                     for (int i = 0; i < mediaSizeCount; i++) {
   2099                         if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
   2100                             mediaSizesChanged = true;
   2101                             break;
   2102                         }
   2103                     }
   2104                 }
   2105                 if (mediaSizesChanged) {
   2106                     // Remember the old media size to try selecting it again.
   2107                     int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
   2108                     MediaSize oldMediaSize = mCurrPrintAttributes.getMediaSize();
   2109 
   2110                     // Rebuild the adapter data.
   2111                     mMediaSizeSpinnerAdapter.clear();
   2112                     for (int i = 0; i < mediaSizeCount; i++) {
   2113                         MediaSize mediaSize = mediaSizes.get(i);
   2114                         if (mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
   2115                             // Update the index of the old selection.
   2116                             oldMediaSizeNewIndex = i;
   2117                         }
   2118                         mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
   2119                                 mediaSize, mediaSize.getLabel(getPackageManager())));
   2120                     }
   2121 
   2122                     mMediaSizeSpinner.setEnabled(true);
   2123 
   2124                     if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
   2125                         // Select the old media size - nothing really changed.
   2126                         setMediaSizeSpinnerSelectionNoCallback(oldMediaSizeNewIndex);
   2127                     } else {
   2128                         // Select the first or the default and mark if selection changed.
   2129                         final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
   2130                                 defaultAttributes.getMediaSize()), 0);
   2131                         setMediaSizeSpinnerSelectionNoCallback(mediaSizeIndex);
   2132                         if (oldMediaSize.isPortrait()) {
   2133                             mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
   2134                                     .getItem(mediaSizeIndex).value.asPortrait());
   2135                         } else {
   2136                             mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
   2137                                     .getItem(mediaSizeIndex).value.asLandscape());
   2138                         }
   2139                         someAttributeSelectionChanged = true;
   2140                     }
   2141                 }
   2142                 mMediaSizeSpinner.setEnabled(true);
   2143 
   2144                 // Color mode.
   2145                 final int colorModes = capabilities.getColorModes();
   2146 
   2147                 // If the color modes changed, we update the adapter and the spinner.
   2148                 boolean colorModesChanged = false;
   2149                 if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
   2150                     colorModesChanged = true;
   2151                 } else {
   2152                     int remainingColorModes = colorModes;
   2153                     int adapterIndex = 0;
   2154                     while (remainingColorModes != 0) {
   2155                         final int colorBitOffset = Integer.numberOfTrailingZeros(
   2156                                 remainingColorModes);
   2157                         final int colorMode = 1 << colorBitOffset;
   2158                         remainingColorModes &= ~colorMode;
   2159                         if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
   2160                             colorModesChanged = true;
   2161                             break;
   2162                         }
   2163                         adapterIndex++;
   2164                     }
   2165                 }
   2166                 if (colorModesChanged) {
   2167                     // Remember the old color mode to try selecting it again.
   2168                     int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
   2169                     final int oldColorMode = mCurrPrintAttributes.getColorMode();
   2170 
   2171                     // Rebuild the adapter data.
   2172                     mColorModeSpinnerAdapter.clear();
   2173                     String[] colorModeLabels = getResources().getStringArray(
   2174                             R.array.color_mode_labels);
   2175                     int remainingColorModes = colorModes;
   2176                     while (remainingColorModes != 0) {
   2177                         final int colorBitOffset = Integer.numberOfTrailingZeros(
   2178                                 remainingColorModes);
   2179                         final int colorMode = 1 << colorBitOffset;
   2180                         if (colorMode == oldColorMode) {
   2181                             // Update the index of the old selection.
   2182                             oldColorModeNewIndex = colorBitOffset;
   2183                         }
   2184                         remainingColorModes &= ~colorMode;
   2185                         mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
   2186                                 colorModeLabels[colorBitOffset]));
   2187                     }
   2188                     mColorModeSpinner.setEnabled(true);
   2189                     if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
   2190                         // Select the old color mode - nothing really changed.
   2191                         setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex);
   2192                     } else {
   2193                         final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
   2194                                     (colorModes & defaultAttributes.getColorMode()));
   2195                         setColorModeSpinnerSelectionNoCallback(selectedColorModeIndex);
   2196                         mCurrPrintAttributes.setColorMode(mColorModeSpinnerAdapter
   2197                                 .getItem(selectedColorModeIndex).value);
   2198                         someAttributeSelectionChanged = true;
   2199                     }
   2200                 }
   2201                 mColorModeSpinner.setEnabled(true);
   2202 
   2203                 // Orientation
   2204                 MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
   2205                 if (mediaSize.isPortrait()
   2206                         && mOrientationSpinner.getSelectedItemPosition() != 0) {
   2207                     mIgnoreNextOrientationChange = true;
   2208                     mOrientationSpinner.setSelection(0);
   2209                 } else if (!mediaSize.isPortrait()
   2210                         && mOrientationSpinner.getSelectedItemPosition() != 1) {
   2211                     mIgnoreNextOrientationChange = true;
   2212                     mOrientationSpinner.setSelection(1);
   2213                 }
   2214                 mOrientationSpinner.setEnabled(true);
   2215 
   2216                 // Range options
   2217                 PrintDocumentInfo info = mDocument.info;
   2218                 if (info != null && info.getPageCount() > 0) {
   2219                     if (info.getPageCount() == 1) {
   2220                         mRangeOptionsSpinner.setEnabled(false);
   2221                     } else {
   2222                         mRangeOptionsSpinner.setEnabled(true);
   2223                         if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
   2224                             if (!mPageRangeEditText.isEnabled()) {
   2225                                 mPageRangeEditText.setEnabled(true);
   2226                                 mPageRangeEditText.setVisibility(View.VISIBLE);
   2227                                 mPageRangeTitle.setVisibility(View.VISIBLE);
   2228                                 mPageRangeEditText.requestFocus();
   2229                                 InputMethodManager imm = (InputMethodManager)
   2230                                         getSystemService(INPUT_METHOD_SERVICE);
   2231                                 imm.showSoftInput(mPageRangeEditText, 0);
   2232                             }
   2233                         } else {
   2234                             mPageRangeEditText.setEnabled(false);
   2235                             mPageRangeEditText.setVisibility(View.INVISIBLE);
   2236                             mPageRangeTitle.setVisibility(View.INVISIBLE);
   2237                         }
   2238                     }
   2239                     final int pageCount = mDocument.info.getPageCount();
   2240                     String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
   2241                             ? getString(R.string.label_pages, String.valueOf(pageCount))
   2242                             : getString(R.string.page_count_unknown);
   2243                     mRangeOptionsTitle.setText(title);
   2244                 } else {
   2245                     if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
   2246                         mIgnoreNextRangeOptionChange = true;
   2247                         mRangeOptionsSpinner.setSelection(0);
   2248                     }
   2249                     mRangeOptionsSpinner.setEnabled(false);
   2250                     mRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
   2251                     mPageRangeEditText.setEnabled(false);
   2252                     mPageRangeEditText.setVisibility(View.INVISIBLE);
   2253                     mPageRangeTitle.setVisibility(View.INVISIBLE);
   2254                 }
   2255 
   2256                 // Advanced print options
   2257                 ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
   2258                 if (!TextUtils.isEmpty(getAdvancedOptionsActivityName(serviceName))) {
   2259                     mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
   2260                     mAdvancedOptionsButton.setEnabled(true);
   2261                 } else {
   2262                     mAdvancedPrintOptionsContainer.setVisibility(View.GONE);
   2263                     mAdvancedOptionsButton.setEnabled(false);
   2264                 }
   2265 
   2266                 // Print
   2267                 if (mDestinationSpinner.getSelectedItemId()
   2268                         != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
   2269                     String newText = getString(R.string.print_button);
   2270                     if (!TextUtils.equals(newText, mPrintButton.getText())) {
   2271                         mPrintButton.setText(R.string.print_button);
   2272                     }
   2273                 } else {
   2274                     String newText = getString(R.string.save_button);
   2275                     if (!TextUtils.equals(newText, mPrintButton.getText())) {
   2276                         mPrintButton.setText(R.string.save_button);
   2277                     }
   2278                 }
   2279                 if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
   2280                             && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
   2281                         || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
   2282                             && (!mController.hasPerformedLayout() || hasErrors()))) {
   2283                     mPrintButton.setEnabled(false);
   2284                 } else {
   2285                     mPrintButton.setEnabled(true);
   2286                 }
   2287 
   2288                 // Copies
   2289                 if (mDestinationSpinner.getSelectedItemId()
   2290                         != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
   2291                     mCopiesEditText.setEnabled(true);
   2292                 } else {
   2293                     mCopiesEditText.setEnabled(false);
   2294                 }
   2295                 if (mCopiesEditText.getError() == null
   2296                         && TextUtils.isEmpty(mCopiesEditText.getText())) {
   2297                     mIgnoreNextCopiesChange = true;
   2298                     mCopiesEditText.setText(String.valueOf(MIN_COPIES));
   2299                     mCopiesEditText.requestFocus();
   2300                 }
   2301 
   2302                 return someAttributeSelectionChanged;
   2303             }
   2304         }
   2305 
   2306         private String getAdvancedOptionsActivityName(ComponentName serviceName) {
   2307             PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
   2308             List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
   2309             final int printServiceCount = printServices.size();
   2310             for (int i = 0; i < printServiceCount; i ++) {
   2311                 PrintServiceInfo printServiceInfo = printServices.get(i);
   2312                 ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
   2313                 if (serviceInfo.name.equals(serviceName.getClassName())
   2314                         && serviceInfo.packageName.equals(serviceName.getPackageName())) {
   2315                     return printServiceInfo.getAdvancedOptionsActivityName();
   2316                 }
   2317             }
   2318             return null;
   2319         }
   2320 
   2321         private void setMediaSizeSpinnerSelectionNoCallback(int position) {
   2322             if (mMediaSizeSpinner.getSelectedItemPosition() != position) {
   2323                 mOldMediaSizeSelectionIndex = position;
   2324                 mMediaSizeSpinner.setSelection(position);
   2325             }
   2326         }
   2327 
   2328         private void setColorModeSpinnerSelectionNoCallback(int position) {
   2329             if (mColorModeSpinner.getSelectedItemPosition() != position) {
   2330                 mOldColorModeSelectionIndex = position;
   2331                 mColorModeSpinner.setSelection(position);
   2332             }
   2333         }
   2334 
   2335         private void startSelectPrinterActivity() {
   2336             Intent intent = new Intent(PrintJobConfigActivity.this,
   2337                     SelectPrinterActivity.class);
   2338             startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
   2339         }
   2340 
   2341         private boolean hasErrors() {
   2342             if (mCopiesEditText.getError() != null) {
   2343                 return true;
   2344             }
   2345             return mPageRangeEditText.getVisibility() == View.VISIBLE
   2346                     && mPageRangeEditText.getError() != null;
   2347         }
   2348 
   2349         private final class SpinnerItem<T> {
   2350             final T value;
   2351             CharSequence label;
   2352 
   2353             public SpinnerItem(T value, CharSequence label) {
   2354                 this.value = value;
   2355                 this.label = label;
   2356             }
   2357 
   2358             public String toString() {
   2359                 return label.toString();
   2360             }
   2361         }
   2362 
   2363         private final class WaitForPrinterCapabilitiesTimeout implements Runnable {
   2364             private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec
   2365 
   2366             private boolean mIsPosted;
   2367 
   2368             public void post() {
   2369                 if (!mIsPosted) {
   2370                     mDestinationSpinner.postDelayed(this,
   2371                             GET_CAPABILITIES_TIMEOUT_MILLIS);
   2372                     mIsPosted = true;
   2373                 }
   2374             }
   2375 
   2376             public void remove() {
   2377                 if (mIsPosted) {
   2378                     mIsPosted = false;
   2379                     mDestinationSpinner.removeCallbacks(this);
   2380                 }
   2381             }
   2382 
   2383             public boolean isPosted() {
   2384                 return mIsPosted;
   2385             }
   2386 
   2387             @Override
   2388             public void run() {
   2389                 mIsPosted = false;
   2390                 if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
   2391                     View itemView = mDestinationSpinner.getSelectedView();
   2392                     TextView titleView = (TextView) itemView.findViewById(R.id.subtitle);
   2393                     try {
   2394                         PackageInfo packageInfo = getPackageManager().getPackageInfo(
   2395                                 mCurrentPrinter.getId().getServiceName().getPackageName(), 0);
   2396                         CharSequence service = packageInfo.applicationInfo.loadLabel(
   2397                                 getPackageManager());
   2398                         String subtitle = getString(R.string.printer_unavailable, service.toString());
   2399                         titleView.setText(subtitle);
   2400                     } catch (NameNotFoundException nnfe) {
   2401                         /* ignore */
   2402                     }
   2403                 }
   2404             }
   2405         }
   2406 
   2407         private final class DestinationAdapter extends BaseAdapter
   2408                 implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
   2409             private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
   2410 
   2411             private PrinterInfo mFakePdfPrinter;
   2412 
   2413             public DestinationAdapter() {
   2414                 getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
   2415             }
   2416 
   2417             public int getPrinterIndex(PrinterId printerId) {
   2418                 for (int i = 0; i < getCount(); i++) {
   2419                     PrinterInfo printer = (PrinterInfo) getItem(i);
   2420                     if (printer != null && printer.getId().equals(printerId)) {
   2421                         return i;
   2422                     }
   2423                 }
   2424                 return AdapterView.INVALID_POSITION;
   2425             }
   2426 
   2427             public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
   2428                 final int printerCount = mPrinters.size();
   2429                 for (int i = 0; i < printerCount; i++) {
   2430                     PrinterInfo printer = (PrinterInfo) mPrinters.get(i);
   2431                     if (printer.getId().equals(printerId)) {
   2432                         // If already in the list - do nothing.
   2433                         if (i < getCount() - 2) {
   2434                             return;
   2435                         }
   2436                         // Else replace the last one (two items are not printers).
   2437                         final int lastPrinterIndex = getCount() - 3;
   2438                         mPrinters.set(i, mPrinters.get(lastPrinterIndex));
   2439                         mPrinters.set(lastPrinterIndex, printer);
   2440                         notifyDataSetChanged();
   2441                         return;
   2442                     }
   2443                 }
   2444             }
   2445 
   2446             @Override
   2447             public int getCount() {
   2448                 if (mFakePdfPrinter == null) {
   2449                     return 0;
   2450                 }
   2451                 return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
   2452             }
   2453 
   2454             @Override
   2455             public boolean isEnabled(int position) {
   2456                 Object item = getItem(position);
   2457                 if (item instanceof PrinterInfo) {
   2458                     PrinterInfo printer = (PrinterInfo) item;
   2459                     return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
   2460                 }
   2461                 return true;
   2462             }
   2463 
   2464             @Override
   2465             public Object getItem(int position) {
   2466                 if (mPrinters.isEmpty()) {
   2467                     if (position == 0 && mFakePdfPrinter != null) {
   2468                         return mFakePdfPrinter;
   2469                     }
   2470                 } else {
   2471                     if (position < 1) {
   2472                         return mPrinters.get(position);
   2473                     }
   2474                     if (position == 1 && mFakePdfPrinter != null) {
   2475                         return mFakePdfPrinter;
   2476                     }
   2477                     if (position < getCount() - 1) {
   2478                         return mPrinters.get(position - 1);
   2479                     }
   2480                 }
   2481                 return null;
   2482             }
   2483 
   2484             @Override
   2485             public long getItemId(int position) {
   2486                 if (mPrinters.isEmpty()) {
   2487                     if (mFakePdfPrinter != null) {
   2488                         if (position == 0) {
   2489                             return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
   2490                         } else if (position == 1) {
   2491                             return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
   2492                         }
   2493                     }
   2494                 } else {
   2495                     if (position == 1 && mFakePdfPrinter != null) {
   2496                         return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
   2497                     }
   2498                     if (position == getCount() - 1) {
   2499                         return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
   2500                     }
   2501                 }
   2502                 return position;
   2503             }
   2504 
   2505             @Override
   2506             public View getDropDownView(int position, View convertView,
   2507                     ViewGroup parent) {
   2508                 View view = getView(position, convertView, parent);
   2509                 view.setEnabled(isEnabled(position));
   2510                 return view;
   2511             }
   2512 
   2513             @Override
   2514             public View getView(int position, View convertView, ViewGroup parent) {
   2515                 if (convertView == null) {
   2516                     convertView = getLayoutInflater().inflate(
   2517                             R.layout.printer_dropdown_item, parent, false);
   2518                 }
   2519 
   2520                 CharSequence title = null;
   2521                 CharSequence subtitle = null;
   2522                 Drawable icon = null;
   2523 
   2524                 if (mPrinters.isEmpty()) {
   2525                     if (position == 0 && mFakePdfPrinter != null) {
   2526                         PrinterInfo printer = (PrinterInfo) getItem(position);
   2527                         title = printer.getName();
   2528                     } else if (position == 1) {
   2529                         title = getString(R.string.all_printers);
   2530                     }
   2531                 } else {
   2532                     if (position == 1 && mFakePdfPrinter != null) {
   2533                         PrinterInfo printer = (PrinterInfo) getItem(position);
   2534                         title = printer.getName();
   2535                     } else if (position == getCount() - 1) {
   2536                         title = getString(R.string.all_printers);
   2537                     } else {
   2538                         PrinterInfo printer = (PrinterInfo) getItem(position);
   2539                         title = printer.getName();
   2540                         try {
   2541                             PackageInfo packageInfo = getPackageManager().getPackageInfo(
   2542                                     printer.getId().getServiceName().getPackageName(), 0);
   2543                             subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
   2544                             icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
   2545                         } catch (NameNotFoundException nnfe) {
   2546                             /* ignore */
   2547                         }
   2548                     }
   2549                 }
   2550 
   2551                 TextView titleView = (TextView) convertView.findViewById(R.id.title);
   2552                 titleView.setText(title);
   2553 
   2554                 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
   2555                 if (!TextUtils.isEmpty(subtitle)) {
   2556                     subtitleView.setText(subtitle);
   2557                     subtitleView.setVisibility(View.VISIBLE);
   2558                 } else {
   2559                     subtitleView.setText(null);
   2560                     subtitleView.setVisibility(View.GONE);
   2561                 }
   2562 
   2563                 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
   2564                 if (icon != null) {
   2565                     iconView.setImageDrawable(icon);
   2566                     iconView.setVisibility(View.VISIBLE);
   2567                 } else {
   2568                     iconView.setVisibility(View.INVISIBLE);
   2569                 }
   2570 
   2571                 return convertView;
   2572             }
   2573 
   2574             @Override
   2575             public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
   2576                 if (id == LOADER_ID_PRINTERS_LOADER) {
   2577                     return new FusedPrintersProvider(PrintJobConfigActivity.this);
   2578                 }
   2579                 return null;
   2580             }
   2581 
   2582             @Override
   2583             public void onLoadFinished(Loader<List<PrinterInfo>> loader,
   2584                     List<PrinterInfo> printers) {
   2585                 // If this is the first load, create the fake PDF printer.
   2586                 // We do this to avoid flicker where the PDF printer is the
   2587                 // only one and as soon as the loader loads the favorites
   2588                 // it gets switched. Not a great user experience.
   2589                 if (mFakePdfPrinter == null) {
   2590                     mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter();
   2591                     updatePrintAttributes(mCurrentPrinter.getCapabilities());
   2592                     updateUi();
   2593                 }
   2594 
   2595                 // We rearrange the printers if the user selects a printer
   2596                 // not shown in the initial short list. Therefore, we have
   2597                 // to keep the printer order.
   2598 
   2599                 // No old printers - do not bother keeping their position.
   2600                 if (mPrinters.isEmpty()) {
   2601                     mPrinters.addAll(printers);
   2602                     mEditor.ensureCurrentPrinterSelected();
   2603                     notifyDataSetChanged();
   2604                     return;
   2605                 }
   2606 
   2607                 // Add the new printers to a map.
   2608                 ArrayMap<PrinterId, PrinterInfo> newPrintersMap =
   2609                         new ArrayMap<PrinterId, PrinterInfo>();
   2610                 final int printerCount = printers.size();
   2611                 for (int i = 0; i < printerCount; i++) {
   2612                     PrinterInfo printer = printers.get(i);
   2613                     newPrintersMap.put(printer.getId(), printer);
   2614                 }
   2615 
   2616                 List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>();
   2617 
   2618                 // Update printers we already have.
   2619                 final int oldPrinterCount = mPrinters.size();
   2620                 for (int i = 0; i < oldPrinterCount; i++) {
   2621                     PrinterId oldPrinterId = mPrinters.get(i).getId();
   2622                     PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
   2623                     if (updatedPrinter != null) {
   2624                         newPrinters.add(updatedPrinter);
   2625                     }
   2626                 }
   2627 
   2628                 // Add the rest of the new printers, i.e. what is left.
   2629                 newPrinters.addAll(newPrintersMap.values());
   2630 
   2631                 mPrinters.clear();
   2632                 mPrinters.addAll(newPrinters);
   2633 
   2634                 mEditor.ensureCurrentPrinterSelected();
   2635                 notifyDataSetChanged();
   2636             }
   2637 
   2638             @Override
   2639             public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
   2640                 mPrinters.clear();
   2641                 notifyDataSetInvalidated();
   2642             }
   2643 
   2644 
   2645             private PrinterInfo createFakePdfPrinter() {
   2646                 MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintJobConfigActivity.this);
   2647 
   2648                 PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
   2649 
   2650                 PrinterCapabilitiesInfo.Builder builder =
   2651                         new PrinterCapabilitiesInfo.Builder(printerId);
   2652 
   2653                 String[] mediaSizeIds = getResources().getStringArray(
   2654                         R.array.pdf_printer_media_sizes);
   2655                 final int mediaSizeIdCount = mediaSizeIds.length;
   2656                 for (int i = 0; i < mediaSizeIdCount; i++) {
   2657                     String id = mediaSizeIds[i];
   2658                     MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
   2659                     builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
   2660                 }
   2661 
   2662                 builder.addResolution(new Resolution("PDF resolution", "PDF resolution",
   2663                             300, 300), true);
   2664                 builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
   2665                         | PrintAttributes.COLOR_MODE_MONOCHROME,
   2666                         PrintAttributes.COLOR_MODE_COLOR);
   2667 
   2668                 return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
   2669                         PrinterInfo.STATUS_IDLE)
   2670                     .setCapabilities(builder.build())
   2671                     .build();
   2672             }
   2673         }
   2674     }
   2675 
   2676     /**
   2677      * An instance of this class class is intended to be the first focusable
   2678      * in a layout to which the system automatically gives focus. It performs
   2679      * some voodoo to avoid the first tap on it to start an edit mode, rather
   2680      * to bring up the IME, i.e. to get the behavior as if the view was not
   2681      * focused.
   2682      */
   2683     public static final class CustomEditText extends EditText {
   2684         private boolean mClickedBeforeFocus;
   2685         private CharSequence mError;
   2686 
   2687         public CustomEditText(Context context, AttributeSet attrs) {
   2688             super(context, attrs);
   2689         }
   2690 
   2691         @Override
   2692         public boolean performClick() {
   2693             super.performClick();
   2694             if (isFocused() && !mClickedBeforeFocus) {
   2695                 clearFocus();
   2696                 requestFocus();
   2697             }
   2698             mClickedBeforeFocus = true;
   2699             return true;
   2700         }
   2701 
   2702         @Override
   2703         public CharSequence getError() {
   2704             return mError;
   2705         }
   2706 
   2707         @Override
   2708         public void setError(CharSequence error, Drawable icon) {
   2709             setCompoundDrawables(null, null, icon, null);
   2710             mError = error;
   2711         }
   2712 
   2713         protected void onFocusChanged(boolean gainFocus, int direction,
   2714                 Rect previouslyFocusedRect) {
   2715             if (!gainFocus) {
   2716                 mClickedBeforeFocus = false;
   2717             }
   2718             super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   2719         }
   2720     }
   2721 
   2722     private static final class Document {
   2723         public PrintDocumentInfo info;
   2724         public PageRange[] pages;
   2725     }
   2726 
   2727     private static final class PageRangeUtils {
   2728 
   2729         private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
   2730             @Override
   2731             public int compare(PageRange lhs, PageRange rhs) {
   2732                 return lhs.getStart() - rhs.getStart();
   2733             }
   2734         };
   2735 
   2736         private PageRangeUtils() {
   2737             throw new UnsupportedOperationException();
   2738         }
   2739 
   2740         public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) {
   2741             if (ourRanges == null || otherRanges == null) {
   2742                 return false;
   2743             }
   2744 
   2745             if (ourRanges.length == 1
   2746                     && PageRange.ALL_PAGES.equals(ourRanges[0])) {
   2747                 return true;
   2748             }
   2749 
   2750             ourRanges = normalize(ourRanges);
   2751             otherRanges = normalize(otherRanges);
   2752 
   2753             // Note that the code below relies on the ranges being normalized
   2754             // which is they contain monotonically increasing non-intersecting
   2755             // subranges whose start is less that or equal to the end.
   2756             int otherRangeIdx = 0;
   2757             final int ourRangeCount = ourRanges.length;
   2758             final int otherRangeCount = otherRanges.length;
   2759             for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
   2760                 PageRange ourRange = ourRanges[ourRangeIdx];
   2761                 for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
   2762                     PageRange otherRange = otherRanges[otherRangeIdx];
   2763                     if (otherRange.getStart() > ourRange.getEnd()) {
   2764                         break;
   2765                     }
   2766                     if (otherRange.getStart() < ourRange.getStart()
   2767                             || otherRange.getEnd() > ourRange.getEnd()) {
   2768                         return false;
   2769                     }
   2770                 }
   2771             }
   2772             if (otherRangeIdx < otherRangeCount) {
   2773                 return false;
   2774             }
   2775             return true;
   2776         }
   2777 
   2778         public static PageRange[] normalize(PageRange[] pageRanges) {
   2779             if (pageRanges == null) {
   2780                 return null;
   2781             }
   2782             final int oldRangeCount = pageRanges.length;
   2783             if (oldRangeCount <= 1) {
   2784                 return pageRanges;
   2785             }
   2786             Arrays.sort(pageRanges, sComparator);
   2787             int newRangeCount = 1;
   2788             for (int i = 0; i < oldRangeCount - 1; i++) {
   2789                 newRangeCount++;
   2790                 PageRange currentRange = pageRanges[i];
   2791                 PageRange nextRange = pageRanges[i + 1];
   2792                 if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
   2793                     newRangeCount--;
   2794                     pageRanges[i] = null;
   2795                     pageRanges[i + 1] = new PageRange(currentRange.getStart(),
   2796                             Math.max(currentRange.getEnd(), nextRange.getEnd()));
   2797                 }
   2798             }
   2799             if (newRangeCount == oldRangeCount) {
   2800                 return pageRanges;
   2801             }
   2802             return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
   2803                     oldRangeCount);
   2804         }
   2805 
   2806         public static void offset(PageRange[] pageRanges, int offset) {
   2807             if (offset == 0) {
   2808                 return;
   2809             }
   2810             final int pageRangeCount = pageRanges.length;
   2811             for (int i = 0; i < pageRangeCount; i++) {
   2812                 final int start = pageRanges[i].getStart() + offset;
   2813                 final int end = pageRanges[i].getEnd() + offset;
   2814                 pageRanges[i] = new PageRange(start, end);
   2815             }
   2816         }
   2817     }
   2818 
   2819     private static final class AutoCancellingAnimator
   2820             implements OnAttachStateChangeListener, Runnable {
   2821 
   2822         private ViewPropertyAnimator mAnimator;
   2823 
   2824         private boolean mCancelled;
   2825         private Runnable mEndCallback;
   2826 
   2827         public static AutoCancellingAnimator animate(View view) {
   2828             ViewPropertyAnimator animator = view.animate();
   2829             AutoCancellingAnimator cancellingWrapper =
   2830                     new AutoCancellingAnimator(animator);
   2831             view.addOnAttachStateChangeListener(cancellingWrapper);
   2832             return cancellingWrapper;
   2833         }
   2834 
   2835         private AutoCancellingAnimator(ViewPropertyAnimator animator) {
   2836             mAnimator = animator;
   2837         }
   2838 
   2839         public AutoCancellingAnimator alpha(float alpha) {
   2840             mAnimator = mAnimator.alpha(alpha);
   2841             return this;
   2842         }
   2843 
   2844         public void cancel() {
   2845             mAnimator.cancel();
   2846         }
   2847 
   2848         public AutoCancellingAnimator withLayer() {
   2849             mAnimator = mAnimator.withLayer();
   2850             return this;
   2851         }
   2852 
   2853         public AutoCancellingAnimator withEndAction(Runnable callback) {
   2854             mEndCallback = callback;
   2855             mAnimator = mAnimator.withEndAction(this);
   2856             return this;
   2857         }
   2858 
   2859         public AutoCancellingAnimator scaleY(float scale) {
   2860             mAnimator = mAnimator.scaleY(scale);
   2861             return this;
   2862         }
   2863 
   2864         @Override
   2865         public void onViewAttachedToWindow(View v) {
   2866             /* do nothing */
   2867         }
   2868 
   2869         @Override
   2870         public void onViewDetachedFromWindow(View v) {
   2871             cancel();
   2872         }
   2873 
   2874         @Override
   2875         public void run() {
   2876             if (!mCancelled) {
   2877                 mEndCallback.run();
   2878             }
   2879         }
   2880     }
   2881 
   2882     private static final class PrintSpoolerProvider implements ServiceConnection {
   2883         private final Context mContext;
   2884         private final Runnable mCallback;
   2885 
   2886         private PrintSpoolerService mSpooler;
   2887 
   2888         public PrintSpoolerProvider(Context context, Runnable callback) {
   2889             mContext = context;
   2890             mCallback = callback;
   2891             Intent intent = new Intent(mContext, PrintSpoolerService.class);
   2892             mContext.bindService(intent, this, 0);
   2893         }
   2894 
   2895         public PrintSpoolerService getSpooler() {
   2896             return mSpooler;
   2897         }
   2898 
   2899         public void destroy() {
   2900             if (mSpooler != null) {
   2901                 mContext.unbindService(this);
   2902             }
   2903         }
   2904 
   2905         @Override
   2906         public void onServiceConnected(ComponentName name, IBinder service) {
   2907             mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
   2908             if (mSpooler != null) {
   2909                 mCallback.run();
   2910             }
   2911         }
   2912 
   2913         @Override
   2914         public void onServiceDisconnected(ComponentName name) {
   2915             /* do noting - we are in the same process */
   2916         }
   2917     }
   2918 
   2919     private static final class PrintDocumentAdapterObserver
   2920             extends IPrintDocumentAdapterObserver.Stub {
   2921         private final WeakReference<PrintJobConfigActivity> mWeakActvity;
   2922 
   2923         public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) {
   2924             mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity);
   2925         }
   2926 
   2927         @Override
   2928         public void onDestroy() {
   2929             final PrintJobConfigActivity activity = mWeakActvity.get();
   2930             if (activity != null) {
   2931                 activity.mController.mHandler.post(new Runnable() {
   2932                     @Override
   2933                     public void run() {
   2934                         if (activity.mController != null) {
   2935                             activity.mController.cancel();
   2936                         }
   2937                         if (activity.mEditor != null) {
   2938                             activity.mEditor.cancel();
   2939                         }
   2940                         activity.finish();
   2941                     }
   2942                 });
   2943             }
   2944         }
   2945     }
   2946 }
   2947