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