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