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