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 onActivityPaused(Activity activity) {
    638             /* do nothing */
    639         }
    640 
    641         @Override
    642         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    643             /* do nothing */
    644         }
    645 
    646         @Override
    647         public void onActivityStarted(Activity activity) {
    648             /* do nothing */
    649         }
    650 
    651         @Override
    652         public void onActivityResumed(Activity activity) {
    653             /* do nothing */
    654         }
    655 
    656         @Override
    657         public void onActivityStopped(Activity activity) {
    658             /* do nothing */
    659         }
    660 
    661         @Override
    662         public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    663             /* do nothing */
    664         }
    665 
    666         @Override
    667         public void onActivityDestroyed(Activity activity) {
    668             // We really care only if the activity is being destroyed to
    669             // notify the the print spooler so it can close the print dialog.
    670             // Note the the spooler has a death recipient that observes if
    671             // this process gets killed so we cover the case of onDestroy not
    672             // being called due to this process being killed to reclaim memory.
    673             IPrintDocumentAdapterObserver observer = null;
    674             synchronized (mLock) {
    675                 if (activity == mActivity) {
    676                     observer = mObserver;
    677                     destroyLocked();
    678                 }
    679             }
    680             if (observer != null) {
    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 isDestroyedLocked() {
    690             return (mActivity == null);
    691         }
    692 
    693         private void destroyLocked() {
    694             mActivity.getApplication().unregisterActivityLifecycleCallbacks(
    695                     PrintDocumentAdapterDelegate.this);
    696             mActivity = null;
    697 
    698             mDocumentAdapter = null;
    699 
    700             // This method is only called from the main thread, so
    701             // clearing the messages guarantees that any time a
    702             // message is handled we are not in a destroyed state.
    703             mHandler.removeMessages(MyHandler.MSG_ON_START);
    704             mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
    705             mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
    706             mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
    707             mHandler = null;
    708 
    709             mObserver = null;
    710 
    711             if (mPendingCallback != null) {
    712                 mPendingCallback.destroy();
    713                 mPendingCallback = null;
    714             }
    715         }
    716 
    717         private final class MyHandler extends Handler {
    718             public static final int MSG_ON_START = 1;
    719             public static final int MSG_ON_LAYOUT = 2;
    720             public static final int MSG_ON_WRITE = 3;
    721             public static final int MSG_ON_FINISH = 4;
    722 
    723             public MyHandler(Looper looper) {
    724                 super(looper, null, true);
    725             }
    726 
    727             @Override
    728             public void handleMessage(Message message) {
    729                 switch (message.what) {
    730                     case MSG_ON_START: {
    731                         if (DEBUG) {
    732                             Log.i(LOG_TAG, "onStart()");
    733                         }
    734 
    735                         ((PrintDocumentAdapter) message.obj).onStart();
    736                     } break;
    737 
    738                     case MSG_ON_LAYOUT: {
    739                         SomeArgs args = (SomeArgs) message.obj;
    740                         PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
    741                         PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
    742                         PrintAttributes newAttributes = (PrintAttributes) args.arg3;
    743                         CancellationSignal cancellation = (CancellationSignal) args.arg4;
    744                         LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
    745                         Bundle metadata = (Bundle) args.arg6;
    746                         args.recycle();
    747 
    748                         if (DEBUG) {
    749                             StringBuilder builder = new StringBuilder();
    750                             builder.append("PrintDocumentAdapter#onLayout() {\n");
    751                             builder.append("\n  oldAttributes:").append(oldAttributes);
    752                             builder.append("\n  newAttributes:").append(newAttributes);
    753                             builder.append("\n  preview:").append(metadata.getBoolean(
    754                                     PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
    755                             builder.append("\n}");
    756                             Log.i(LOG_TAG, builder.toString());
    757                         }
    758 
    759                         adapter.onLayout(oldAttributes, newAttributes, cancellation,
    760                                 callback, metadata);
    761                     } break;
    762 
    763                     case MSG_ON_WRITE: {
    764                         SomeArgs args = (SomeArgs) message.obj;
    765                         PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
    766                         PageRange[] pages = (PageRange[]) args.arg2;
    767                         ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
    768                         CancellationSignal cancellation = (CancellationSignal) args.arg4;
    769                         WriteResultCallback callback = (WriteResultCallback) args.arg5;
    770                         args.recycle();
    771 
    772                         if (DEBUG) {
    773                             StringBuilder builder = new StringBuilder();
    774                             builder.append("PrintDocumentAdapter#onWrite() {\n");
    775                             builder.append("\n  pages:").append(Arrays.toString(pages));
    776                             builder.append("\n}");
    777                             Log.i(LOG_TAG, builder.toString());
    778                         }
    779 
    780                         adapter.onWrite(pages, fd, cancellation, callback);
    781                     } break;
    782 
    783                     case MSG_ON_FINISH: {
    784                         if (DEBUG) {
    785                             Log.i(LOG_TAG, "onFinish()");
    786                         }
    787 
    788                         ((PrintDocumentAdapter) message.obj).onFinish();
    789 
    790                         // Done printing, so destroy this instance as it
    791                         // should not be used anymore.
    792                         synchronized (mLock) {
    793                             destroyLocked();
    794                         }
    795                     } break;
    796 
    797                     default: {
    798                         throw new IllegalArgumentException("Unknown message: "
    799                                 + message.what);
    800                     }
    801                 }
    802             }
    803         }
    804 
    805         private interface DestroyableCallback {
    806             public void destroy();
    807         }
    808 
    809         private final class MyLayoutResultCallback extends LayoutResultCallback
    810                 implements DestroyableCallback {
    811             private ILayoutResultCallback mCallback;
    812             private final int mSequence;
    813 
    814             public MyLayoutResultCallback(ILayoutResultCallback callback,
    815                     int sequence) {
    816                 mCallback = callback;
    817                 mSequence = sequence;
    818             }
    819 
    820             @Override
    821             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
    822                 final ILayoutResultCallback callback;
    823                 synchronized (mLock) {
    824                     callback = mCallback;
    825                 }
    826 
    827                 // If the callback is null we are destroyed.
    828                 if (callback == null) {
    829                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    830                             + "finish the printing activity before print completion "
    831                             + "or did you invoke a callback after finish?");
    832                     return;
    833                 }
    834 
    835                 try {
    836                     if (info == null) {
    837                         throw new NullPointerException("document info cannot be null");
    838                     }
    839 
    840                     try {
    841                         callback.onLayoutFinished(info, changed, mSequence);
    842                     } catch (RemoteException re) {
    843                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
    844                     }
    845                 } finally {
    846                     destroy();
    847                 }
    848             }
    849 
    850             @Override
    851             public void onLayoutFailed(CharSequence error) {
    852                 final ILayoutResultCallback callback;
    853                 synchronized (mLock) {
    854                     callback = mCallback;
    855                 }
    856 
    857                 // If the callback is null we are destroyed.
    858                 if (callback == null) {
    859                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    860                             + "finish the printing activity before print completion "
    861                             + "or did you invoke a callback after finish?");
    862                     return;
    863                 }
    864 
    865                 try {
    866                     callback.onLayoutFailed(error, mSequence);
    867                 } catch (RemoteException re) {
    868                     Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
    869                 } finally {
    870                     destroy();
    871                 }
    872             }
    873 
    874             @Override
    875             public void onLayoutCancelled() {
    876                 final ILayoutResultCallback callback;
    877                 synchronized (mLock) {
    878                     callback = mCallback;
    879                 }
    880 
    881                 // If the callback is null we are destroyed.
    882                 if (callback == null) {
    883                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    884                             + "finish the printing activity before print completion "
    885                             + "or did you invoke a callback after finish?");
    886                     return;
    887                 }
    888 
    889                 try {
    890                     callback.onLayoutCanceled(mSequence);
    891                 } catch (RemoteException re) {
    892                     Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
    893                 } finally {
    894                     destroy();
    895                 }
    896             }
    897 
    898             @Override
    899             public void destroy() {
    900                 synchronized (mLock) {
    901                     mCallback = null;
    902                     mPendingCallback = null;
    903                 }
    904             }
    905         }
    906 
    907         private final class MyWriteResultCallback extends WriteResultCallback
    908                 implements DestroyableCallback {
    909             private ParcelFileDescriptor mFd;
    910             private IWriteResultCallback mCallback;
    911             private final int mSequence;
    912 
    913             public MyWriteResultCallback(IWriteResultCallback callback,
    914                     ParcelFileDescriptor fd, int sequence) {
    915                 mFd = fd;
    916                 mSequence = sequence;
    917                 mCallback = callback;
    918             }
    919 
    920             @Override
    921             public void onWriteFinished(PageRange[] pages) {
    922                 final IWriteResultCallback callback;
    923                 synchronized (mLock) {
    924                     callback = mCallback;
    925                 }
    926 
    927                 // If the callback is null we are destroyed.
    928                 if (callback == null) {
    929                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    930                             + "finish the printing activity before print completion "
    931                             + "or did you invoke a callback after finish?");
    932                     return;
    933                 }
    934 
    935                 try {
    936                     if (pages == null) {
    937                         throw new IllegalArgumentException("pages cannot be null");
    938                     }
    939                     if (pages.length == 0) {
    940                         throw new IllegalArgumentException("pages cannot be empty");
    941                     }
    942 
    943                     try {
    944                         callback.onWriteFinished(pages, mSequence);
    945                     } catch (RemoteException re) {
    946                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
    947                     }
    948                 } finally {
    949                     destroy();
    950                 }
    951             }
    952 
    953             @Override
    954             public void onWriteFailed(CharSequence error) {
    955                 final IWriteResultCallback callback;
    956                 synchronized (mLock) {
    957                     callback = mCallback;
    958                 }
    959 
    960                 // If the callback is null we are destroyed.
    961                 if (callback == null) {
    962                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    963                             + "finish the printing activity before print completion "
    964                             + "or did you invoke a callback after finish?");
    965                     return;
    966                 }
    967 
    968                 try {
    969                     callback.onWriteFailed(error, mSequence);
    970                 } catch (RemoteException re) {
    971                     Log.e(LOG_TAG, "Error calling onWriteFailed", re);
    972                 } finally {
    973                     destroy();
    974                 }
    975             }
    976 
    977             @Override
    978             public void onWriteCancelled() {
    979                 final IWriteResultCallback callback;
    980                 synchronized (mLock) {
    981                     callback = mCallback;
    982                 }
    983 
    984                 // If the callback is null we are destroyed.
    985                 if (callback == null) {
    986                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
    987                             + "finish the printing activity before print completion "
    988                             + "or did you invoke a callback after finish?");
    989                     return;
    990                 }
    991 
    992                 try {
    993                     callback.onWriteCanceled(mSequence);
    994                 } catch (RemoteException re) {
    995                     Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
    996                 } finally {
    997                     destroy();
    998                 }
    999             }
   1000 
   1001             @Override
   1002             public void destroy() {
   1003                 synchronized (mLock) {
   1004                     IoUtils.closeQuietly(mFd);
   1005                     mCallback = null;
   1006                     mFd = null;
   1007                     mPendingCallback = null;
   1008                 }
   1009             }
   1010         }
   1011     }
   1012 
   1013     private static final class PrintJobStateChangeListenerWrapper extends
   1014             IPrintJobStateChangeListener.Stub {
   1015         private final WeakReference<PrintJobStateChangeListener> mWeakListener;
   1016         private final WeakReference<Handler> mWeakHandler;
   1017 
   1018         public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
   1019                 Handler handler) {
   1020             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
   1021             mWeakHandler = new WeakReference<Handler>(handler);
   1022         }
   1023 
   1024         @Override
   1025         public void onPrintJobStateChanged(PrintJobId printJobId) {
   1026             Handler handler = mWeakHandler.get();
   1027             PrintJobStateChangeListener listener = mWeakListener.get();
   1028             if (handler != null && listener != null) {
   1029                 SomeArgs args = SomeArgs.obtain();
   1030                 args.arg1 = this;
   1031                 args.arg2 = printJobId;
   1032                 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
   1033                         args).sendToTarget();
   1034             }
   1035         }
   1036 
   1037         public void destroy() {
   1038             mWeakListener.clear();
   1039         }
   1040 
   1041         public PrintJobStateChangeListener getListener() {
   1042             return mWeakListener.get();
   1043         }
   1044     }
   1045 }
   1046