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