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