blob: 9e8cfad8a73de2ce1aeb6c6ab1fb44c8a61d365a [file] [log] [blame]
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001/*
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;
Svetoslav62836082013-07-17 14:52:35 -070022import android.os.Bundle;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070023import android.os.CancellationSignal;
24import android.os.Handler;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070025import android.os.Looper;
26import android.os.Message;
27import android.os.ParcelFileDescriptor;
28import android.os.RemoteException;
Svetoslav Ganova0027152013-06-25 14:59:53 -070029import android.print.PrintDocumentAdapter.LayoutResultCallback;
30import android.print.PrintDocumentAdapter.WriteResultCallback;
Svetoslav Ganov88d19912013-07-22 12:32:03 -070031import android.text.TextUtils;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070032import 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 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070075 * 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.
Svetoslav Ganova0027152013-06-25 14:59:53 -0700108 * @return An instance if the caller has the permission to access
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700109 * 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
Svetoslav Ganova0027152013-06-25 14:59:53 -0700117 PrintJobInfo getPrintJobInfo(int printJobId) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700118 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700119 return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700120 } catch (RemoteException re) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700121 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700122 }
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 {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700135 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700136 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) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700146 Log.e(LOG_TAG, "Error getting print jobs", re);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700147 }
148 return Collections.emptyList();
149 }
150
Svetoslav Ganova0027152013-06-25 14:59:53 -0700151 void cancelPrintJob(int printJobId) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700152 try {
153 mService.cancelPrintJob(printJobId, mAppId, mUserId);
154 } catch (RemoteException re) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700155 Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700156 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700157 }
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.
Svetoslavfd906512013-06-24 09:04:48 -0700166 *
167 * @see PrintJob
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700168 */
169 public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
Svetoslav17b7f6e2013-06-24 18:29:33 -0700170 FileDocumentAdapter documentAdapter = new FileDocumentAdapter(mContext, pdfFile);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700171 return print(printJobName, documentAdapter, attributes);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700172 }
173
174 /**
Svetoslav Ganova0027152013-06-25 14:59:53 -0700175 * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700176 * attributes.
177 *
178 * @param printJobName A name for the new print job.
Svetoslav Ganova0027152013-06-25 14:59:53 -0700179 * @param documentAdapter An adapter that emits the document to print.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700180 * @param attributes The default print job attributes.
181 * @return The created print job.
Svetoslavfd906512013-06-24 09:04:48 -0700182 *
183 * @see PrintJob
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700184 */
Svetoslav Ganova0027152013-06-25 14:59:53 -0700185 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700186 PrintAttributes attributes) {
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700187 if (TextUtils.isEmpty(printJobName)) {
188 throw new IllegalArgumentException("priintJobName cannot be empty");
189 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700190 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700191 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
Svetoslav Ganova0027152013-06-25 14:59:53 -0700224 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700225
226 private final Object mLock = new Object();
227
228 private CancellationSignal mLayoutOrWriteCancellation;
229
Svetoslav Ganova0027152013-06-25 14:59:53 -0700230 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700231
Svetoslav Ganova0027152013-06-25 14:59:53 -0700232 private Handler mHandler; // Strong reference OK - cleared in finish()
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700233
Svetoslav Ganova0027152013-06-25 14:59:53 -0700234 public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
235 mDocumentAdapter = documentAdapter;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700236 mHandler = new MyHandler(looper);
237 }
238
239 @Override
240 public void start() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700241 mHandler.sendEmptyMessage(MyHandler.MSG_START);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700242 }
243
244 @Override
Svetoslav62836082013-07-17 14:52:35 -0700245 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700246 ILayoutResultCallback callback, Bundle metadata, int sequence) {
247 synchronized (mLock) {
248 if (mLayoutOrWriteCancellation != null) {
249 mLayoutOrWriteCancellation.cancel();
250 }
251 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700252 SomeArgs args = SomeArgs.obtain();
253 args.arg1 = oldAttributes;
254 args.arg2 = newAttributes;
255 args.arg3 = callback;
Svetoslav62836082013-07-17 14:52:35 -0700256 args.arg4 = metadata;
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700257 args.argi1 = sequence;
258 mHandler.removeMessages(MyHandler.MSG_LAYOUT);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700259 mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700260 }
261
262 @Override
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700263 public void write(PageRange[] pages, ParcelFileDescriptor fd,
264 IWriteResultCallback callback, int sequence) {
265 synchronized (mLock) {
266 if (mLayoutOrWriteCancellation != null) {
267 mLayoutOrWriteCancellation.cancel();
268 }
269 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700270 SomeArgs args = SomeArgs.obtain();
271 args.arg1 = pages;
272 args.arg2 = fd.getFileDescriptor();
273 args.arg3 = callback;
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700274 args.argi1 = sequence;
275 mHandler.removeMessages(MyHandler.MSG_WRITE);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700276 mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700277 }
278
279 @Override
280 public void finish() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700281 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700282 }
283
Svetoslav Ganova0027152013-06-25 14:59:53 -0700284 private boolean isFinished() {
285 return mDocumentAdapter == null;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700286 }
287
Svetoslav Ganova0027152013-06-25 14:59:53 -0700288 private void doFinish() {
289 mDocumentAdapter = null;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700290 mHandler = null;
291 }
292
293 private final class MyHandler extends Handler {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700294 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;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700298
299 public MyHandler(Looper looper) {
300 super(looper, null, true);
301 }
302
303 @Override
304 public void handleMessage(Message message) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700305 if (isFinished()) {
306 return;
307 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700308 switch (message.what) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700309 case MSG_START: {
310 mDocumentAdapter.onStart();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700311 } break;
312
Svetoslav Ganova0027152013-06-25 14:59:53 -0700313 case MSG_LAYOUT: {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700314 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700315 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;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700320 args.recycle();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700321
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700322 CancellationSignal cancellation = new CancellationSignal();
323 synchronized (mLock) {
324 mLayoutOrWriteCancellation = cancellation;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700325 }
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700326
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);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700363 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700364
Svetoslav Ganova0027152013-06-25 14:59:53 -0700365 case MSG_WRITE: {
366 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700367 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;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700371 args.recycle();
372
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700373 CancellationSignal cancellation = new CancellationSignal();
374 synchronized (mLock) {
375 mLayoutOrWriteCancellation = cancellation;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700376 }
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700377
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 });
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700425 } break;
426
Svetoslav Ganova0027152013-06-25 14:59:53 -0700427 case MSG_FINISH: {
428 mDocumentAdapter.onFinish();
429 doFinish();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700430 } break;
431
432 default: {
433 throw new IllegalArgumentException("Unknown message: "
434 + message.what);
435 }
436 }
437 }
438 }
439 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700440}