Home | History | Annotate | Download | only in print
      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 android.print;
     18 
     19 import android.content.Context;
     20 import android.content.IntentSender;
     21 import android.content.IntentSender.SendIntentException;
     22 import android.os.Bundle;
     23 import android.os.CancellationSignal;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.ParcelFileDescriptor;
     28 import android.os.RemoteException;
     29 import android.print.PrintDocumentAdapter.LayoutResultCallback;
     30 import android.print.PrintDocumentAdapter.WriteResultCallback;
     31 import android.printservice.PrintServiceInfo;
     32 import android.text.TextUtils;
     33 import android.util.ArrayMap;
     34 import android.util.Log;
     35 
     36 import com.android.internal.os.SomeArgs;
     37 
     38 import libcore.io.IoUtils;
     39 
     40 import java.lang.ref.WeakReference;
     41 import java.util.ArrayList;
     42 import java.util.Collections;
     43 import java.util.List;
     44 import java.util.Map;
     45 
     46 /**
     47  * System level service for accessing the printing capabilities of the platform.
     48  * <p>
     49  * To obtain a handle to the print manager do the following:
     50  * </p>
     51  *
     52  * <pre>
     53  * PrintManager printManager =
     54  *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
     55  * </pre>
     56  */
     57 public final class PrintManager {
     58 
     59     private static final String LOG_TAG = "PrintManager";
     60 
     61     private static final boolean DEBUG = false;
     62 
     63     private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
     64 
     65     /**
     66      * The action for launching the print dialog activity.
     67      *
     68      * @hide
     69      */
     70     public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
     71 
     72     /**
     73      * Extra with the intent for starting the print dialog.
     74      * <p>
     75      * <strong>Type:</strong> {@link android.content.IntentSender}
     76      * </p>
     77      *
     78      * @hide
     79      */
     80     public static final String EXTRA_PRINT_DIALOG_INTENT =
     81             "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
     82 
     83     /**
     84      * Extra with a print job.
     85      * <p>
     86      * <strong>Type:</strong> {@link android.print.PrintJobInfo}
     87      * </p>
     88      *
     89      * @hide
     90      */
     91     public static final String EXTRA_PRINT_JOB =
     92             "android.print.intent.extra.EXTRA_PRINT_JOB";
     93 
     94     /**
     95      * Extra with the print document adapter to be printed.
     96      * <p>
     97      * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
     98      * </p>
     99      *
    100      * @hide
    101      */
    102     public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
    103             "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
    104 
    105     /** @hide */
    106     public static final int APP_ID_ANY = -2;
    107 
    108     private final Context mContext;
    109 
    110     private final IPrintManager mService;
    111 
    112     private final int mUserId;
    113 
    114     private final int mAppId;
    115 
    116     private final Handler mHandler;
    117 
    118     private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
    119 
    120     /** @hide */
    121     public interface PrintJobStateChangeListener {
    122 
    123         /**
    124          * Callback notifying that a print job state changed.
    125          *
    126          * @param printJobId The print job id.
    127          */
    128         public void onPrintJobStateChanged(PrintJobId printJobId);
    129     }
    130 
    131     /**
    132      * Creates a new instance.
    133      *
    134      * @param context The current context in which to operate.
    135      * @param service The backing system service.
    136      * @hide
    137      */
    138     public PrintManager(Context context, IPrintManager service, int userId, int appId) {
    139         mContext = context;
    140         mService = service;
    141         mUserId = userId;
    142         mAppId = appId;
    143         mHandler = new Handler(context.getMainLooper(), null, false) {
    144             @Override
    145             public void handleMessage(Message message) {
    146                 switch (message.what) {
    147                     case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
    148                         SomeArgs args = (SomeArgs) message.obj;
    149                         PrintJobStateChangeListenerWrapper wrapper =
    150                                 (PrintJobStateChangeListenerWrapper) args.arg1;
    151                         PrintJobStateChangeListener listener = wrapper.getListener();
    152                         if (listener != null) {
    153                             PrintJobId printJobId = (PrintJobId) args.arg2;
    154                             listener.onPrintJobStateChanged(printJobId);
    155                         }
    156                         args.recycle();
    157                     } break;
    158                 }
    159             }
    160         };
    161     }
    162 
    163     /**
    164      * Creates an instance that can access all print jobs.
    165      *
    166      * @param userId The user id for which to get all print jobs.
    167      * @return An instance if the caller has the permission to access all print
    168      *         jobs, null otherwise.
    169      * @hide
    170      */
    171     public PrintManager getGlobalPrintManagerForUser(int userId) {
    172         return new PrintManager(mContext, mService, userId, APP_ID_ANY);
    173     }
    174 
    175     PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
    176         try {
    177             return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
    178         } catch (RemoteException re) {
    179             Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
    180         }
    181         return null;
    182     }
    183 
    184     /**
    185      * Adds a listener for observing the state of print jobs.
    186      *
    187      * @param listener The listener to add.
    188      * @hide
    189      */
    190     public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
    191         if (mPrintJobStateChangeListeners == null) {
    192             mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
    193                     PrintJobStateChangeListenerWrapper>();
    194         }
    195         PrintJobStateChangeListenerWrapper wrappedListener =
    196                 new PrintJobStateChangeListenerWrapper(listener, mHandler);
    197         try {
    198             mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
    199             mPrintJobStateChangeListeners.put(listener, wrappedListener);
    200         } catch (RemoteException re) {
    201             Log.e(LOG_TAG, "Error adding print job state change listener", re);
    202         }
    203     }
    204 
    205     /**
    206      * Removes a listener for observing the state of print jobs.
    207      *
    208      * @param listener The listener to remove.
    209      * @hide
    210      */
    211     public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
    212         if (mPrintJobStateChangeListeners == null) {
    213             return;
    214         }
    215         PrintJobStateChangeListenerWrapper wrappedListener =
    216                 mPrintJobStateChangeListeners.remove(listener);
    217         if (wrappedListener == null) {
    218             return;
    219         }
    220         if (mPrintJobStateChangeListeners.isEmpty()) {
    221             mPrintJobStateChangeListeners = null;
    222         }
    223         wrappedListener.destroy();
    224         try {
    225             mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
    226         } catch (RemoteException re) {
    227             Log.e(LOG_TAG, "Error removing print job state change listener", re);
    228         }
    229     }
    230 
    231     /**
    232      * Gets a print job given its id.
    233      *
    234      * @return The print job list.
    235      * @see PrintJob
    236      * @hide
    237      */
    238     public PrintJob getPrintJob(PrintJobId printJobId) {
    239         try {
    240             PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
    241             if (printJob != null) {
    242                 return new PrintJob(printJob, this);
    243             }
    244         } catch (RemoteException re) {
    245             Log.e(LOG_TAG, "Error getting print job", re);
    246         }
    247         return null;
    248     }
    249 
    250     /**
    251      * Gets the print jobs for this application.
    252      *
    253      * @return The print job list.
    254      * @see PrintJob
    255      */
    256     public List<PrintJob> getPrintJobs() {
    257         try {
    258             List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
    259             if (printJobInfos == null) {
    260                 return Collections.emptyList();
    261             }
    262             final int printJobCount = printJobInfos.size();
    263             List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
    264             for (int i = 0; i < printJobCount; i++) {
    265                 printJobs.add(new PrintJob(printJobInfos.get(i), this));
    266             }
    267             return printJobs;
    268         } catch (RemoteException re) {
    269             Log.e(LOG_TAG, "Error getting print jobs", re);
    270         }
    271         return Collections.emptyList();
    272     }
    273 
    274     void cancelPrintJob(PrintJobId printJobId) {
    275         try {
    276             mService.cancelPrintJob(printJobId, mAppId, mUserId);
    277         } catch (RemoteException re) {
    278             Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
    279         }
    280     }
    281 
    282     void restartPrintJob(PrintJobId printJobId) {
    283         try {
    284             mService.restartPrintJob(printJobId, mAppId, mUserId);
    285         } catch (RemoteException re) {
    286             Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re);
    287         }
    288     }
    289 
    290     /**
    291      * Creates a print job for printing a {@link PrintDocumentAdapter} with
    292      * default print attributes.
    293      *
    294      * @param printJobName A name for the new print job.
    295      * @param documentAdapter An adapter that emits the document to print.
    296      * @param attributes The default print job attributes.
    297      * @return The created print job on success or null on failure.
    298      * @see PrintJob
    299      */
    300     public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
    301             PrintAttributes attributes) {
    302         if (TextUtils.isEmpty(printJobName)) {
    303             throw new IllegalArgumentException("priintJobName cannot be empty");
    304         }
    305         PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
    306                 mContext.getMainLooper());
    307         try {
    308             Bundle result = mService.print(printJobName, delegate,
    309                     attributes, mContext.getPackageName(), mAppId, mUserId);
    310             if (result != null) {
    311                 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
    312                 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
    313                 if (printJob == null || intent == null) {
    314                     return null;
    315                 }
    316                 try {
    317                     mContext.startIntentSender(intent, null, 0, 0, 0);
    318                     return new PrintJob(printJob, this);
    319                 } catch (SendIntentException sie) {
    320                     Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
    321                 }
    322             }
    323         } catch (RemoteException re) {
    324             Log.e(LOG_TAG, "Error creating a print job", re);
    325         }
    326         return null;
    327     }
    328 
    329     /**
    330      * Gets the list of enabled print services.
    331      *
    332      * @return The enabled service list or an empty list.
    333      * @hide
    334      */
    335     public List<PrintServiceInfo> getEnabledPrintServices() {
    336         try {
    337             List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
    338             if (enabledServices != null) {
    339                 return enabledServices;
    340             }
    341         } catch (RemoteException re) {
    342             Log.e(LOG_TAG, "Error getting the enabled print services", re);
    343         }
    344         return Collections.emptyList();
    345     }
    346 
    347     /**
    348      * Gets the list of installed print services.
    349      *
    350      * @return The installed service list or an empty list.
    351      * @hide
    352      */
    353     public List<PrintServiceInfo> getInstalledPrintServices() {
    354         try {
    355             List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
    356             if (installedServices != null) {
    357                 return installedServices;
    358             }
    359         } catch (RemoteException re) {
    360             Log.e(LOG_TAG, "Error getting the installed print services", re);
    361         }
    362         return Collections.emptyList();
    363     }
    364 
    365     /**
    366      * @hide
    367      */
    368     public PrinterDiscoverySession createPrinterDiscoverySession() {
    369         return new PrinterDiscoverySession(mService, mContext, mUserId);
    370     }
    371 
    372     private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
    373 
    374         private final Object mLock = new Object();
    375 
    376         private CancellationSignal mLayoutOrWriteCancellation;
    377 
    378         private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK -
    379                                                        // cleared in finish()
    380 
    381         private Handler mHandler; // Strong reference OK - cleared in finish()
    382 
    383         private LayoutSpec mLastLayoutSpec;
    384 
    385         private WriteSpec mLastWriteSpec;
    386 
    387         private boolean mStartReqeusted;
    388         private boolean mStarted;
    389 
    390         private boolean mFinishRequested;
    391         private boolean mFinished;
    392 
    393         public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
    394             mDocumentAdapter = documentAdapter;
    395             mHandler = new MyHandler(looper);
    396         }
    397 
    398         @Override
    399         public void start() {
    400             synchronized (mLock) {
    401                 // Started or finished - nothing to do.
    402                 if (mStartReqeusted || mFinishRequested) {
    403                     return;
    404                 }
    405 
    406                 mStartReqeusted = true;
    407 
    408                 doPendingWorkLocked();
    409             }
    410         }
    411 
    412         @Override
    413         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
    414                 ILayoutResultCallback callback, Bundle metadata, int sequence) {
    415             synchronized (mLock) {
    416                 // Start not called or finish called - nothing to do.
    417                 if (!mStartReqeusted || mFinishRequested) {
    418                     return;
    419                 }
    420 
    421                 // Layout cancels write and overrides layout.
    422                 if (mLastWriteSpec != null) {
    423                     IoUtils.closeQuietly(mLastWriteSpec.fd);
    424                     mLastWriteSpec = null;
    425                 }
    426 
    427                 mLastLayoutSpec = new LayoutSpec();
    428                 mLastLayoutSpec.callback = callback;
    429                 mLastLayoutSpec.oldAttributes = oldAttributes;
    430                 mLastLayoutSpec.newAttributes = newAttributes;
    431                 mLastLayoutSpec.metadata = metadata;
    432                 mLastLayoutSpec.sequence = sequence;
    433 
    434                 // Cancel the previous cancellable operation.When the
    435                 // cancellation completes we will do the pending work.
    436                 if (cancelPreviousCancellableOperationLocked()) {
    437                     return;
    438                 }
    439 
    440                 doPendingWorkLocked();
    441             }
    442         }
    443 
    444         @Override
    445         public void write(PageRange[] pages, ParcelFileDescriptor fd,
    446                 IWriteResultCallback callback, int sequence) {
    447             synchronized (mLock) {
    448                 // Start not called or finish called - nothing to do.
    449                 if (!mStartReqeusted || mFinishRequested) {
    450                     return;
    451                 }
    452 
    453                 // Write cancels previous writes.
    454                 if (mLastWriteSpec != null) {
    455                     IoUtils.closeQuietly(mLastWriteSpec.fd);
    456                     mLastWriteSpec = null;
    457                 }
    458 
    459                 mLastWriteSpec = new WriteSpec();
    460                 mLastWriteSpec.callback = callback;
    461                 mLastWriteSpec.pages = pages;
    462                 mLastWriteSpec.fd = fd;
    463                 mLastWriteSpec.sequence = sequence;
    464 
    465                 // Cancel the previous cancellable operation.When the
    466                 // cancellation completes we will do the pending work.
    467                 if (cancelPreviousCancellableOperationLocked()) {
    468                     return;
    469                 }
    470 
    471                 doPendingWorkLocked();
    472             }
    473         }
    474 
    475         @Override
    476         public void finish() {
    477             synchronized (mLock) {
    478                 // Start not called or finish called - nothing to do.
    479                 if (!mStartReqeusted || mFinishRequested) {
    480                     return;
    481                 }
    482 
    483                 mFinishRequested = true;
    484 
    485                 // When the current write or layout complete we
    486                 // will do the pending work.
    487                 if (mLastLayoutSpec != null || mLastWriteSpec != null) {
    488                     if (DEBUG) {
    489                         Log.i(LOG_TAG, "Waiting for current operation");
    490                     }
    491                     return;
    492                 }
    493 
    494                 doPendingWorkLocked();
    495             }
    496         }
    497 
    498         private boolean isFinished() {
    499             return mDocumentAdapter == null;
    500         }
    501 
    502         private void doFinish() {
    503             mDocumentAdapter = null;
    504             mHandler = null;
    505             synchronized (mLock) {
    506                 mLayoutOrWriteCancellation = null;
    507             }
    508         }
    509 
    510         private boolean cancelPreviousCancellableOperationLocked() {
    511             if (mLayoutOrWriteCancellation != null) {
    512                 mLayoutOrWriteCancellation.cancel();
    513                 if (DEBUG) {
    514                     Log.i(LOG_TAG, "Cancelling previous operation");
    515                 }
    516                 return true;
    517             }
    518             return false;
    519         }
    520 
    521         private void doPendingWorkLocked() {
    522             if (mStartReqeusted && !mStarted) {
    523                 mStarted = true;
    524                 mHandler.sendEmptyMessage(MyHandler.MSG_START);
    525             } else if (mLastLayoutSpec != null) {
    526                 mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
    527             } else if (mLastWriteSpec != null) {
    528                 mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
    529             } else if (mFinishRequested && !mFinished) {
    530                 mFinished = true;
    531                 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
    532             }
    533         }
    534 
    535         private class LayoutSpec {
    536             ILayoutResultCallback callback;
    537             PrintAttributes oldAttributes;
    538             PrintAttributes newAttributes;
    539             Bundle metadata;
    540             int sequence;
    541         }
    542 
    543         private class WriteSpec {
    544             IWriteResultCallback callback;
    545             PageRange[] pages;
    546             ParcelFileDescriptor fd;
    547             int sequence;
    548         }
    549 
    550         private final class MyHandler extends Handler {
    551             public static final int MSG_START = 1;
    552             public static final int MSG_LAYOUT = 2;
    553             public static final int MSG_WRITE = 3;
    554             public static final int MSG_FINISH = 4;
    555 
    556             public MyHandler(Looper looper) {
    557                 super(looper, null, true);
    558             }
    559 
    560             @Override
    561             public void handleMessage(Message message) {
    562                 if (isFinished()) {
    563                     return;
    564                 }
    565                 switch (message.what) {
    566                     case MSG_START: {
    567                         mDocumentAdapter.onStart();
    568                     }
    569                         break;
    570 
    571                     case MSG_LAYOUT: {
    572                         final CancellationSignal cancellation;
    573                         final LayoutSpec layoutSpec;
    574 
    575                         synchronized (mLock) {
    576                             layoutSpec = mLastLayoutSpec;
    577                             mLastLayoutSpec = null;
    578                             cancellation = new CancellationSignal();
    579                             mLayoutOrWriteCancellation = cancellation;
    580                         }
    581 
    582                         if (layoutSpec != null) {
    583                             if (DEBUG) {
    584                                 Log.i(LOG_TAG, "Performing layout");
    585                             }
    586                             mDocumentAdapter.onLayout(layoutSpec.oldAttributes,
    587                                     layoutSpec.newAttributes, cancellation,
    588                                     new MyLayoutResultCallback(layoutSpec.callback,
    589                                             layoutSpec.sequence), layoutSpec.metadata);
    590                         }
    591                     }
    592                         break;
    593 
    594                     case MSG_WRITE: {
    595                         final CancellationSignal cancellation;
    596                         final WriteSpec writeSpec;
    597 
    598                         synchronized (mLock) {
    599                             writeSpec = mLastWriteSpec;
    600                             mLastWriteSpec = null;
    601                             cancellation = new CancellationSignal();
    602                             mLayoutOrWriteCancellation = cancellation;
    603                         }
    604 
    605                         if (writeSpec != null) {
    606                             if (DEBUG) {
    607                                 Log.i(LOG_TAG, "Performing write");
    608                             }
    609                             mDocumentAdapter.onWrite(writeSpec.pages, writeSpec.fd,
    610                                     cancellation, new MyWriteResultCallback(writeSpec.callback,
    611                                             writeSpec.fd, writeSpec.sequence));
    612                         }
    613                     }
    614                         break;
    615 
    616                     case MSG_FINISH: {
    617                         if (DEBUG) {
    618                             Log.i(LOG_TAG, "Performing finish");
    619                         }
    620                         mDocumentAdapter.onFinish();
    621                         doFinish();
    622                     }
    623                         break;
    624 
    625                     default: {
    626                         throw new IllegalArgumentException("Unknown message: "
    627                                 + message.what);
    628                     }
    629                 }
    630             }
    631         }
    632 
    633         private final class MyLayoutResultCallback extends LayoutResultCallback {
    634             private ILayoutResultCallback mCallback;
    635             private final int mSequence;
    636 
    637             public MyLayoutResultCallback(ILayoutResultCallback callback,
    638                     int sequence) {
    639                 mCallback = callback;
    640                 mSequence = sequence;
    641             }
    642 
    643             @Override
    644             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
    645                 if (info == null) {
    646                     throw new NullPointerException("document info cannot be null");
    647                 }
    648                 final ILayoutResultCallback callback;
    649                 synchronized (mLock) {
    650                     callback = mCallback;
    651                     clearLocked();
    652                 }
    653                 if (callback != null) {
    654                     try {
    655                         callback.onLayoutFinished(info, changed, mSequence);
    656                     } catch (RemoteException re) {
    657                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
    658                     }
    659                 }
    660             }
    661 
    662             @Override
    663             public void onLayoutFailed(CharSequence error) {
    664                 final ILayoutResultCallback callback;
    665                 synchronized (mLock) {
    666                     callback = mCallback;
    667                     clearLocked();
    668                 }
    669                 if (callback != null) {
    670                     try {
    671                         callback.onLayoutFailed(error, mSequence);
    672                     } catch (RemoteException re) {
    673                         Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
    674                     }
    675                 }
    676             }
    677 
    678             @Override
    679             public void onLayoutCancelled() {
    680                 synchronized (mLock) {
    681                     clearLocked();
    682                 }
    683             }
    684 
    685             private void clearLocked() {
    686                 mLayoutOrWriteCancellation = null;
    687                 mCallback = null;
    688                 doPendingWorkLocked();
    689             }
    690         }
    691 
    692         private final class MyWriteResultCallback extends WriteResultCallback {
    693             private ParcelFileDescriptor mFd;
    694             private int mSequence;
    695             private IWriteResultCallback mCallback;
    696 
    697             public MyWriteResultCallback(IWriteResultCallback callback,
    698                     ParcelFileDescriptor fd, int sequence) {
    699                 mFd = fd;
    700                 mSequence = sequence;
    701                 mCallback = callback;
    702             }
    703 
    704             @Override
    705             public void onWriteFinished(PageRange[] pages) {
    706                 final IWriteResultCallback callback;
    707                 synchronized (mLock) {
    708                     callback = mCallback;
    709                     clearLocked();
    710                 }
    711                 if (pages == null) {
    712                     throw new IllegalArgumentException("pages cannot be null");
    713                 }
    714                 if (pages.length == 0) {
    715                     throw new IllegalArgumentException("pages cannot be empty");
    716                 }
    717                 if (callback != null) {
    718                     try {
    719                         callback.onWriteFinished(pages, mSequence);
    720                     } catch (RemoteException re) {
    721                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
    722                     }
    723                 }
    724             }
    725 
    726             @Override
    727             public void onWriteFailed(CharSequence error) {
    728                 final IWriteResultCallback callback;
    729                 synchronized (mLock) {
    730                     callback = mCallback;
    731                     clearLocked();
    732                 }
    733                 if (callback != null) {
    734                     try {
    735                         callback.onWriteFailed(error, mSequence);
    736                     } catch (RemoteException re) {
    737                         Log.e(LOG_TAG, "Error calling onWriteFailed", re);
    738                     }
    739                 }
    740             }
    741 
    742             @Override
    743             public void onWriteCancelled() {
    744                 synchronized (mLock) {
    745                     clearLocked();
    746                 }
    747             }
    748 
    749             private void clearLocked() {
    750                 mLayoutOrWriteCancellation = null;
    751                 IoUtils.closeQuietly(mFd);
    752                 mCallback = null;
    753                 mFd = null;
    754                 doPendingWorkLocked();
    755             }
    756         }
    757     }
    758 
    759     private static final class PrintJobStateChangeListenerWrapper extends
    760             IPrintJobStateChangeListener.Stub {
    761         private final WeakReference<PrintJobStateChangeListener> mWeakListener;
    762         private final WeakReference<Handler> mWeakHandler;
    763 
    764         public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
    765                 Handler handler) {
    766             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
    767             mWeakHandler = new WeakReference<Handler>(handler);
    768         }
    769 
    770         @Override
    771         public void onPrintJobStateChanged(PrintJobId printJobId) {
    772             Handler handler = mWeakHandler.get();
    773             PrintJobStateChangeListener listener = mWeakListener.get();
    774             if (handler != null && listener != null) {
    775                 SomeArgs args = SomeArgs.obtain();
    776                 args.arg1 = this;
    777                 args.arg2 = printJobId;
    778                 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
    779                         args).sendToTarget();
    780             }
    781         }
    782 
    783         public void destroy() {
    784             mWeakListener.clear();
    785         }
    786 
    787         public PrintJobStateChangeListener getListener() {
    788             return mWeakListener.get();
    789         }
    790     }
    791 }
    792