PrintManager.java revision 860f8a6b663ca96d30d17da09eca8caf065aae62
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.Log;
34
35import com.android.internal.os.SomeArgs;
36
37import libcore.io.IoUtils;
38
39import java.io.File;
40import java.lang.ref.WeakReference;
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.List;
44
45/**
46 * System level service for accessing the printing capabilities of the platform.
47 * <p>
48 * To obtain a handle to the print manager do the following:
49 * </p>
50 * <pre>
51 * PrintManager printManager =
52 *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
53 * </pre>
54 */
55public final class PrintManager {
56
57    private static final String LOG_TAG = "PrintManager";
58
59    /** @hide */
60    public static final int APP_ID_ANY = -2;
61
62    private final Context mContext;
63
64    private final IPrintManager mService;
65
66    private final int mUserId;
67
68    private final int mAppId;
69
70    private final PrintClient mPrintClient;
71
72    private final Handler mHandler;
73
74    /**
75     * Creates a new instance.
76     *
77     * @param context The current context in which to operate.
78     * @param service The backing system service.
79     *
80     * @hide
81     */
82    public PrintManager(Context context, IPrintManager service, int userId, int appId) {
83        mContext = context;
84        mService = service;
85        mUserId = userId;
86        mAppId = appId;
87        mPrintClient = new PrintClient(this);
88        mHandler = new Handler(context.getMainLooper(), null, false) {
89            @Override
90            public void handleMessage(Message message) {
91                SomeArgs args = (SomeArgs) message.obj;
92                Context context = (Context) args.arg1;
93                IntentSender intent = (IntentSender) args.arg2;
94                args.recycle();
95                try {
96                    context.startIntentSender(intent, null, 0, 0, 0);
97                } catch (SendIntentException sie) {
98                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
99                }
100            }
101        };
102    }
103
104    /**
105     * Creates an instance that can access all print jobs.
106     *
107     * @param userId The user id for which to get all print jobs.
108     * @return An instance if the caller has the permission to access
109     * all print jobs, null otherwise.
110     *
111     * @hide
112     */
113    public PrintManager getGlobalPrintManagerForUser(int userId) {
114        return new PrintManager(mContext, mService, userId, APP_ID_ANY);
115    }
116
117    PrintJobInfo getPrintJobInfo(int printJobId) {
118        try {
119            return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
120        } catch (RemoteException re) {
121            Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
122        }
123        return null;
124    }
125
126    /**
127     * Gets the print jobs for this application.
128     *
129     * @return The print job list.
130     *
131     * @see PrintJob
132     */
133    public List<PrintJob> getPrintJobs() {
134        try {
135            List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
136            if (printJobInfos == null) {
137                return Collections.emptyList();
138            }
139            final int printJobCount = printJobInfos.size();
140            List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
141            for (int i = 0; i < printJobCount; i++) {
142                printJobs.add(new PrintJob(printJobInfos.get(i), this));
143            }
144            return printJobs;
145        } catch (RemoteException re) {
146            Log.e(LOG_TAG, "Error getting print jobs", re);
147        }
148        return Collections.emptyList();
149    }
150
151    void cancelPrintJob(int printJobId) {
152        try {
153            mService.cancelPrintJob(printJobId, mAppId, mUserId);
154        } catch (RemoteException re) {
155            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
156        }
157    }
158
159    /**
160     * Creates a print job for printing a file with default print attributes.
161     *
162     * @param printJobName A name for the new print job.
163     * @param pdfFile The PDF file to print.
164     * @param documentInfo Information about the printed document.
165     * @param attributes The default print job attributes.
166     * @return The created print job on success or null on failure.
167     *
168     * @see PrintJob
169     */
170    public PrintJob print(String printJobName, File pdfFile, PrintDocumentInfo documentInfo,
171            PrintAttributes attributes) {
172        PrintFileDocumentAdapter documentAdapter = new PrintFileDocumentAdapter(
173                mContext, pdfFile, documentInfo);
174        return print(printJobName, documentAdapter, attributes);
175    }
176
177    /**
178     * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
179     * attributes.
180     *
181     * @param printJobName A name for the new print job.
182     * @param documentAdapter An adapter that emits the document to print.
183     * @param attributes The default print job attributes.
184     * @return The created print job on success or null on failure.
185     *
186     * @see PrintJob
187     */
188    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
189            PrintAttributes attributes) {
190        if (TextUtils.isEmpty(printJobName)) {
191            throw new IllegalArgumentException("priintJobName cannot be empty");
192        }
193        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
194                mContext.getMainLooper());
195        try {
196            PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate,
197                    attributes, mAppId, mUserId);
198            if (printJob != null) {
199                return new PrintJob(printJob, this);
200            }
201        } catch (RemoteException re) {
202            Log.e(LOG_TAG, "Error creating a print job", re);
203        }
204        return null;
205    }
206
207    /**
208     * Gets the list of enabled print services.
209     *
210     * @return The enabled service list or an empty list.
211     *
212     * @hide
213     */
214    public List<PrintServiceInfo> getEnabledPrintServices() {
215        try {
216            List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
217            if (enabledServices != null) {
218                return enabledServices;
219            }
220        } catch (RemoteException re) {
221            Log.e(LOG_TAG, "Error getting the enalbed print services", re);
222        }
223        return Collections.emptyList();
224    }
225
226    /**
227     * @hide
228     */
229    public PrinterDiscoverySession createPrinterDiscoverySession() {
230        return new PrinterDiscoverySession(mService, mContext, mUserId);
231    }
232
233    private static final class PrintClient extends IPrintClient.Stub {
234
235        private final WeakReference<PrintManager> mWeakPrintManager;
236
237        public PrintClient(PrintManager manager) {
238            mWeakPrintManager = new WeakReference<PrintManager>(manager);
239        }
240
241        @Override
242        public void startPrintJobConfigActivity(IntentSender intent)  {
243            PrintManager manager = mWeakPrintManager.get();
244            if (manager != null) {
245                SomeArgs args = SomeArgs.obtain();
246                args.arg1 =  manager.mContext;
247                args.arg2 = intent;
248                manager.mHandler.obtainMessage(0, args).sendToTarget();
249            }
250        }
251    }
252
253    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
254
255        private final Object mLock = new Object();
256
257        private CancellationSignal mLayoutOrWriteCancellation;
258
259        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
260
261        private Handler mHandler; // Strong reference OK - cleared in finish()
262
263        public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
264            mDocumentAdapter = documentAdapter;
265            mHandler = new MyHandler(looper);
266        }
267
268        @Override
269        public void start() {
270            mHandler.sendEmptyMessage(MyHandler.MSG_START);
271        }
272
273        @Override
274        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
275                ILayoutResultCallback callback, Bundle metadata, int sequence) {
276            synchronized (mLock) {
277                if (mLayoutOrWriteCancellation != null) {
278                    mLayoutOrWriteCancellation.cancel();
279                }
280            }
281            SomeArgs args = SomeArgs.obtain();
282            args.arg1 = oldAttributes;
283            args.arg2 = newAttributes;
284            args.arg3 = callback;
285            args.arg4 = metadata;
286            args.argi1 = sequence;
287            mHandler.removeMessages(MyHandler.MSG_LAYOUT);
288            mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
289        }
290
291        @Override
292        public void write(PageRange[] pages, ParcelFileDescriptor fd,
293            IWriteResultCallback callback, int sequence) {
294            synchronized (mLock) {
295                if (mLayoutOrWriteCancellation != null) {
296                    mLayoutOrWriteCancellation.cancel();
297                }
298            }
299            SomeArgs args = SomeArgs.obtain();
300            args.arg1 = pages;
301            args.arg2 = fd;
302            args.arg3 = callback;
303            args.argi1 = sequence;
304            mHandler.removeMessages(MyHandler.MSG_WRITE);
305            mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
306        }
307
308        @Override
309        public void finish() {
310            mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
311        }
312
313        private boolean isFinished() {
314            return mDocumentAdapter == null;
315        }
316
317        private void doFinish() {
318            mDocumentAdapter = null;
319            mHandler = null;
320            mLayoutOrWriteCancellation = null;
321        }
322
323        private final class MyHandler extends Handler {
324            public static final int MSG_START = 1;
325            public static final int MSG_LAYOUT = 2;
326            public static final int MSG_WRITE = 3;
327            public static final int MSG_FINISH = 4;
328
329            public MyHandler(Looper looper) {
330                super(looper, null, true);
331            }
332
333            @Override
334            public void handleMessage(Message message) {
335                if (isFinished()) {
336                    return;
337                }
338                switch (message.what) {
339                    case MSG_START: {
340                        mDocumentAdapter.onStart();
341                    } break;
342
343                    case MSG_LAYOUT: {
344                        SomeArgs args = (SomeArgs) message.obj;
345                        PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
346                        PrintAttributes newAttributes = (PrintAttributes) args.arg2;
347                        ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
348                        Bundle metadata = (Bundle) args.arg4;
349                        final int sequence = args.argi1;
350                        args.recycle();
351
352                        CancellationSignal cancellation = new CancellationSignal();
353                        synchronized (mLock) {
354                            mLayoutOrWriteCancellation = cancellation;
355                        }
356
357                        mDocumentAdapter.onLayout(oldAttributes, newAttributes, cancellation,
358                                new MyLayoutResultCallback(callback, sequence), metadata);
359                    } break;
360
361                    case MSG_WRITE: {
362                        SomeArgs args = (SomeArgs) message.obj;
363                        PageRange[] pages = (PageRange[]) args.arg1;
364                        ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg2;
365                        IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
366                        final int sequence = args.argi1;
367                        args.recycle();
368
369                        CancellationSignal cancellation = new CancellationSignal();
370                        synchronized (mLock) {
371                            mLayoutOrWriteCancellation = cancellation;
372                        }
373
374                        mDocumentAdapter.onWrite(pages, fd, cancellation,
375                                new MyWriteResultCallback(callback, fd, sequence));
376                    } break;
377
378                    case MSG_FINISH: {
379                        mDocumentAdapter.onFinish();
380                        doFinish();
381                    } break;
382
383                    default: {
384                        throw new IllegalArgumentException("Unknown message: "
385                                + message.what);
386                    }
387                }
388            }
389        }
390
391        private final class MyLayoutResultCallback extends LayoutResultCallback {
392            private ILayoutResultCallback mCallback;
393            private final int mSequence;
394
395            public MyLayoutResultCallback(ILayoutResultCallback callback,
396                    int sequence) {
397                mCallback = callback;
398                mSequence = sequence;
399            }
400
401            @Override
402            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
403                if (info == null) {
404                    throw new NullPointerException("document info cannot be null");
405                }
406                final ILayoutResultCallback callback;
407                synchronized (mLock) {
408                    callback = mCallback;
409                    clearLocked();
410                }
411                if (callback != null) {
412                    try {
413                        callback.onLayoutFinished(info, changed, mSequence);
414                    } catch (RemoteException re) {
415                        Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
416                    }
417                }
418            }
419
420            @Override
421            public void onLayoutFailed(CharSequence error) {
422                final ILayoutResultCallback callback;
423                synchronized (mLock) {
424                    callback = mCallback;
425                    clearLocked();
426                }
427                if (callback != null) {
428                    try {
429                        callback.onLayoutFailed(error, mSequence);
430                    } catch (RemoteException re) {
431                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
432                    }
433                }
434            }
435
436            @Override
437            public void onLayoutCancelled() {
438                synchronized (mLock) {
439                    clearLocked();
440                }
441            }
442
443            private void clearLocked() {
444                mLayoutOrWriteCancellation = null;
445                mCallback = null;
446            }
447        }
448
449        private final class MyWriteResultCallback extends WriteResultCallback {
450            private ParcelFileDescriptor mFd;
451            private int mSequence;
452            private IWriteResultCallback mCallback;
453
454            public MyWriteResultCallback(IWriteResultCallback callback,
455                    ParcelFileDescriptor fd, int sequence) {
456                mFd = fd;
457                mSequence = sequence;
458                mCallback = callback;
459            }
460
461            @Override
462            public void onWriteFinished(PageRange[] pages) {
463                final IWriteResultCallback callback;
464                synchronized (mLock) {
465                    callback = mCallback;
466                    clearLocked();
467                }
468                if (pages == null) {
469                    throw new IllegalArgumentException("pages cannot be null");
470                }
471                if (pages.length == 0) {
472                    throw new IllegalArgumentException("pages cannot be empty");
473                }
474                if (callback != null) {
475                    try {
476                        callback.onWriteFinished(pages, mSequence);
477                    } catch (RemoteException re) {
478                        Log.e(LOG_TAG, "Error calling onWriteFinished", re);
479                    }
480                }
481            }
482
483            @Override
484            public void onWriteFailed(CharSequence error) {
485                final IWriteResultCallback callback;
486                synchronized (mLock) {
487                    callback = mCallback;
488                    clearLocked();
489                }
490                if (callback != null) {
491                    try {
492                        callback.onWriteFailed(error, mSequence);
493                    } catch (RemoteException re) {
494                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
495                    }
496                }
497            }
498
499            @Override
500            public void onWriteCancelled() {
501                synchronized (mLock) {
502                    clearLocked();
503                }
504            }
505
506            private void clearLocked() {
507                mLayoutOrWriteCancellation = null;
508                IoUtils.closeQuietly(mFd);
509                mCallback = null;
510                mFd = null;
511            }
512        }
513    }
514}
515