PrintManager.java revision 88d199130d44c6bacb383a7757e782cf97483c68
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.ICancellationSignal;
26import android.os.Looper;
27import android.os.Message;
28import android.os.ParcelFileDescriptor;
29import android.os.RemoteException;
30import android.print.PrintDocumentAdapter.LayoutResultCallback;
31import android.print.PrintDocumentAdapter.WriteResultCallback;
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.io.FileDescriptor;
41import java.lang.ref.WeakReference;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.List;
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    /**
76     * Creates a new instance.
77     *
78     * @param context The current context in which to operate.
79     * @param service The backing system service.
80     *
81     * @hide
82     */
83    public PrintManager(Context context, IPrintManager service, int userId, int appId) {
84        mContext = context;
85        mService = service;
86        mUserId = userId;
87        mAppId = appId;
88        mPrintClient = new PrintClient(this);
89        mHandler = new Handler(context.getMainLooper(), null, false) {
90            @Override
91            public void handleMessage(Message message) {
92                SomeArgs args = (SomeArgs) message.obj;
93                Context context = (Context) args.arg1;
94                IntentSender intent = (IntentSender) args.arg2;
95                args.recycle();
96                try {
97                    context.startIntentSender(intent, null, 0, 0, 0);
98                } catch (SendIntentException sie) {
99                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
100                }
101            }
102        };
103    }
104
105    /**
106     * Creates an instance that can access all print jobs.
107     *
108     * @param userId The user id for which to get all print jobs.
109     * @return An instance if the caller has the permission to access
110     * all print jobs, null otherwise.
111     *
112     * @hide
113     */
114    public PrintManager getGlobalPrintManagerForUser(int userId) {
115        return new PrintManager(mContext, mService, userId, APP_ID_ANY);
116    }
117
118    PrintJobInfo getPrintJobInfo(int printJobId) {
119        try {
120            return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
121        } catch (RemoteException re) {
122            Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
123        }
124        return null;
125    }
126
127    /**
128     * Gets the print jobs for this application.
129     *
130     * @return The print job list.
131     *
132     * @see PrintJob
133     */
134    public List<PrintJob> getPrintJobs() {
135        try {
136            List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
137            if (printJobInfos == null) {
138                return Collections.emptyList();
139            }
140            final int printJobCount = printJobInfos.size();
141            List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
142            for (int i = 0; i < printJobCount; i++) {
143                printJobs.add(new PrintJob(printJobInfos.get(i), this));
144            }
145            return printJobs;
146        } catch (RemoteException re) {
147            Log.e(LOG_TAG, "Error getting print jobs", re);
148        }
149        return Collections.emptyList();
150    }
151
152    void cancelPrintJob(int printJobId) {
153        try {
154            mService.cancelPrintJob(printJobId, mAppId, mUserId);
155        } catch (RemoteException re) {
156            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
157        }
158    }
159
160    /**
161     * Creates a print job for printing a file with default print attributes.
162     *
163     * @param printJobName A name for the new print job.
164     * @param pdfFile The PDF file to print.
165     * @param attributes The default print job attributes.
166     * @return The created print job.
167     *
168     * @see PrintJob
169     */
170    public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
171        FileDocumentAdapter documentAdapter = new FileDocumentAdapter(mContext, pdfFile);
172        return print(printJobName, documentAdapter, attributes);
173    }
174
175    /**
176     * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
177     * attributes.
178     *
179     * @param printJobName A name for the new print job.
180     * @param documentAdapter An adapter that emits the document to print.
181     * @param attributes The default print job attributes.
182     * @return The created print job.
183     *
184     * @see PrintJob
185     */
186    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
187            PrintAttributes attributes) {
188        if (TextUtils.isEmpty(printJobName)) {
189            throw new IllegalArgumentException("priintJobName cannot be empty");
190        }
191        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
192                mContext.getMainLooper());
193        try {
194            PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate,
195                    attributes, mAppId, mUserId);
196            if (printJob != null) {
197                return new PrintJob(printJob, this);
198            }
199        } catch (RemoteException re) {
200            Log.e(LOG_TAG, "Error creating a print job", re);
201        }
202        return null;
203    }
204
205    private static final class PrintClient extends IPrintClient.Stub {
206
207        private final WeakReference<PrintManager> mWeakPrintManager;
208
209        public PrintClient(PrintManager manager) {
210            mWeakPrintManager = new WeakReference<PrintManager>(manager);
211        }
212
213        @Override
214        public void startPrintJobConfigActivity(IntentSender intent)  {
215            PrintManager manager = mWeakPrintManager.get();
216            if (manager != null) {
217                SomeArgs args = SomeArgs.obtain();
218                args.arg1 =  manager.mContext;
219                args.arg2 = intent;
220                manager.mHandler.obtainMessage(0, args).sendToTarget();
221            }
222        }
223    }
224
225    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
226        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
227
228        private Handler mHandler; // Strong reference OK - cleared in finish()
229
230        public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
231            mDocumentAdapter = documentAdapter;
232            mHandler = new MyHandler(looper);
233        }
234
235        @Override
236        public void start() {
237            mHandler.sendEmptyMessage(MyHandler.MSG_START);
238        }
239
240        @Override
241        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
242                ILayoutResultCallback callback, Bundle metadata) {
243            SomeArgs args = SomeArgs.obtain();
244            args.arg1 = oldAttributes;
245            args.arg2 = newAttributes;
246            args.arg3 = callback;
247            args.arg4 = metadata;
248            mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
249        }
250
251        @Override
252        public void write(List<PageRange> pages, ParcelFileDescriptor fd,
253            IWriteResultCallback callback) {
254            SomeArgs args = SomeArgs.obtain();
255            args.arg1 = pages;
256            args.arg2 = fd.getFileDescriptor();
257            args.arg3 = callback;
258            mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
259        }
260
261        @Override
262        public void finish() {
263            mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
264        }
265
266        private boolean isFinished() {
267            return mDocumentAdapter == null;
268        }
269
270        private void doFinish() {
271            mDocumentAdapter = null;
272            mHandler = null;
273        }
274
275        private final class MyHandler extends Handler {
276            public static final int MSG_START = 1;
277            public static final int MSG_LAYOUT = 2;
278            public static final int MSG_WRITE = 3;
279            public static final int MSG_FINISH = 4;
280
281            public MyHandler(Looper looper) {
282                super(looper, null, true);
283            }
284
285            @Override
286            @SuppressWarnings("unchecked")
287            public void handleMessage(Message message) {
288                if (isFinished()) {
289                    return;
290                }
291                switch (message.what) {
292                    case MSG_START: {
293                        mDocumentAdapter.onStart();
294                    } break;
295
296                    case MSG_LAYOUT: {
297                        SomeArgs args = (SomeArgs) message.obj;
298                        PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
299                        PrintAttributes newAttributes = (PrintAttributes) args.arg2;
300                        ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
301                        Bundle metadata = (Bundle) args.arg4;
302                        args.recycle();
303
304                        try {
305                            ICancellationSignal remoteSignal = CancellationSignal.createTransport();
306                            callback.onLayoutStarted(remoteSignal);
307
308                            mDocumentAdapter.onLayout(oldAttributes, newAttributes,
309                                    CancellationSignal.fromTransport(remoteSignal),
310                                    new LayoutResultCallbackWrapper(callback), metadata);
311                        } catch (RemoteException re) {
312                            Log.e(LOG_TAG, "Error printing", re);
313                        }
314                    } break;
315
316                    case MSG_WRITE: {
317                        SomeArgs args = (SomeArgs) message.obj;
318                        List<PageRange> pages = (List<PageRange>) args.arg1;
319                        FileDescriptor fd = (FileDescriptor) args.arg2;
320                        IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
321                        args.recycle();
322
323                        try {
324                            ICancellationSignal remoteSignal = CancellationSignal.createTransport();
325                            callback.onWriteStarted(remoteSignal);
326
327                            mDocumentAdapter.onWrite(pages, fd,
328                                    CancellationSignal.fromTransport(remoteSignal),
329                                    new WriteResultCallbackWrapper(callback, fd));
330                        } catch (RemoteException re) {
331                            Log.e(LOG_TAG, "Error printing", re);
332                            IoUtils.closeQuietly(fd);
333                        }
334                    } break;
335
336                    case MSG_FINISH: {
337                        mDocumentAdapter.onFinish();
338                        doFinish();
339                    } break;
340
341                    default: {
342                        throw new IllegalArgumentException("Unknown message: "
343                                + message.what);
344                    }
345                }
346            }
347        }
348    }
349
350    private static final class WriteResultCallbackWrapper extends WriteResultCallback {
351
352        private final IWriteResultCallback mWrappedCallback;
353        private final FileDescriptor mFd;
354
355        public WriteResultCallbackWrapper(IWriteResultCallback callback,
356                FileDescriptor fd) {
357            mWrappedCallback = callback;
358            mFd = fd;
359        }
360
361        @Override
362        public void onWriteFinished(List<PageRange> pages) {
363            try {
364                // Close before notifying the other end. We want
365                // to be ready by the time we announce it.
366                IoUtils.closeQuietly(mFd);
367                mWrappedCallback.onWriteFinished(pages);
368            } catch (RemoteException re) {
369                Log.e(LOG_TAG, "Error calling onWriteFinished", re);
370            }
371        }
372
373        @Override
374        public void onWriteFailed(CharSequence error) {
375            try {
376                // Close before notifying the other end. We want
377                // to be ready by the time we announce it.
378                IoUtils.closeQuietly(mFd);
379                mWrappedCallback.onWriteFailed(error);
380            } catch (RemoteException re) {
381                Log.e(LOG_TAG, "Error calling onWriteFailed", re);
382            }
383        }
384    }
385
386    private static final class LayoutResultCallbackWrapper extends LayoutResultCallback {
387
388        private final ILayoutResultCallback mWrappedCallback;
389
390        public LayoutResultCallbackWrapper(ILayoutResultCallback callback) {
391            mWrappedCallback = callback;
392        }
393
394        @Override
395        public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
396            try {
397                mWrappedCallback.onLayoutFinished(info, changed);
398            } catch (RemoteException re) {
399                Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
400            }
401        }
402
403        @Override
404        public void onLayoutFailed(CharSequence error) {
405            try {
406                mWrappedCallback.onLayoutFailed(error);
407            } catch (RemoteException re) {
408                Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
409            }
410        }
411    }
412}
413