blob: 25fc968fec5a7bc6e10ef59c2cefced7948f5833 [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
Philip P. Moltmannc43639c2015-12-18 13:58:40 -080019import android.annotation.NonNull;
20import android.annotation.Nullable;
Svetoslav Ganov858a1852013-10-17 22:20:40 -070021import android.app.Activity;
22import android.app.Application.ActivityLifecycleCallbacks;
Philip P. Moltmann66c96592016-02-24 11:32:43 -080023import android.content.ComponentName;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070024import android.content.Context;
25import android.content.IntentSender;
26import android.content.IntentSender.SendIntentException;
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -080027import android.graphics.drawable.Icon;
Svetoslav62836082013-07-17 14:52:35 -070028import android.os.Bundle;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070029import android.os.CancellationSignal;
30import android.os.Handler;
Svetoslava798c0a2014-05-15 10:47:19 -070031import android.os.ICancellationSignal;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070032import android.os.Looper;
33import android.os.Message;
34import android.os.ParcelFileDescriptor;
35import android.os.RemoteException;
Svetoslav Ganova0027152013-06-25 14:59:53 -070036import android.print.PrintDocumentAdapter.LayoutResultCallback;
37import android.print.PrintDocumentAdapter.WriteResultCallback;
Svetoslav Ganov860f8a62013-09-14 00:59:03 -070038import android.printservice.PrintServiceInfo;
Svetoslav Ganov88d19912013-07-22 12:32:03 -070039import android.text.TextUtils;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070040import android.util.ArrayMap;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070041import android.util.Log;
42
43import com.android.internal.os.SomeArgs;
44
45import libcore.io.IoUtils;
46
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070047import java.lang.ref.WeakReference;
48import java.util.ArrayList;
Svetoslava798c0a2014-05-15 10:47:19 -070049import java.util.Arrays;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070050import java.util.Collections;
51import java.util.List;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070052import java.util.Map;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070053
54/**
55 * System level service for accessing the printing capabilities of the platform.
56 * <p>
57 * To obtain a handle to the print manager do the following:
58 * </p>
Svetoslava798c0a2014-05-15 10:47:19 -070059 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070060 * <pre>
61 * PrintManager printManager =
62 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
63 * </pre>
Svetoslava798c0a2014-05-15 10:47:19 -070064 *
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -070065 * <h3>Print mechanics</h3>
66 * <p>
67 * The key idea behind printing on the platform is that the content to be printed
68 * should be laid out for the currently selected print options resulting in an
69 * optimized output and higher user satisfaction. To achieve this goal the platform
70 * declares a contract that the printing application has to follow which is defined
71 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that
72 * when the user selects some options from the print UI that may affect the way
73 * content is laid out, for example page size, the application receives a callback
74 * allowing it to layout the content to better fit these new constraints. After a
75 * layout pass the system may ask the application to render one or more pages one
76 * or more times. For example, an application may produce a single column list for
77 * smaller page sizes and a multi-column table for larger page sizes.
78 * </p>
79 * <h3>Print jobs</h3>
80 * <p>
81 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter,
82 * PrintAttributes)} from an activity which results in bringing up the system print
83 * UI. Once the print UI is up, when the user changes a selected print option that
84 * affects the way content is laid out the system starts to interact with the
85 * application following the mechanics described the section above.
86 * </p>
87 * <p>
88 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link
89 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started},
90 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED
91 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link
92 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated
93 * system spooler until they are handled which is they are cancelled or completed.
94 * Active print jobs, ones that are not cancelled or completed, are considered failed
95 * if the device reboots as the new boot may be after a very long time. The user may
96 * choose to restart such print jobs. Once a print job is queued all relevant content
97 * is stored in the system spooler and its lifecycle becomes detached from this of
98 * the application that created it.
99 * </p>
100 * <p>
101 * An applications can query the print spooler for current print jobs it created
102 * but not print jobs created by other applications.
103 * </p>
104 *
105 * @see PrintJob
106 * @see PrintJobInfo
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700107 */
108public final class PrintManager {
109
110 private static final String LOG_TAG = "PrintManager";
111
Svetoslav15cbc8a2014-07-11 09:45:07 -0700112 private static final boolean DEBUG = false;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700113
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700114 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800115 private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2;
116
117 /**
118 * Package name of print spooler.
119 *
120 * @hide
121 */
122 public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
123
124 /**
125 * Select enabled services.
126 * </p>
127 * @see #getPrintServices
128 * @hide
129 */
130 public static final int ENABLED_SERVICES = 1 << 0;
131
132 /**
133 * Select disabled services.
134 * </p>
135 * @see #getPrintServices
136 * @hide
137 */
138 public static final int DISABLED_SERVICES = 1 << 1;
139
140 /**
141 * Select all services.
142 * </p>
143 * @see #getPrintServices
144 * @hide
145 */
146 public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES;
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700147
148 /**
149 * The action for launching the print dialog activity.
150 *
151 * @hide
152 */
153 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
154
155 /**
156 * Extra with the intent for starting the print dialog.
157 * <p>
158 * <strong>Type:</strong> {@link android.content.IntentSender}
159 * </p>
160 *
161 * @hide
162 */
163 public static final String EXTRA_PRINT_DIALOG_INTENT =
164 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
165
166 /**
167 * Extra with a print job.
168 * <p>
169 * <strong>Type:</strong> {@link android.print.PrintJobInfo}
170 * </p>
171 *
172 * @hide
173 */
174 public static final String EXTRA_PRINT_JOB =
175 "android.print.intent.extra.EXTRA_PRINT_JOB";
176
177 /**
178 * Extra with the print document adapter to be printed.
179 * <p>
180 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
181 * </p>
182 *
183 * @hide
184 */
185 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
186 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700187
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700188 /** @hide */
189 public static final int APP_ID_ANY = -2;
190
191 private final Context mContext;
192
193 private final IPrintManager mService;
194
195 private final int mUserId;
196
197 private final int mAppId;
198
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700199 private final Handler mHandler;
200
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800201 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper>
202 mPrintJobStateChangeListeners;
203 private Map<PrintServicesChangeListener, PrintServicesChangeListenerWrapper>
204 mPrintServicesChangeListeners;
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700205
206 /** @hide */
207 public interface PrintJobStateChangeListener {
208
209 /**
210 * Callback notifying that a print job state changed.
Svetoslav2235a172014-05-08 14:19:01 -0700211 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700212 * @param printJobId The print job id.
213 */
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700214 public void onPrintJobStateChanged(PrintJobId printJobId);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700215 }
216
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800217 /** @hide */
218 public interface PrintServicesChangeListener {
219
220 /**
221 * Callback notifying that the print services changed.
222 */
223 public void onPrintServicesChanged();
224 }
225
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700226 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700227 * Creates a new instance.
Svetoslav2235a172014-05-08 14:19:01 -0700228 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700229 * @param context The current context in which to operate.
230 * @param service The backing system service.
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800231 * @param userId The user id in which to operate.
232 * @param appId The application id in which to operate.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700233 * @hide
234 */
235 public PrintManager(Context context, IPrintManager service, int userId, int appId) {
236 mContext = context;
237 mService = service;
238 mUserId = userId;
239 mAppId = appId;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700240 mHandler = new Handler(context.getMainLooper(), null, false) {
241 @Override
242 public void handleMessage(Message message) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700243 switch (message.what) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700244 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
245 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700246 PrintJobStateChangeListenerWrapper wrapper =
247 (PrintJobStateChangeListenerWrapper) args.arg1;
248 PrintJobStateChangeListener listener = wrapper.getListener();
249 if (listener != null) {
250 PrintJobId printJobId = (PrintJobId) args.arg2;
251 listener.onPrintJobStateChanged(printJobId);
252 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700253 args.recycle();
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700254 } break;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800255 case MSG_NOTIFY_PRINT_SERVICES_CHANGED: {
256 PrintServicesChangeListenerWrapper wrapper =
257 (PrintServicesChangeListenerWrapper) message.obj;
258 PrintServicesChangeListener listener = wrapper.getListener();
259 if (listener != null) {
260 listener.onPrintServicesChanged();
261 }
262 } break;
263
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700264 }
265 }
266 };
267 }
268
269 /**
270 * Creates an instance that can access all print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700271 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700272 * @param userId The user id for which to get all print jobs.
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700273 * @return An instance if the caller has the permission to access all print
274 * jobs, null otherwise.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700275 * @hide
276 */
277 public PrintManager getGlobalPrintManagerForUser(int userId) {
Svetoslav2235a172014-05-08 14:19:01 -0700278 if (mService == null) {
279 Log.w(LOG_TAG, "Feature android.software.print not available");
280 return null;
281 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700282 return new PrintManager(mContext, mService, userId, APP_ID_ANY);
283 }
284
Svetoslav2fbd2a72013-09-16 17:53:51 -0700285 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700286 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700287 return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700288 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700289 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700290 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700291 }
292
293 /**
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700294 * Adds a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700295 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700296 * @param listener The listener to add.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700297 * @hide
298 */
299 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700300 if (mService == null) {
301 Log.w(LOG_TAG, "Feature android.software.print not available");
302 return;
303 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700304 if (mPrintJobStateChangeListeners == null) {
305 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
306 PrintJobStateChangeListenerWrapper>();
307 }
308 PrintJobStateChangeListenerWrapper wrappedListener =
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700309 new PrintJobStateChangeListenerWrapper(listener, mHandler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700310 try {
311 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
312 mPrintJobStateChangeListeners.put(listener, wrappedListener);
313 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700314 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700315 }
316 }
317
318 /**
319 * Removes a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700320 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700321 * @param listener The listener to remove.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700322 * @hide
323 */
324 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700325 if (mService == null) {
326 Log.w(LOG_TAG, "Feature android.software.print not available");
327 return;
328 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700329 if (mPrintJobStateChangeListeners == null) {
330 return;
331 }
332 PrintJobStateChangeListenerWrapper wrappedListener =
333 mPrintJobStateChangeListeners.remove(listener);
334 if (wrappedListener == null) {
335 return;
336 }
337 if (mPrintJobStateChangeListeners.isEmpty()) {
338 mPrintJobStateChangeListeners = null;
339 }
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700340 wrappedListener.destroy();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700341 try {
342 mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
343 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700344 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700345 }
346 }
347
348 /**
349 * Gets a print job given its id.
Svetoslav2235a172014-05-08 14:19:01 -0700350 *
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800351 * @param printJobId The id of the print job.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700352 * @return The print job list.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700353 * @see PrintJob
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700354 * @hide
355 */
356 public PrintJob getPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700357 if (mService == null) {
358 Log.w(LOG_TAG, "Feature android.software.print not available");
359 return null;
360 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700361 try {
362 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
363 if (printJob != null) {
364 return new PrintJob(printJob, this);
365 }
366 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700367 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700368 }
369 return null;
370 }
371
372 /**
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800373 * Get the custom icon for a printer. If the icon is not cached, the icon is
374 * requested asynchronously. Once it is available the printer is updated.
375 *
376 * @param printerId the id of the printer the icon should be loaded for
377 * @return the custom icon to be used for the printer or null if the icon is
378 * not yet available
379 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
380 * @hide
381 */
382 public Icon getCustomPrinterIcon(PrinterId printerId) {
383 if (mService == null) {
384 Log.w(LOG_TAG, "Feature android.software.print not available");
385 return null;
386 }
387 try {
388 return mService.getCustomPrinterIcon(printerId, mUserId);
389 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700390 throw re.rethrowFromSystemServer();
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800391 }
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800392 }
393
394 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700395 * Gets the print jobs for this application.
Svetoslav2235a172014-05-08 14:19:01 -0700396 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700397 * @return The print job list.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700398 * @see PrintJob
399 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800400 public @NonNull List<PrintJob> getPrintJobs() {
Svetoslav2235a172014-05-08 14:19:01 -0700401 if (mService == null) {
402 Log.w(LOG_TAG, "Feature android.software.print not available");
403 return Collections.emptyList();
404 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700405 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700406 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700407 if (printJobInfos == null) {
408 return Collections.emptyList();
409 }
410 final int printJobCount = printJobInfos.size();
411 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
412 for (int i = 0; i < printJobCount; i++) {
413 printJobs.add(new PrintJob(printJobInfos.get(i), this));
414 }
415 return printJobs;
416 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700417 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700418 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700419 }
420
Svetoslav2fbd2a72013-09-16 17:53:51 -0700421 void cancelPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700422 if (mService == null) {
423 Log.w(LOG_TAG, "Feature android.software.print not available");
424 return;
425 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700426 try {
427 mService.cancelPrintJob(printJobId, mAppId, mUserId);
428 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700429 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700430 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700431 }
432
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700433 void restartPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700434 if (mService == null) {
435 Log.w(LOG_TAG, "Feature android.software.print not available");
436 return;
437 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700438 try {
439 mService.restartPrintJob(printJobId, mAppId, mUserId);
440 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700441 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700442 }
443 }
444
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700445 /**
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700446 * Creates a print job for printing a {@link PrintDocumentAdapter} with
447 * default print attributes.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700448 * <p>
449 * Calling this method brings the print UI allowing the user to customize
450 * the print job and returns a {@link PrintJob} object without waiting for the
451 * user to customize or confirm the print job. The returned print job instance
452 * is in a {@link PrintJobInfo#STATE_CREATED created} state.
453 * <p>
454 * This method can be called only from an {@link Activity}. The rationale is that
455 * printing from a service will create an inconsistent user experience as the print
456 * UI would appear without any context.
457 * </p>
458 * <p>
459 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
460 * your activity is finished. The rationale is that once the activity that
461 * initiated printing is finished, the provided adapter may be in an inconsistent
462 * state as it may depend on the UI presented by the activity.
463 * </p>
464 * <p>
465 * The default print attributes are a hint to the system how the data is to
466 * be printed. For example, a photo editor may look at the photo aspect ratio
467 * to determine the default orientation and provide a hint whether the printing
468 * should be in portrait or landscape. The system will do a best effort to
469 * selected the hinted options in the print dialog, given the current printer
470 * supports them.
471 * </p>
Svetoslav81f14b92014-03-18 10:37:20 -0700472 * <p>
473 * <strong>Note:</strong> Calling this method will bring the print dialog and
474 * the system will connect to the provided {@link PrintDocumentAdapter}. If a
Svetoslav85a85a02014-03-18 10:48:51 -0700475 * configuration change occurs that you application does not handle, for example
476 * a rotation change, the system will drop the connection to the adapter as the
477 * activity has to be recreated and the old adapter may be invalid in this context,
478 * hence a new adapter instance is required. As a consequence, if your activity
479 * does not handle configuration changes (default behavior), you have to save the
480 * state that you were printing and call this method again when your activity
481 * is recreated.
Svetoslav81f14b92014-03-18 10:37:20 -0700482 * </p>
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700483 *
484 * @param printJobName A name for the new print job which is shown to the user.
Svetoslav Ganova0027152013-06-25 14:59:53 -0700485 * @param documentAdapter An adapter that emits the document to print.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700486 * @param attributes The default print job attributes or <code>null</code>.
Svetoslav Ganovd26d4892013-08-28 14:37:54 -0700487 * @return The created print job on success or null on failure.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700488 * @throws IllegalStateException If not called from an {@link Activity}.
489 * @throws IllegalArgumentException If the print job name is empty or the
490 * document adapter is null.
491 *
Svetoslavfd906512013-06-24 09:04:48 -0700492 * @see PrintJob
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700493 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800494 public @NonNull PrintJob print(@NonNull String printJobName,
495 @NonNull PrintDocumentAdapter documentAdapter,
496 @Nullable PrintAttributes attributes) {
Svetoslav2235a172014-05-08 14:19:01 -0700497 if (mService == null) {
498 Log.w(LOG_TAG, "Feature android.software.print not available");
499 return null;
500 }
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700501 if (!(mContext instanceof Activity)) {
502 throw new IllegalStateException("Can print only from an activity");
503 }
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700504 if (TextUtils.isEmpty(printJobName)) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700505 throw new IllegalArgumentException("printJobName cannot be empty");
506 }
507 if (documentAdapter == null) {
508 throw new IllegalArgumentException("documentAdapter cannot be null");
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700509 }
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700510 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700511 (Activity) mContext, documentAdapter);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700512 try {
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700513 Bundle result = mService.print(printJobName, delegate,
514 attributes, mContext.getPackageName(), mAppId, mUserId);
515 if (result != null) {
516 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
517 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
518 if (printJob == null || intent == null) {
519 return null;
520 }
521 try {
522 mContext.startIntentSender(intent, null, 0, 0, 0);
523 return new PrintJob(printJob, this);
524 } catch (SendIntentException sie) {
525 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
526 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700527 }
528 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700529 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700530 }
531 return null;
532 }
533
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700534 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800535 * Listen for changes to the installed and enabled print services.
Svetoslav2235a172014-05-08 14:19:01 -0700536 *
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800537 * @param listener the listener to add
538 *
539 * @see android.print.PrintManager#getPrintServices
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700540 */
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800541 void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700542 if (mService == null) {
543 Log.w(LOG_TAG, "Feature android.software.print not available");
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800544 return;
Svetoslav2235a172014-05-08 14:19:01 -0700545 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800546 if (mPrintServicesChangeListeners == null) {
547 mPrintServicesChangeListeners = new ArrayMap<PrintServicesChangeListener,
548 PrintServicesChangeListenerWrapper>();
549 }
550 PrintServicesChangeListenerWrapper wrappedListener =
551 new PrintServicesChangeListenerWrapper(listener, mHandler);
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700552 try {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800553 mService.addPrintServicesChangeListener(wrappedListener, mUserId);
554 mPrintServicesChangeListeners.put(listener, wrappedListener);
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700555 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700556 throw re.rethrowFromSystemServer();
Svetoslavd8dbc132013-09-27 18:29:53 -0700557 }
Svetoslavd8dbc132013-09-27 18:29:53 -0700558 }
559
560 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800561 * Stop listening for changes to the installed and enabled print services.
Svetoslav2235a172014-05-08 14:19:01 -0700562 *
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800563 * @param listener the listener to remove
564 *
565 * @see android.print.PrintManager#getPrintServices
Svetoslavd8dbc132013-09-27 18:29:53 -0700566 */
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800567 void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700568 if (mService == null) {
569 Log.w(LOG_TAG, "Feature android.software.print not available");
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800570 return;
Svetoslav2235a172014-05-08 14:19:01 -0700571 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800572 if (mPrintServicesChangeListeners == null) {
573 return;
574 }
575 PrintServicesChangeListenerWrapper wrappedListener =
576 mPrintServicesChangeListeners.remove(listener);
577 if (wrappedListener == null) {
578 return;
579 }
580 if (mPrintServicesChangeListeners.isEmpty()) {
581 mPrintServicesChangeListeners = null;
582 }
583 wrappedListener.destroy();
Svetoslavd8dbc132013-09-27 18:29:53 -0700584 try {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800585 mService.removePrintServicesChangeListener(wrappedListener, mUserId);
586 } catch (RemoteException re) {
587 Log.e(LOG_TAG, "Error removing print services change listener", re);
588 }
589 }
590
591
592 /**
593 * Gets the list of print services, but does not register for updates. The user has to register
594 * for updates by itself, or use {@link PrintServicesLoader}.
595 *
596 * @param selectionFlags flags selecting which services to get. Either
597 * {@link #ENABLED_SERVICES},{@link #DISABLED_SERVICES}, or both.
598 *
599 * @return The enabled service list or an empty list.
600 *
601 * @see #addPrintServicesChangeListener(PrintServicesChangeListener)
602 * @see #removePrintServicesChangeListener(PrintServicesChangeListener)
603 *
604 * @hide
605 */
606 public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) {
607 try {
608 List<PrintServiceInfo> services = mService.getPrintServices(selectionFlags, mUserId);
609 if (services != null) {
610 return services;
Svetoslavd8dbc132013-09-27 18:29:53 -0700611 }
612 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700613 throw re.rethrowFromSystemServer();
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700614 }
615 return Collections.emptyList();
616 }
617
618 /**
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700619 * @hide
620 */
621 public PrinterDiscoverySession createPrinterDiscoverySession() {
Svetoslav2235a172014-05-08 14:19:01 -0700622 if (mService == null) {
623 Log.w(LOG_TAG, "Feature android.software.print not available");
624 return null;
625 }
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700626 return new PrinterDiscoverySession(mService, mContext, mUserId);
627 }
628
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -0800629 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800630 * Enable or disable a print service.
631 *
632 * @param service The service to enabled or disable
633 * @param isEnabled whether the service should be enabled or disabled
634 *
635 * @hide
636 */
637 public void setPrintServiceEnabled(@NonNull ComponentName service, boolean isEnabled) {
638 if (mService == null) {
639 Log.w(LOG_TAG, "Feature android.software.print not available");
640 return;
641 }
642 try {
643 mService.setPrintServiceEnabled(service, isEnabled, mUserId);
644 } catch (RemoteException re) {
645 Log.e(LOG_TAG, "Error enabling or disabling " + service, re);
646 }
647 }
648
649 /**
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -0800650 * @hide
651 */
652 public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700653 implements ActivityLifecycleCallbacks {
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700654 private final Object mLock = new Object();
655
Svetoslava798c0a2014-05-15 10:47:19 -0700656 private Activity mActivity; // Strong reference OK - cleared in destroy
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700657
Svetoslava798c0a2014-05-15 10:47:19 -0700658 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700659
Svetoslava798c0a2014-05-15 10:47:19 -0700660 private Handler mHandler; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700661
Svetoslava798c0a2014-05-15 10:47:19 -0700662 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700663
Svetoslava798c0a2014-05-15 10:47:19 -0700664 private DestroyableCallback mPendingCallback;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700665
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700666 public PrintDocumentAdapterDelegate(Activity activity,
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700667 PrintDocumentAdapter documentAdapter) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700668 mActivity = activity;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700669 mDocumentAdapter = documentAdapter;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700670 mHandler = new MyHandler(mActivity.getMainLooper());
671 mActivity.getApplication().registerActivityLifecycleCallbacks(this);
672 }
673
674 @Override
675 public void setObserver(IPrintDocumentAdapterObserver observer) {
676 final boolean destroyed;
677 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700678 mObserver = observer;
679 destroyed = isDestroyedLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700680 }
Svetoslava798c0a2014-05-15 10:47:19 -0700681
Svetoslav6552bf32014-09-03 21:15:55 -0700682 if (destroyed && observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700683 try {
684 observer.onDestroy();
685 } catch (RemoteException re) {
686 Log.e(LOG_TAG, "Error announcing destroyed state", re);
687 }
688 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700689 }
690
691 @Override
692 public void start() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700693 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700694 // If destroyed the handler is null.
695 if (!isDestroyedLocked()) {
696 mHandler.obtainMessage(MyHandler.MSG_ON_START,
697 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700698 }
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700699 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700700 }
701
702 @Override
Svetoslav62836082013-07-17 14:52:35 -0700703 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700704 ILayoutResultCallback callback, Bundle metadata, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700705
Svetoslava798c0a2014-05-15 10:47:19 -0700706 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
707 try {
708 callback.onLayoutStarted(cancellationTransport, sequence);
709 } catch (RemoteException re) {
710 // The spooler is dead - can't recover.
711 Log.e(LOG_TAG, "Error notifying for layout start", re);
712 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700713 }
Svetoslava798c0a2014-05-15 10:47:19 -0700714
715 synchronized (mLock) {
716 // If destroyed the handler is null.
717 if (isDestroyedLocked()) {
718 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700719 }
Svetoslava798c0a2014-05-15 10:47:19 -0700720
721 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
722 cancellationTransport);
723
724 SomeArgs args = SomeArgs.obtain();
725 args.arg1 = mDocumentAdapter;
726 args.arg2 = oldAttributes;
727 args.arg3 = newAttributes;
728 args.arg4 = cancellationSignal;
729 args.arg5 = new MyLayoutResultCallback(callback, sequence);
730 args.arg6 = metadata;
731
732 mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700733 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700734 }
735
736 @Override
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700737 public void write(PageRange[] pages, ParcelFileDescriptor fd,
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700738 IWriteResultCallback callback, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700739
Svetoslava798c0a2014-05-15 10:47:19 -0700740 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
741 try {
742 callback.onWriteStarted(cancellationTransport, sequence);
743 } catch (RemoteException re) {
744 // The spooler is dead - can't recover.
745 Log.e(LOG_TAG, "Error notifying for write start", re);
746 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700747 }
Svetoslava798c0a2014-05-15 10:47:19 -0700748
749 synchronized (mLock) {
750 // If destroyed the handler is null.
751 if (isDestroyedLocked()) {
752 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700753 }
Svetoslava798c0a2014-05-15 10:47:19 -0700754
755 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
756 cancellationTransport);
757
758 SomeArgs args = SomeArgs.obtain();
759 args.arg1 = mDocumentAdapter;
760 args.arg2 = pages;
761 args.arg3 = fd;
762 args.arg4 = cancellationSignal;
763 args.arg5 = new MyWriteResultCallback(callback, fd, sequence);
764
765 mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700766 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700767 }
768
769 @Override
770 public void finish() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700771 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700772 // If destroyed the handler is null.
773 if (!isDestroyedLocked()) {
774 mHandler.obtainMessage(MyHandler.MSG_ON_FINISH,
775 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700776 }
Svetoslavd270cb92013-10-31 14:27:16 -0700777 }
778 }
779
780 @Override
Svet Ganovfce84f02014-10-31 16:56:52 -0700781 public void kill(String reason) {
782 synchronized (mLock) {
783 // If destroyed the handler is null.
784 if (!isDestroyedLocked()) {
785 mHandler.obtainMessage(MyHandler.MSG_ON_KILL,
786 reason).sendToTarget();
787 }
788 }
789 }
790
791 @Override
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700792 public void onActivityPaused(Activity activity) {
793 /* do nothing */
794 }
795
796 @Override
797 public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
798 /* do nothing */
799 }
800
801 @Override
802 public void onActivityStarted(Activity activity) {
803 /* do nothing */
804 }
805
806 @Override
807 public void onActivityResumed(Activity activity) {
808 /* do nothing */
809 }
810
811 @Override
812 public void onActivityStopped(Activity activity) {
813 /* do nothing */
814 }
815
816 @Override
817 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
818 /* do nothing */
819 }
820
821 @Override
822 public void onActivityDestroyed(Activity activity) {
823 // We really care only if the activity is being destroyed to
824 // notify the the print spooler so it can close the print dialog.
825 // Note the the spooler has a death recipient that observes if
826 // this process gets killed so we cover the case of onDestroy not
827 // being called due to this process being killed to reclaim memory.
Svetoslava798c0a2014-05-15 10:47:19 -0700828 IPrintDocumentAdapterObserver observer = null;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700829 synchronized (mLock) {
830 if (activity == mActivity) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700831 observer = mObserver;
Svetoslava798c0a2014-05-15 10:47:19 -0700832 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700833 }
834 }
835 if (observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700836 try {
837 observer.onDestroy();
838 } catch (RemoteException re) {
839 Log.e(LOG_TAG, "Error announcing destroyed state", re);
840 }
841 }
842 }
843
Svetoslava798c0a2014-05-15 10:47:19 -0700844 private boolean isDestroyedLocked() {
845 return (mActivity == null);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700846 }
847
Svetoslava798c0a2014-05-15 10:47:19 -0700848 private void destroyLocked() {
849 mActivity.getApplication().unregisterActivityLifecycleCallbacks(
850 PrintDocumentAdapterDelegate.this);
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700851 mActivity = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700852
Svetoslav Ganova0027152013-06-25 14:59:53 -0700853 mDocumentAdapter = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700854
855 // This method is only called from the main thread, so
856 // clearing the messages guarantees that any time a
857 // message is handled we are not in a destroyed state.
858 mHandler.removeMessages(MyHandler.MSG_ON_START);
859 mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
860 mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
861 mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700862 mHandler = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700863
864 mObserver = null;
865
866 if (mPendingCallback != null) {
867 mPendingCallback.destroy();
868 mPendingCallback = null;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700869 }
870 }
871
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700872 private final class MyHandler extends Handler {
Svetoslava798c0a2014-05-15 10:47:19 -0700873 public static final int MSG_ON_START = 1;
874 public static final int MSG_ON_LAYOUT = 2;
875 public static final int MSG_ON_WRITE = 3;
876 public static final int MSG_ON_FINISH = 4;
Svet Ganovfce84f02014-10-31 16:56:52 -0700877 public static final int MSG_ON_KILL = 5;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700878
879 public MyHandler(Looper looper) {
880 super(looper, null, true);
881 }
882
883 @Override
884 public void handleMessage(Message message) {
885 switch (message.what) {
Svetoslava798c0a2014-05-15 10:47:19 -0700886 case MSG_ON_START: {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700887 if (DEBUG) {
Svetoslava798c0a2014-05-15 10:47:19 -0700888 Log.i(LOG_TAG, "onStart()");
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700889 }
Svetoslava798c0a2014-05-15 10:47:19 -0700890
891 ((PrintDocumentAdapter) message.obj).onStart();
892 } break;
893
894 case MSG_ON_LAYOUT: {
895 SomeArgs args = (SomeArgs) message.obj;
896 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
897 PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
898 PrintAttributes newAttributes = (PrintAttributes) args.arg3;
899 CancellationSignal cancellation = (CancellationSignal) args.arg4;
900 LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
901 Bundle metadata = (Bundle) args.arg6;
902 args.recycle();
903
904 if (DEBUG) {
905 StringBuilder builder = new StringBuilder();
906 builder.append("PrintDocumentAdapter#onLayout() {\n");
907 builder.append("\n oldAttributes:").append(oldAttributes);
908 builder.append("\n newAttributes:").append(newAttributes);
909 builder.append("\n preview:").append(metadata.getBoolean(
910 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
911 builder.append("\n}");
912 Log.i(LOG_TAG, builder.toString());
913 }
914
915 adapter.onLayout(oldAttributes, newAttributes, cancellation,
916 callback, metadata);
917 } break;
918
919 case MSG_ON_WRITE: {
920 SomeArgs args = (SomeArgs) message.obj;
921 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
922 PageRange[] pages = (PageRange[]) args.arg2;
923 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
924 CancellationSignal cancellation = (CancellationSignal) args.arg4;
925 WriteResultCallback callback = (WriteResultCallback) args.arg5;
926 args.recycle();
927
928 if (DEBUG) {
929 StringBuilder builder = new StringBuilder();
930 builder.append("PrintDocumentAdapter#onWrite() {\n");
931 builder.append("\n pages:").append(Arrays.toString(pages));
932 builder.append("\n}");
933 Log.i(LOG_TAG, builder.toString());
934 }
935
936 adapter.onWrite(pages, fd, cancellation, callback);
937 } break;
938
939 case MSG_ON_FINISH: {
940 if (DEBUG) {
941 Log.i(LOG_TAG, "onFinish()");
942 }
943
944 ((PrintDocumentAdapter) message.obj).onFinish();
945
946 // Done printing, so destroy this instance as it
947 // should not be used anymore.
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700948 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700949 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700950 }
951 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700952
Svet Ganovfce84f02014-10-31 16:56:52 -0700953 case MSG_ON_KILL: {
954 if (DEBUG) {
955 Log.i(LOG_TAG, "onKill()");
956 }
957
958 String reason = (String) message.obj;
959 throw new RuntimeException(reason);
960 }
961
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700962 default: {
963 throw new IllegalArgumentException("Unknown message: "
964 + message.what);
965 }
966 }
967 }
968 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700969
Svetoslava798c0a2014-05-15 10:47:19 -0700970 private interface DestroyableCallback {
971 public void destroy();
972 }
973
974 private final class MyLayoutResultCallback extends LayoutResultCallback
975 implements DestroyableCallback {
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700976 private ILayoutResultCallback mCallback;
977 private final int mSequence;
978
979 public MyLayoutResultCallback(ILayoutResultCallback callback,
980 int sequence) {
981 mCallback = callback;
982 mSequence = sequence;
983 }
984
985 @Override
986 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
987 final ILayoutResultCallback callback;
988 synchronized (mLock) {
989 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700990 }
Svetoslava798c0a2014-05-15 10:47:19 -0700991
992 // If the callback is null we are destroyed.
993 if (callback == null) {
994 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
995 + "finish the printing activity before print completion "
996 + "or did you invoke a callback after finish?");
997 return;
998 }
999
1000 try {
1001 if (info == null) {
1002 throw new NullPointerException("document info cannot be null");
1003 }
1004
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001005 try {
1006 callback.onLayoutFinished(info, changed, mSequence);
1007 } catch (RemoteException re) {
1008 Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
1009 }
Svetoslava798c0a2014-05-15 10:47:19 -07001010 } finally {
1011 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001012 }
1013 }
1014
1015 @Override
1016 public void onLayoutFailed(CharSequence error) {
1017 final ILayoutResultCallback callback;
1018 synchronized (mLock) {
1019 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001020 }
Svetoslava798c0a2014-05-15 10:47:19 -07001021
1022 // If the callback is null we are destroyed.
1023 if (callback == null) {
1024 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1025 + "finish the printing activity before print completion "
1026 + "or did you invoke a callback after finish?");
1027 return;
1028 }
1029
1030 try {
1031 callback.onLayoutFailed(error, mSequence);
1032 } catch (RemoteException re) {
1033 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1034 } finally {
1035 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001036 }
1037 }
1038
1039 @Override
1040 public void onLayoutCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -07001041 final ILayoutResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001042 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001043 callback = mCallback;
1044 }
1045
1046 // If the callback is null we are destroyed.
1047 if (callback == null) {
1048 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1049 + "finish the printing activity before print completion "
1050 + "or did you invoke a callback after finish?");
1051 return;
1052 }
1053
1054 try {
1055 callback.onLayoutCanceled(mSequence);
1056 } catch (RemoteException re) {
1057 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1058 } finally {
1059 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001060 }
1061 }
1062
Svetoslava798c0a2014-05-15 10:47:19 -07001063 @Override
1064 public void destroy() {
1065 synchronized (mLock) {
1066 mCallback = null;
1067 mPendingCallback = null;
1068 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001069 }
1070 }
1071
Svetoslava798c0a2014-05-15 10:47:19 -07001072 private final class MyWriteResultCallback extends WriteResultCallback
1073 implements DestroyableCallback {
Svetoslav Ganovd26d4892013-08-28 14:37:54 -07001074 private ParcelFileDescriptor mFd;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001075 private IWriteResultCallback mCallback;
Svetoslava798c0a2014-05-15 10:47:19 -07001076 private final int mSequence;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001077
1078 public MyWriteResultCallback(IWriteResultCallback callback,
Svetoslav Ganovd26d4892013-08-28 14:37:54 -07001079 ParcelFileDescriptor fd, int sequence) {
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001080 mFd = fd;
1081 mSequence = sequence;
1082 mCallback = callback;
1083 }
1084
1085 @Override
1086 public void onWriteFinished(PageRange[] pages) {
1087 final IWriteResultCallback callback;
1088 synchronized (mLock) {
1089 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001090 }
Svetoslava798c0a2014-05-15 10:47:19 -07001091
1092 // If the callback is null we are destroyed.
1093 if (callback == null) {
1094 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1095 + "finish the printing activity before print completion "
1096 + "or did you invoke a callback after finish?");
1097 return;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001098 }
Svetoslava798c0a2014-05-15 10:47:19 -07001099
1100 try {
1101 if (pages == null) {
1102 throw new IllegalArgumentException("pages cannot be null");
1103 }
1104 if (pages.length == 0) {
1105 throw new IllegalArgumentException("pages cannot be empty");
1106 }
1107
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001108 try {
1109 callback.onWriteFinished(pages, mSequence);
1110 } catch (RemoteException re) {
1111 Log.e(LOG_TAG, "Error calling onWriteFinished", re);
1112 }
Svetoslava798c0a2014-05-15 10:47:19 -07001113 } finally {
1114 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001115 }
1116 }
1117
1118 @Override
1119 public void onWriteFailed(CharSequence error) {
1120 final IWriteResultCallback callback;
1121 synchronized (mLock) {
1122 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001123 }
Svetoslava798c0a2014-05-15 10:47:19 -07001124
1125 // If the callback is null we are destroyed.
1126 if (callback == null) {
1127 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1128 + "finish the printing activity before print completion "
1129 + "or did you invoke a callback after finish?");
1130 return;
1131 }
1132
1133 try {
1134 callback.onWriteFailed(error, mSequence);
1135 } catch (RemoteException re) {
1136 Log.e(LOG_TAG, "Error calling onWriteFailed", re);
1137 } finally {
1138 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001139 }
1140 }
1141
1142 @Override
1143 public void onWriteCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -07001144 final IWriteResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001145 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001146 callback = mCallback;
1147 }
1148
1149 // If the callback is null we are destroyed.
1150 if (callback == null) {
1151 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1152 + "finish the printing activity before print completion "
1153 + "or did you invoke a callback after finish?");
1154 return;
1155 }
1156
1157 try {
1158 callback.onWriteCanceled(mSequence);
1159 } catch (RemoteException re) {
1160 Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
1161 } finally {
1162 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001163 }
1164 }
1165
Svetoslava798c0a2014-05-15 10:47:19 -07001166 @Override
1167 public void destroy() {
1168 synchronized (mLock) {
1169 IoUtils.closeQuietly(mFd);
1170 mCallback = null;
1171 mFd = null;
1172 mPendingCallback = null;
1173 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001174 }
1175 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001176 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001177
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -08001178 /**
1179 * @hide
1180 */
1181 public static final class PrintJobStateChangeListenerWrapper extends
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001182 IPrintJobStateChangeListener.Stub {
1183 private final WeakReference<PrintJobStateChangeListener> mWeakListener;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001184 private final WeakReference<Handler> mWeakHandler;
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001185
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001186 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1187 Handler handler) {
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001188 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001189 mWeakHandler = new WeakReference<Handler>(handler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001190 }
1191
1192 @Override
1193 public void onPrintJobStateChanged(PrintJobId printJobId) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001194 Handler handler = mWeakHandler.get();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001195 PrintJobStateChangeListener listener = mWeakListener.get();
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001196 if (handler != null && listener != null) {
1197 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001198 args.arg1 = this;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001199 args.arg2 = printJobId;
1200 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1201 args).sendToTarget();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001202 }
1203 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001204
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001205 public void destroy() {
1206 mWeakListener.clear();
1207 }
1208
1209 public PrintJobStateChangeListener getListener() {
1210 return mWeakListener.get();
1211 }
1212 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001213
1214 /**
1215 * @hide
1216 */
1217 public static final class PrintServicesChangeListenerWrapper extends
1218 IPrintServicesChangeListener.Stub {
1219 private final WeakReference<PrintServicesChangeListener> mWeakListener;
1220 private final WeakReference<Handler> mWeakHandler;
1221
1222 public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener,
1223 Handler handler) {
1224 mWeakListener = new WeakReference<>(listener);
1225 mWeakHandler = new WeakReference<>(handler);
1226 }
1227
1228 @Override
1229 public void onPrintServicesChanged() {
1230 Handler handler = mWeakHandler.get();
1231 PrintServicesChangeListener listener = mWeakListener.get();
1232 if (handler != null && listener != null) {
1233 handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget();
1234 }
1235 }
1236
1237 public void destroy() {
1238 mWeakListener.clear();
1239 }
1240
1241 public PrintServicesChangeListener getListener() {
1242 return mWeakListener.get();
1243 }
1244 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001245}