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