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