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.app.Activity;
     20 import android.app.Application.ActivityLifecycleCallbacks;
     21 import android.content.Context;
     22 import android.content.IntentSender;
     23 import android.content.IntentSender.SendIntentException;
     24 import android.os.Bundle;
     25 import android.os.CancellationSignal;
     26 import android.os.Handler;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.os.ParcelFileDescriptor;
     30 import android.os.RemoteException;
     31 import android.print.PrintDocumentAdapter.LayoutResultCallback;
     32 import android.print.PrintDocumentAdapter.WriteResultCallback;
     33 import android.printservice.PrintServiceInfo;
     34 import android.text.TextUtils;
     35 import android.util.ArrayMap;
     36 import android.util.Log;
     37 
     38 import com.android.internal.os.SomeArgs;
     39 
     40 import libcore.io.IoUtils;
     41 
     42 import java.lang.ref.WeakReference;
     43 import java.util.ArrayList;
     44 import java.util.Collections;
     45 import java.util.List;
     46 import java.util.Map;
     47 
     48 /**
     49  * System level service for accessing the printing capabilities of the platform.
     50  * <p>
     51  * To obtain a handle to the print manager do the following:
     52  * </p>
     53  *
     54  * <pre>
     55  * PrintManager printManager =
     56  *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
     57  * </pre>
     58  *
     59  * <h3>Print mechanics</h3>
     60  * <p>
     61  * The key idea behind printing on the platform is that the content to be printed
     62  * should be laid out for the currently selected print options resulting in an
     63  * optimized output and higher user satisfaction. To achieve this goal the platform
     64  * declares a contract that the printing application has to follow which is defined
     65  * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that
     66  * when the user selects some options from the print UI that may affect the way
     67  * content is laid out, for example page size, the application receives a callback
     68  * allowing it to layout the content to better fit these new constraints. After a
     69  * layout pass the system may ask the application to render one or more pages one
     70  * or more times. For example, an application may produce a single column list for
     71  * smaller page sizes and a multi-column table for larger page sizes.
     72  * </p>
     73  * <h3>Print jobs</h3>
     74  * <p>
     75  * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter,
     76  * PrintAttributes)} from an activity which results in bringing up the system print
     77  * UI. Once the print UI is up, when the user changes a selected print option that
     78  * affects the way content is laid out the system starts to interact with the
     79  * application following the mechanics described the section above.
     80  * </p>
     81  * <p>
     82  * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link
     83  * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started},
     84  * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED
     85  * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link
     86  * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated
     87  * system spooler until they are handled which is they are cancelled or completed.
     88  * Active print jobs, ones that are not cancelled or completed, are considered failed
     89  * if the device reboots as the new boot may be after a very long time. The user may
     90  * choose to restart such print jobs. Once a print job is queued all relevant content
     91  * is stored in the system spooler and its lifecycle becomes detached from this of
     92  * the application that created it.
     93  * </p>
     94  * <p>
     95  * An applications can query the print spooler for current print jobs it created
     96  * but not print jobs created by other applications.
     97  * </p>
     98  *
     99  * @see PrintJob
    100  * @see PrintJobInfo
    101  */
    102 public final class PrintManager {
    103 
    104     private static final String LOG_TAG = "PrintManager";
    105 
    106     private static final boolean DEBUG = false;
    107 
    108     private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
    109 
    110     /**
    111      * The action for launching the print dialog activity.
    112      *
    113      * @hide
    114      */
    115     public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
    116 
    117     /**
    118      * Extra with the intent for starting the print dialog.
    119      * <p>
    120      * <strong>Type:</strong> {@link android.content.IntentSender}
    121      * </p>
    122      *
    123      * @hide
    124      */
    125     public static final String EXTRA_PRINT_DIALOG_INTENT =
    126             "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
    127 
    128     /**
    129      * Extra with a print job.
    130      * <p>
    131      * <strong>Type:</strong> {@link android.print.PrintJobInfo}
    132      * </p>
    133      *
    134      * @hide
    135      */
    136     public static final String EXTRA_PRINT_JOB =
    137             "android.print.intent.extra.EXTRA_PRINT_JOB";
    138 
    139     /**
    140      * Extra with the print document adapter to be printed.
    141      * <p>
    142      * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
    143      * </p>
    144      *
    145      * @hide
    146      */
    147     public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
    148             "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
    149 
    150     /** @hide */
    151     public static final int APP_ID_ANY = -2;
    152 
    153     private final Context mContext;
    154 
    155     private final IPrintManager mService;
    156 
    157     private final int mUserId;
    158 
    159     private final int mAppId;
    160 
    161     private final Handler mHandler;
    162 
    163     private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
    164 
    165     /** @hide */
    166     public interface PrintJobStateChangeListener {
    167 
    168         /**
    169          * Callback notifying that a print job state changed.
    170          *
    171          * @param printJobId The print job id.
    172          */
    173         public void onPrintJobStateChanged(PrintJobId printJobId);
    174     }
    175 
    176     /**
    177      * Creates a new instance.
    178      *
    179      * @param context The current context in which to operate.
    180      * @param service The backing system service.
    181      * @hide
    182      */
    183     public PrintManager(Context context, IPrintManager service, int userId, int appId) {
    184         mContext = context;
    185         mService = service;
    186         mUserId = userId;
    187         mAppId = appId;
    188         mHandler = new Handler(context.getMainLooper(), null, false) {
    189             @Override
    190             public void handleMessage(Message message) {
    191                 switch (message.what) {
    192                     case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
    193                         SomeArgs args = (SomeArgs) message.obj;
    194                         PrintJobStateChangeListenerWrapper wrapper =
    195                                 (PrintJobStateChangeListenerWrapper) args.arg1;
    196                         PrintJobStateChangeListener listener = wrapper.getListener();
    197                         if (listener != null) {
    198                             PrintJobId printJobId = (PrintJobId) args.arg2;
    199                             listener.onPrintJobStateChanged(printJobId);
    200                         }
    201                         args.recycle();
    202                     } break;
    203                 }
    204             }
    205         };
    206     }
    207 
    208     /**
    209      * Creates an instance that can access all print jobs.
    210      *
    211      * @param userId The user id for which to get all print jobs.
    212      * @return An instance if the caller has the permission to access all print
    213      *         jobs, null otherwise.
    214      * @hide
    215      */
    216     public PrintManager getGlobalPrintManagerForUser(int userId) {
    217         return new PrintManager(mContext, mService, userId, APP_ID_ANY);
    218     }
    219 
    220     PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
    221         try {
    222             return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
    223         } catch (RemoteException re) {
    224             Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
    225         }
    226         return null;
    227     }
    228 
    229     /**
    230      * Adds a listener for observing the state of print jobs.
    231      *
    232      * @param listener The listener to add.
    233      * @hide
    234      */
    235     public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
    236         if (mPrintJobStateChangeListeners == null) {
    237             mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
    238                     PrintJobStateChangeListenerWrapper>();
    239         }
    240         PrintJobStateChangeListenerWrapper wrappedListener =
    241                 new PrintJobStateChangeListenerWrapper(listener, mHandler);
    242         try {
    243             mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
    244             mPrintJobStateChangeListeners.put(listener, wrappedListener);
    245         } catch (RemoteException re) {
    246             Log.e(LOG_TAG, "Error adding print job state change listener", re);
    247         }
    248     }
    249 
    250     /**
    251      * Removes a listener for observing the state of print jobs.
    252      *
    253      * @param listener The listener to remove.
    254      * @hide
    255      */
    256     public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
    257         if (mPrintJobStateChangeListeners == null) {
    258             return;
    259         }
    260         PrintJobStateChangeListenerWrapper wrappedListener =
    261                 mPrintJobStateChangeListeners.remove(listener);
    262         if (wrappedListener == null) {
    263             return;
    264         }
    265         if (mPrintJobStateChangeListeners.isEmpty()) {
    266             mPrintJobStateChangeListeners = null;
    267         }
    268         wrappedListener.destroy();
    269         try {
    270             mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
    271         } catch (RemoteException re) {
    272             Log.e(LOG_TAG, "Error removing print job state change listener", re);
    273         }
    274     }
    275 
    276     /**
    277      * Gets a print job given its id.
    278      *
    279      * @return The print job list.
    280      * @see PrintJob
    281      * @hide
    282      */
    283     public PrintJob getPrintJob(PrintJobId printJobId) {
    284         try {
    285             PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
    286             if (printJob != null) {
    287                 return new PrintJob(printJob, this);
    288             }
    289         } catch (RemoteException re) {
    290             Log.e(LOG_TAG, "Error getting print job", re);
    291         }
    292         return null;
    293     }
    294 
    295     /**
    296      * Gets the print jobs for this application.
    297      *
    298      * @return The print job list.
    299      * @see PrintJob
    300      */
    301     public List<PrintJob> getPrintJobs() {
    302         try {
    303             List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
    304             if (printJobInfos == null) {
    305                 return Collections.emptyList();
    306             }
    307             final int printJobCount = printJobInfos.size();
    308             List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
    309             for (int i = 0; i < printJobCount; i++) {
    310                 printJobs.add(new PrintJob(printJobInfos.get(i), this));
    311             }
    312             return printJobs;
    313         } catch (RemoteException re) {
    314             Log.e(LOG_TAG, "Error getting print jobs", re);
    315         }
    316         return Collections.emptyList();
    317     }
    318 
    319     void cancelPrintJob(PrintJobId printJobId) {
    320         try {
    321             mService.cancelPrintJob(printJobId, mAppId, mUserId);
    322         } catch (RemoteException re) {
    323             Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
    324         }
    325     }
    326 
    327     void restartPrintJob(PrintJobId printJobId) {
    328         try {
    329             mService.restartPrintJob(printJobId, mAppId, mUserId);
    330         } catch (RemoteException re) {
    331             Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re);
    332         }
    333     }
    334 
    335     /**
    336      * Creates a print job for printing a {@link PrintDocumentAdapter} with
    337      * default print attributes.
    338      * <p>
    339      * Calling this method brings the print UI allowing the user to customize
    340      * the print job and returns a {@link PrintJob} object without waiting for the
    341      * user to customize or confirm the print job. The returned print job instance
    342      * is in a {@link PrintJobInfo#STATE_CREATED created} state.
    343      * <p>
    344      * This method can be called only from an {@link Activity}. The rationale is that
    345      * printing from a service will create an inconsistent user experience as the print
    346      * UI would appear without any context.
    347      * </p>
    348      * <p>
    349      * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
    350      * your activity is finished. The rationale is that once the activity that
    351      * initiated printing is finished, the provided adapter may be in an inconsistent
    352      * state as it may depend on the UI presented by the activity.
    353      * </p>
    354      * <p>
    355      * The default print attributes are a hint to the system how the data is to
    356      * be printed. For example, a photo editor may look at the photo aspect ratio
    357      * to determine the default orientation and provide a hint whether the printing
    358      * should be in portrait or landscape. The system will do a best effort to
    359      * selected the hinted options in the print dialog, given the current printer
    360      * supports them.
    361      * </p>
    362      *
    363      * @param printJobName A name for the new print job which is shown to the user.
    364      * @param documentAdapter An adapter that emits the document to print.
    365      * @param attributes The default print job attributes or <code>null</code>.
    366      * @return The created print job on success or null on failure.
    367      * @throws IllegalStateException If not called from an {@link Activity}.
    368      * @throws IllegalArgumentException If the print job name is empty or the
    369      * document adapter is null.
    370      *
    371      * @see PrintJob
    372      */
    373     public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
    374             PrintAttributes attributes) {
    375         if (!(mContext instanceof Activity)) {
    376             throw new IllegalStateException("Can print only from an activity");
    377         }
    378         if (TextUtils.isEmpty(printJobName)) {
    379             throw new IllegalArgumentException("printJobName cannot be empty");
    380         }
    381         if (documentAdapter == null) {
    382             throw new IllegalArgumentException("documentAdapter cannot be null");
    383         }
    384         PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
    385                 (Activity) mContext, documentAdapter);
    386         try {
    387             Bundle result = mService.print(printJobName, delegate,
    388                     attributes, mContext.getPackageName(), mAppId, mUserId);
    389             if (result != null) {
    390                 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
    391                 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
    392                 if (printJob == null || intent == null) {
    393                     return null;
    394                 }
    395                 try {
    396                     mContext.startIntentSender(intent, null, 0, 0, 0);
    397                     return new PrintJob(printJob, this);
    398                 } catch (SendIntentException sie) {
    399                     Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
    400                 }
    401             }
    402         } catch (RemoteException re) {
    403             Log.e(LOG_TAG, "Error creating a print job", re);
    404         }
    405         return null;
    406     }
    407 
    408     /**
    409      * Gets the list of enabled print services.
    410      *
    411      * @return The enabled service list or an empty list.
    412      * @hide
    413      */
    414     public List<PrintServiceInfo> getEnabledPrintServices() {
    415         try {
    416             List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
    417             if (enabledServices != null) {
    418                 return enabledServices;
    419             }
    420         } catch (RemoteException re) {
    421             Log.e(LOG_TAG, "Error getting the enabled print services", re);
    422         }
    423         return Collections.emptyList();
    424     }
    425 
    426     /**
    427      * Gets the list of installed print services.
    428      *
    429      * @return The installed service list or an empty list.
    430      * @hide
    431      */
    432     public List<PrintServiceInfo> getInstalledPrintServices() {
    433         try {
    434             List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
    435             if (installedServices != null) {
    436                 return installedServices;
    437             }
    438         } catch (RemoteException re) {
    439             Log.e(LOG_TAG, "Error getting the installed print services", re);
    440         }
    441         return Collections.emptyList();
    442     }
    443 
    444     /**
    445      * @hide
    446      */
    447     public PrinterDiscoverySession createPrinterDiscoverySession() {
    448         return new PrinterDiscoverySession(mService, mContext, mUserId);
    449     }
    450 
    451     private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
    452             implements ActivityLifecycleCallbacks {
    453 
    454         private final Object mLock = new Object();
    455 
    456         private CancellationSignal mLayoutOrWriteCancellation;
    457 
    458         private Activity mActivity; // Strong reference OK - cleared in finish()
    459 
    460         private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
    461 
    462         private Handler mHandler; // Strong reference OK - cleared in finish()
    463 
    464         private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
    465 
    466         private LayoutSpec mLastLayoutSpec;
    467 
    468         private WriteSpec mLastWriteSpec;
    469 
    470         private boolean mStartReqeusted;
    471         private boolean mStarted;
    472 
    473         private boolean mFinishRequested;
    474         private boolean mFinished;
    475 
    476         private boolean mDestroyed;
    477 
    478         public PrintDocumentAdapterDelegate(Activity activity,
    479                 PrintDocumentAdapter documentAdapter) {
    480             mActivity = activity;
    481             mDocumentAdapter = documentAdapter;
    482             mHandler = new MyHandler(mActivity.getMainLooper());
    483             mActivity.getApplication().registerActivityLifecycleCallbacks(this);
    484         }
    485 
    486         @Override
    487         public void setObserver(IPrintDocumentAdapterObserver observer) {
    488             final boolean destroyed;
    489             synchronized (mLock) {
    490                 if (!mDestroyed) {
    491                     mObserver = observer;
    492                 }
    493                 destroyed = mDestroyed;
    494             }
    495             if (destroyed) {
    496                 try {
    497                     observer.onDestroy();
    498                 } catch (RemoteException re) {
    499                     Log.e(LOG_TAG, "Error announcing destroyed state", re);
    500                 }
    501             }
    502         }
    503 
    504         @Override
    505         public void start() {
    506             synchronized (mLock) {
    507                 // Started called or finish called or destroyed - nothing to do.
    508                 if (mStartReqeusted || mFinishRequested || mDestroyed) {
    509                     return;
    510                 }
    511 
    512                 mStartReqeusted = true;
    513 
    514                 doPendingWorkLocked();
    515             }
    516         }
    517 
    518         @Override
    519         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
    520                 ILayoutResultCallback callback, Bundle metadata, int sequence) {
    521             final boolean destroyed;
    522             synchronized (mLock) {
    523                 destroyed = mDestroyed;
    524                 // If start called and not finished called and not destroyed - do some work.
    525                 if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
    526                     // Layout cancels write and overrides layout.
    527                     if (mLastWriteSpec != null) {
    528                         IoUtils.closeQuietly(mLastWriteSpec.fd);
    529                         mLastWriteSpec = null;
    530                     }
    531 
    532                     mLastLayoutSpec = new LayoutSpec();
    533                     mLastLayoutSpec.callback = callback;
    534                     mLastLayoutSpec.oldAttributes = oldAttributes;
    535                     mLastLayoutSpec.newAttributes = newAttributes;
    536                     mLastLayoutSpec.metadata = metadata;
    537                     mLastLayoutSpec.sequence = sequence;
    538 
    539                     // Cancel the previous cancellable operation.When the
    540                     // cancellation completes we will do the pending work.
    541                     if (cancelPreviousCancellableOperationLocked()) {
    542                         return;
    543                     }
    544 
    545                     doPendingWorkLocked();
    546                 }
    547             }
    548             if (destroyed) {
    549                 try {
    550                     callback.onLayoutFailed(null, sequence);
    551                 } catch (RemoteException re) {
    552                     Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
    553                 }
    554             }
    555         }
    556 
    557         @Override
    558         public void write(PageRange[] pages, ParcelFileDescriptor fd,
    559                 IWriteResultCallback callback, int sequence) {
    560             final boolean destroyed;
    561             synchronized (mLock) {
    562                 destroyed = mDestroyed;
    563                 // If start called and not finished called and not destroyed - do some work.
    564                 if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
    565                     // Write cancels previous writes.
    566                     if (mLastWriteSpec != null) {
    567                         IoUtils.closeQuietly(mLastWriteSpec.fd);
    568                         mLastWriteSpec = null;
    569                     }
    570 
    571                     mLastWriteSpec = new WriteSpec();
    572                     mLastWriteSpec.callback = callback;
    573                     mLastWriteSpec.pages = pages;
    574                     mLastWriteSpec.fd = fd;
    575                     mLastWriteSpec.sequence = sequence;
    576 
    577                     // Cancel the previous cancellable operation.When the
    578                     // cancellation completes we will do the pending work.
    579                     if (cancelPreviousCancellableOperationLocked()) {
    580                         return;
    581                     }
    582 
    583                     doPendingWorkLocked();
    584                 }
    585             }
    586             if (destroyed) {
    587                 try {
    588                     callback.onWriteFailed(null, sequence);
    589                 } catch (RemoteException re) {
    590                     Log.i(LOG_TAG, "Error notifying for cancelled write", re);
    591                 }
    592             }
    593         }
    594 
    595         @Override
    596         public void finish() {
    597             synchronized (mLock) {
    598                 // Start not called or finish called or destroyed - nothing to do.
    599                 if (!mStartReqeusted || mFinishRequested || mDestroyed) {
    600                     return;
    601                 }
    602 
    603                 mFinishRequested = true;
    604 
    605                 // When the current write or layout complete we
    606                 // will do the pending work.
    607                 if (mLastLayoutSpec != null || mLastWriteSpec != null) {
    608                     if (DEBUG) {
    609                         Log.i(LOG_TAG, "Waiting for current operation");
    610                     }
    611                     return;
    612                 }
    613 
    614                 doPendingWorkLocked();
    615             }
    616         }
    617 
    618         @Override
    619         public void cancel() {
    620             // Start not called or finish called or destroyed - nothing to do.
    621             if (!mStartReqeusted || mFinishRequested || mDestroyed) {
    622                 return;
    623             }
    624             // Request cancellation of pending work if needed.
    625             synchronized (mLock) {
    626                 cancelPreviousCancellableOperationLocked();
    627             }
    628         }
    629 
    630         @Override
    631         public void onActivityPaused(Activity activity) {
    632             /* do nothing */
    633         }
    634 
    635         @Override
    636         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    637             /* do nothing */
    638         }
    639 
    640         @Override
    641         public void onActivityStarted(Activity activity) {
    642             /* do nothing */
    643         }
    644 
    645         @Override
    646         public void onActivityResumed(Activity activity) {
    647             /* do nothing */
    648         }
    649 
    650         @Override
    651         public void onActivityStopped(Activity activity) {
    652             /* do nothing */
    653         }
    654 
    655         @Override
    656         public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    657             /* do nothing */
    658         }
    659 
    660         @Override
    661         public void onActivityDestroyed(Activity activity) {
    662             // We really care only if the activity is being destroyed to
    663             // notify the the print spooler so it can close the print dialog.
    664             // Note the the spooler has a death recipient that observes if
    665             // this process gets killed so we cover the case of onDestroy not
    666             // being called due to this process being killed to reclaim memory.
    667             final IPrintDocumentAdapterObserver observer;
    668             synchronized (mLock) {
    669                 if (activity == mActivity) {
    670                     mDestroyed = true;
    671                     observer = mObserver;
    672                     clearLocked();
    673                 } else {
    674                     observer = null;
    675                     activity = null;
    676                 }
    677             }
    678             if (observer != null) {
    679                 activity.getApplication().unregisterActivityLifecycleCallbacks(
    680                         PrintDocumentAdapterDelegate.this);
    681                 try {
    682                     observer.onDestroy();
    683                 } catch (RemoteException re) {
    684                     Log.e(LOG_TAG, "Error announcing destroyed state", re);
    685                 }
    686             }
    687         }
    688 
    689         private boolean isFinished() {
    690             return mDocumentAdapter == null;
    691         }
    692 
    693         private void clearLocked() {
    694             mActivity = null;
    695             mDocumentAdapter = null;
    696             mHandler = null;
    697             mLayoutOrWriteCancellation = null;
    698             mLastLayoutSpec = null;
    699             if (mLastWriteSpec != null) {
    700                 IoUtils.closeQuietly(mLastWriteSpec.fd);
    701                 mLastWriteSpec = null;
    702             }
    703         }
    704 
    705         private boolean cancelPreviousCancellableOperationLocked() {
    706             if (mLayoutOrWriteCancellation != null) {
    707                 mLayoutOrWriteCancellation.cancel();
    708                 if (DEBUG) {
    709                     Log.i(LOG_TAG, "Cancelling previous operation");
    710                 }
    711                 return true;
    712             }
    713             return false;
    714         }
    715 
    716         private void doPendingWorkLocked() {
    717             if (mStartReqeusted && !mStarted) {
    718                 mStarted = true;
    719                 mHandler.sendEmptyMessage(MyHandler.MSG_START);
    720             } else if (mLastLayoutSpec != null) {
    721                 mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
    722             } else if (mLastWriteSpec != null) {
    723                 mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
    724             } else if (mFinishRequested && !mFinished) {
    725                 mFinished = true;
    726                 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
    727             }
    728         }
    729 
    730         private class LayoutSpec {
    731             ILayoutResultCallback callback;
    732             PrintAttributes oldAttributes;
    733             PrintAttributes newAttributes;
    734             Bundle metadata;
    735             int sequence;
    736         }
    737 
    738         private class WriteSpec {
    739             IWriteResultCallback callback;
    740             PageRange[] pages;
    741             ParcelFileDescriptor fd;
    742             int sequence;
    743         }
    744 
    745         private final class MyHandler extends Handler {
    746             public static final int MSG_START = 1;
    747             public static final int MSG_LAYOUT = 2;
    748             public static final int MSG_WRITE = 3;
    749             public static final int MSG_FINISH = 4;
    750 
    751             public MyHandler(Looper looper) {
    752                 super(looper, null, true);
    753             }
    754 
    755             @Override
    756             public void handleMessage(Message message) {
    757                 if (isFinished()) {
    758                     return;
    759                 }
    760                 switch (message.what) {
    761                     case MSG_START: {
    762                         final PrintDocumentAdapter adapter;
    763                         synchronized (mLock) {
    764                             adapter = mDocumentAdapter;
    765                         }
    766                         if (adapter != null) {
    767                             adapter.onStart();
    768                         }
    769                     } break;
    770 
    771                     case MSG_LAYOUT: {
    772                         final PrintDocumentAdapter adapter;
    773                         final CancellationSignal cancellation;
    774                         final LayoutSpec layoutSpec;
    775 
    776                         synchronized (mLock) {
    777                             adapter = mDocumentAdapter;
    778                             layoutSpec = mLastLayoutSpec;
    779                             mLastLayoutSpec = null;
    780                             cancellation = new CancellationSignal();
    781                             mLayoutOrWriteCancellation = cancellation;
    782                         }
    783 
    784                         if (layoutSpec != null && adapter != null) {
    785                             if (DEBUG) {
    786                                 Log.i(LOG_TAG, "Performing layout");
    787                             }
    788                             adapter.onLayout(layoutSpec.oldAttributes,
    789                                     layoutSpec.newAttributes, cancellation,
    790                                     new MyLayoutResultCallback(layoutSpec.callback,
    791                                             layoutSpec.sequence), layoutSpec.metadata);
    792                         }
    793                     } break;
    794 
    795                     case MSG_WRITE: {
    796                         final PrintDocumentAdapter adapter;
    797                         final CancellationSignal cancellation;
    798                         final WriteSpec writeSpec;
    799 
    800                         synchronized (mLock) {
    801                             adapter = mDocumentAdapter;
    802                             writeSpec = mLastWriteSpec;
    803                             mLastWriteSpec = null;
    804                             cancellation = new CancellationSignal();
    805                             mLayoutOrWriteCancellation = cancellation;
    806                         }
    807 
    808                         if (writeSpec != null && adapter != null) {
    809                             if (DEBUG) {
    810                                 Log.i(LOG_TAG, "Performing write");
    811                             }
    812                             adapter.onWrite(writeSpec.pages, writeSpec.fd,
    813                                     cancellation, new MyWriteResultCallback(writeSpec.callback,
    814                                             writeSpec.fd, writeSpec.sequence));
    815                         }
    816                     } break;
    817 
    818                     case MSG_FINISH: {
    819                         if (DEBUG) {
    820                             Log.i(LOG_TAG, "Performing finish");
    821                         }
    822                         final PrintDocumentAdapter adapter;
    823                         final Activity activity;
    824                         synchronized (mLock) {
    825                             adapter = mDocumentAdapter;
    826                             activity = mActivity;
    827                             clearLocked();
    828                         }
    829                         if (adapter != null) {
    830                             adapter.onFinish();
    831                         }
    832                         if (activity != null) {
    833                             activity.getApplication().unregisterActivityLifecycleCallbacks(
    834                                     PrintDocumentAdapterDelegate.this);
    835                         }
    836                     } break;
    837 
    838                     default: {
    839                         throw new IllegalArgumentException("Unknown message: "
    840                                 + message.what);
    841                     }
    842                 }
    843             }
    844         }
    845 
    846         private final class MyLayoutResultCallback extends LayoutResultCallback {
    847             private ILayoutResultCallback mCallback;
    848             private final int mSequence;
    849 
    850             public MyLayoutResultCallback(ILayoutResultCallback callback,
    851                     int sequence) {
    852                 mCallback = callback;
    853                 mSequence = sequence;
    854             }
    855 
    856             @Override
    857             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
    858                 if (info == null) {
    859                     throw new NullPointerException("document info cannot be null");
    860                 }
    861                 final ILayoutResultCallback callback;
    862                 synchronized (mLock) {
    863                     if (mDestroyed) {
    864                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    865                                 + "finish the printing activity before print completion?");
    866                         return;
    867                     }
    868                     callback = mCallback;
    869                     clearLocked();
    870                 }
    871                 if (callback != null) {
    872                     try {
    873                         callback.onLayoutFinished(info, changed, mSequence);
    874                     } catch (RemoteException re) {
    875                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
    876                     }
    877                 }
    878             }
    879 
    880             @Override
    881             public void onLayoutFailed(CharSequence error) {
    882                 final ILayoutResultCallback callback;
    883                 synchronized (mLock) {
    884                     if (mDestroyed) {
    885                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    886                                 + "finish the printing activity before print completion?");
    887                         return;
    888                     }
    889                     callback = mCallback;
    890                     clearLocked();
    891                 }
    892                 if (callback != null) {
    893                     try {
    894                         callback.onLayoutFailed(error, mSequence);
    895                     } catch (RemoteException re) {
    896                         Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
    897                     }
    898                 }
    899             }
    900 
    901             @Override
    902             public void onLayoutCancelled() {
    903                 synchronized (mLock) {
    904                     if (mDestroyed) {
    905                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    906                                 + "finish the printing activity before print completion?");
    907                         return;
    908                     }
    909                     clearLocked();
    910                 }
    911             }
    912 
    913             private void clearLocked() {
    914                 mLayoutOrWriteCancellation = null;
    915                 mCallback = null;
    916                 doPendingWorkLocked();
    917             }
    918         }
    919 
    920         private final class MyWriteResultCallback extends WriteResultCallback {
    921             private ParcelFileDescriptor mFd;
    922             private int mSequence;
    923             private IWriteResultCallback mCallback;
    924 
    925             public MyWriteResultCallback(IWriteResultCallback callback,
    926                     ParcelFileDescriptor fd, int sequence) {
    927                 mFd = fd;
    928                 mSequence = sequence;
    929                 mCallback = callback;
    930             }
    931 
    932             @Override
    933             public void onWriteFinished(PageRange[] pages) {
    934                 final IWriteResultCallback callback;
    935                 synchronized (mLock) {
    936                     if (mDestroyed) {
    937                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    938                                 + "finish the printing activity before print completion?");
    939                         return;
    940                     }
    941                     callback = mCallback;
    942                     clearLocked();
    943                 }
    944                 if (pages == null) {
    945                     throw new IllegalArgumentException("pages cannot be null");
    946                 }
    947                 if (pages.length == 0) {
    948                     throw new IllegalArgumentException("pages cannot be empty");
    949                 }
    950                 if (callback != null) {
    951                     try {
    952                         callback.onWriteFinished(pages, mSequence);
    953                     } catch (RemoteException re) {
    954                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
    955                     }
    956                 }
    957             }
    958 
    959             @Override
    960             public void onWriteFailed(CharSequence error) {
    961                 final IWriteResultCallback callback;
    962                 synchronized (mLock) {
    963                     if (mDestroyed) {
    964                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    965                                 + "finish the printing activity before print completion?");
    966                         return;
    967                     }
    968                     callback = mCallback;
    969                     clearLocked();
    970                 }
    971                 if (callback != null) {
    972                     try {
    973                         callback.onWriteFailed(error, mSequence);
    974                     } catch (RemoteException re) {
    975                         Log.e(LOG_TAG, "Error calling onWriteFailed", re);
    976                     }
    977                 }
    978             }
    979 
    980             @Override
    981             public void onWriteCancelled() {
    982                 synchronized (mLock) {
    983                     if (mDestroyed) {
    984                         Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    985                                 + "finish the printing activity before print completion?");
    986                         return;
    987                     }
    988                     clearLocked();
    989                 }
    990             }
    991 
    992             private void clearLocked() {
    993                 mLayoutOrWriteCancellation = null;
    994                 IoUtils.closeQuietly(mFd);
    995                 mCallback = null;
    996                 mFd = null;
    997                 doPendingWorkLocked();
    998             }
    999         }
   1000     }
   1001 
   1002     private static final class PrintJobStateChangeListenerWrapper extends
   1003             IPrintJobStateChangeListener.Stub {
   1004         private final WeakReference<PrintJobStateChangeListener> mWeakListener;
   1005         private final WeakReference<Handler> mWeakHandler;
   1006 
   1007         public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
   1008                 Handler handler) {
   1009             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
   1010             mWeakHandler = new WeakReference<Handler>(handler);
   1011         }
   1012 
   1013         @Override
   1014         public void onPrintJobStateChanged(PrintJobId printJobId) {
   1015             Handler handler = mWeakHandler.get();
   1016             PrintJobStateChangeListener listener = mWeakListener.get();
   1017             if (handler != null && listener != null) {
   1018                 SomeArgs args = SomeArgs.obtain();
   1019                 args.arg1 = this;
   1020                 args.arg2 = printJobId;
   1021                 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
   1022                         args).sendToTarget();
   1023             }
   1024         }
   1025 
   1026         public void destroy() {
   1027             mWeakListener.clear();
   1028         }
   1029 
   1030         public PrintJobStateChangeListener getListener() {
   1031             return mWeakListener.get();
   1032         }
   1033     }
   1034 }
   1035