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