PrintManager.java revision 85b1f883056a1d74473fd9ce774948878f389ab6
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.io.FileDescriptor;
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 attributes The default print job attributes.
165     * @return The created print job.
166     *
167     * @see PrintJob
168     */
169    public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
170        FileDocumentAdapter documentAdapter = new FileDocumentAdapter(mContext, pdfFile);
171        return print(printJobName, documentAdapter, attributes);
172    }
173
174    /**
175     * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
176     * attributes.
177     *
178     * @param printJobName A name for the new print job.
179     * @param documentAdapter An adapter that emits the document to print.
180     * @param attributes The default print job attributes.
181     * @return The created print job.
182     *
183     * @see PrintJob
184     */
185    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
186            PrintAttributes attributes) {
187        if (TextUtils.isEmpty(printJobName)) {
188            throw new IllegalArgumentException("priintJobName cannot be empty");
189        }
190        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
191                mContext.getMainLooper());
192        try {
193            PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate,
194                    attributes, mAppId, mUserId);
195            if (printJob != null) {
196                return new PrintJob(printJob, this);
197            }
198        } catch (RemoteException re) {
199            Log.e(LOG_TAG, "Error creating a print job", re);
200        }
201        return null;
202    }
203
204    private static final class PrintClient extends IPrintClient.Stub {
205
206        private final WeakReference<PrintManager> mWeakPrintManager;
207
208        public PrintClient(PrintManager manager) {
209            mWeakPrintManager = new WeakReference<PrintManager>(manager);
210        }
211
212        @Override
213        public void startPrintJobConfigActivity(IntentSender intent)  {
214            PrintManager manager = mWeakPrintManager.get();
215            if (manager != null) {
216                SomeArgs args = SomeArgs.obtain();
217                args.arg1 =  manager.mContext;
218                args.arg2 = intent;
219                manager.mHandler.obtainMessage(0, args).sendToTarget();
220            }
221        }
222    }
223
224    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
225
226        private final Object mLock = new Object();
227
228        private CancellationSignal mLayoutOrWriteCancellation;
229
230        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
231
232        private Handler mHandler; // Strong reference OK - cleared in finish()
233
234        public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
235            mDocumentAdapter = documentAdapter;
236            mHandler = new MyHandler(looper);
237        }
238
239        @Override
240        public void start() {
241            mHandler.sendEmptyMessage(MyHandler.MSG_START);
242        }
243
244        @Override
245        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
246                ILayoutResultCallback callback, Bundle metadata, int sequence) {
247            synchronized (mLock) {
248                if (mLayoutOrWriteCancellation != null) {
249                    mLayoutOrWriteCancellation.cancel();
250                }
251            }
252            SomeArgs args = SomeArgs.obtain();
253            args.arg1 = oldAttributes;
254            args.arg2 = newAttributes;
255            args.arg3 = callback;
256            args.arg4 = metadata;
257            args.argi1 = sequence;
258            mHandler.removeMessages(MyHandler.MSG_LAYOUT);
259            mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
260        }
261
262        @Override
263        public void write(PageRange[] pages, ParcelFileDescriptor fd,
264            IWriteResultCallback callback, int sequence) {
265            synchronized (mLock) {
266                if (mLayoutOrWriteCancellation != null) {
267                    mLayoutOrWriteCancellation.cancel();
268                }
269            }
270            SomeArgs args = SomeArgs.obtain();
271            args.arg1 = pages;
272            args.arg2 = fd.getFileDescriptor();
273            args.arg3 = callback;
274            args.argi1 = sequence;
275            mHandler.removeMessages(MyHandler.MSG_WRITE);
276            mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
277        }
278
279        @Override
280        public void finish() {
281            mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
282        }
283
284        private boolean isFinished() {
285            return mDocumentAdapter == null;
286        }
287
288        private void doFinish() {
289            mDocumentAdapter = null;
290            mHandler = null;
291        }
292
293        private final class MyHandler extends Handler {
294            public static final int MSG_START = 1;
295            public static final int MSG_LAYOUT = 2;
296            public static final int MSG_WRITE = 3;
297            public static final int MSG_FINISH = 4;
298
299            public MyHandler(Looper looper) {
300                super(looper, null, true);
301            }
302
303            @Override
304            public void handleMessage(Message message) {
305                if (isFinished()) {
306                    return;
307                }
308                switch (message.what) {
309                    case MSG_START: {
310                        mDocumentAdapter.onStart();
311                    } break;
312
313                    case MSG_LAYOUT: {
314                        SomeArgs args = (SomeArgs) message.obj;
315                        final PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
316                        final PrintAttributes newAttributes = (PrintAttributes) args.arg2;
317                        final ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
318                        final Bundle metadata = (Bundle) args.arg4;
319                        final int sequence = args.argi1;
320                        args.recycle();
321
322                        CancellationSignal cancellation = new CancellationSignal();
323                        synchronized (mLock) {
324                            mLayoutOrWriteCancellation = cancellation;
325                        }
326
327                        mDocumentAdapter.onLayout(oldAttributes, newAttributes,
328                                cancellation, new LayoutResultCallback() {
329                            @Override
330                            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
331                                if (info == null) {
332                                    throw new IllegalArgumentException("info cannot be null");
333                                }
334                                synchronized (mLock) {
335                                    mLayoutOrWriteCancellation = null;
336                                }
337                                try {
338                                    callback.onLayoutFinished(info, changed, sequence);
339                                } catch (RemoteException re) {
340                                    Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
341                                }
342                            }
343
344                            @Override
345                            public void onLayoutFailed(CharSequence error) {
346                                synchronized (mLock) {
347                                    mLayoutOrWriteCancellation = null;
348                                }
349                                try {
350                                    callback.onLayoutFailed(error, sequence);
351                                } catch (RemoteException re) {
352                                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
353                                }
354                            }
355
356                            @Override
357                            public void onLayoutCancelled() {
358                                synchronized (mLock) {
359                                    mLayoutOrWriteCancellation = null;
360                                }
361                            }
362                        }, metadata);
363                    } break;
364
365                    case MSG_WRITE: {
366                        SomeArgs args = (SomeArgs) message.obj;
367                        final PageRange[] pages = (PageRange[]) args.arg1;
368                        final FileDescriptor fd = (FileDescriptor) args.arg2;
369                        final IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
370                        final int sequence = args.argi1;
371                        args.recycle();
372
373                        CancellationSignal cancellation = new CancellationSignal();
374                        synchronized (mLock) {
375                            mLayoutOrWriteCancellation = cancellation;
376                        }
377
378                        mDocumentAdapter.onWrite(pages, fd, cancellation,
379                                new WriteResultCallback() {
380                            @Override
381                            public void onWriteFinished(PageRange[] pages) {
382                                if (pages == null) {
383                                    throw new IllegalArgumentException("pages cannot be null");
384                                }
385                                if (pages.length == 0) {
386                                    throw new IllegalArgumentException("pages cannot be empty");
387                                }
388                                synchronized (mLock) {
389                                    mLayoutOrWriteCancellation = null;
390                                }
391                                // Close before notifying the other end. We want
392                                // to be ready by the time we announce it.
393                                IoUtils.closeQuietly(fd);
394                                try {
395                                    callback.onWriteFinished(pages, sequence);
396                                } catch (RemoteException re) {
397                                    Log.e(LOG_TAG, "Error calling onWriteFinished", re);
398                                }
399                            }
400
401                            @Override
402                            public void onWriteFailed(CharSequence error) {
403                                synchronized (mLock) {
404                                    mLayoutOrWriteCancellation = null;
405                                }
406                                // Close before notifying the other end. We want
407                                // to be ready by the time we announce it.
408                                IoUtils.closeQuietly(fd);
409                                try {
410                                    callback.onWriteFailed(error, sequence);
411                                } catch (RemoteException re) {
412                                    Log.e(LOG_TAG, "Error calling onWriteFailed", re);
413                                }
414                            }
415
416                            @Override
417                            public void onWriteCancelled() {
418                                synchronized (mLock) {
419                                    mLayoutOrWriteCancellation = null;
420                                }
421                                // Just close the fd for now.
422                                IoUtils.closeQuietly(fd);
423                            }
424                        });
425                    } break;
426
427                    case MSG_FINISH: {
428                        mDocumentAdapter.onFinish();
429                        doFinish();
430                    } break;
431
432                    default: {
433                        throw new IllegalArgumentException("Unknown message: "
434                                + message.what);
435                    }
436                }
437            }
438        }
439    }
440}
441