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