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