blob: 0540036f1a496e724aa4e5cd6d88b319ee0e4f44 [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;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070023import android.content.Context;
24import android.content.IntentSender;
25import android.content.IntentSender.SendIntentException;
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -080026import android.graphics.drawable.Icon;
Svetoslav62836082013-07-17 14:52:35 -070027import android.os.Bundle;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070028import android.os.CancellationSignal;
29import android.os.Handler;
Svetoslava798c0a2014-05-15 10:47:19 -070030import android.os.ICancellationSignal;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070031import android.os.Looper;
32import android.os.Message;
33import android.os.ParcelFileDescriptor;
34import android.os.RemoteException;
Svetoslav Ganova0027152013-06-25 14:59:53 -070035import android.print.PrintDocumentAdapter.LayoutResultCallback;
36import android.print.PrintDocumentAdapter.WriteResultCallback;
Svetoslav Ganov860f8a62013-09-14 00:59:03 -070037import android.printservice.PrintServiceInfo;
Svetoslav Ganov88d19912013-07-22 12:32:03 -070038import android.text.TextUtils;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070039import android.util.ArrayMap;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070040import android.util.Log;
41
42import com.android.internal.os.SomeArgs;
43
44import libcore.io.IoUtils;
45
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070046import java.lang.ref.WeakReference;
47import java.util.ArrayList;
Svetoslava798c0a2014-05-15 10:47:19 -070048import java.util.Arrays;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070049import java.util.Collections;
50import java.util.List;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070051import java.util.Map;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070052
53/**
54 * System level service for accessing the printing capabilities of the platform.
55 * <p>
56 * To obtain a handle to the print manager do the following:
57 * </p>
Svetoslava798c0a2014-05-15 10:47:19 -070058 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070059 * <pre>
60 * PrintManager printManager =
61 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
62 * </pre>
Svetoslava798c0a2014-05-15 10:47:19 -070063 *
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -070064 * <h3>Print mechanics</h3>
65 * <p>
66 * The key idea behind printing on the platform is that the content to be printed
67 * should be laid out for the currently selected print options resulting in an
68 * optimized output and higher user satisfaction. To achieve this goal the platform
69 * declares a contract that the printing application has to follow which is defined
70 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that
71 * when the user selects some options from the print UI that may affect the way
72 * content is laid out, for example page size, the application receives a callback
73 * allowing it to layout the content to better fit these new constraints. After a
74 * layout pass the system may ask the application to render one or more pages one
75 * or more times. For example, an application may produce a single column list for
76 * smaller page sizes and a multi-column table for larger page sizes.
77 * </p>
78 * <h3>Print jobs</h3>
79 * <p>
80 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter,
81 * PrintAttributes)} from an activity which results in bringing up the system print
82 * UI. Once the print UI is up, when the user changes a selected print option that
83 * affects the way content is laid out the system starts to interact with the
84 * application following the mechanics described the section above.
85 * </p>
86 * <p>
87 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link
88 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started},
89 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED
90 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link
91 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated
92 * system spooler until they are handled which is they are cancelled or completed.
93 * Active print jobs, ones that are not cancelled or completed, are considered failed
94 * if the device reboots as the new boot may be after a very long time. The user may
95 * choose to restart such print jobs. Once a print job is queued all relevant content
96 * is stored in the system spooler and its lifecycle becomes detached from this of
97 * the application that created it.
98 * </p>
99 * <p>
100 * An applications can query the print spooler for current print jobs it created
101 * but not print jobs created by other applications.
102 * </p>
103 *
104 * @see PrintJob
105 * @see PrintJobInfo
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700106 */
107public final class PrintManager {
108
109 private static final String LOG_TAG = "PrintManager";
110
Svetoslav15cbc8a2014-07-11 09:45:07 -0700111 private static final boolean DEBUG = false;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700112
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700113 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
114
115 /**
116 * The action for launching the print dialog activity.
117 *
118 * @hide
119 */
120 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
121
122 /**
123 * Extra with the intent for starting the print dialog.
124 * <p>
125 * <strong>Type:</strong> {@link android.content.IntentSender}
126 * </p>
127 *
128 * @hide
129 */
130 public static final String EXTRA_PRINT_DIALOG_INTENT =
131 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
132
133 /**
134 * Extra with a print job.
135 * <p>
136 * <strong>Type:</strong> {@link android.print.PrintJobInfo}
137 * </p>
138 *
139 * @hide
140 */
141 public static final String EXTRA_PRINT_JOB =
142 "android.print.intent.extra.EXTRA_PRINT_JOB";
143
144 /**
145 * Extra with the print document adapter to be printed.
146 * <p>
147 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
148 * </p>
149 *
150 * @hide
151 */
152 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
153 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700154
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700155 /** @hide */
156 public static final int APP_ID_ANY = -2;
157
158 private final Context mContext;
159
160 private final IPrintManager mService;
161
162 private final int mUserId;
163
164 private final int mAppId;
165
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700166 private final Handler mHandler;
167
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700168 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
169
170 /** @hide */
171 public interface PrintJobStateChangeListener {
172
173 /**
174 * Callback notifying that a print job state changed.
Svetoslav2235a172014-05-08 14:19:01 -0700175 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700176 * @param printJobId The print job id.
177 */
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700178 public void onPrintJobStateChanged(PrintJobId printJobId);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700179 }
180
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700181 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700182 * Creates a new instance.
Svetoslav2235a172014-05-08 14:19:01 -0700183 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700184 * @param context The current context in which to operate.
185 * @param service The backing system service.
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800186 * @param userId The user id in which to operate.
187 * @param appId The application id in which to operate.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700188 * @hide
189 */
190 public PrintManager(Context context, IPrintManager service, int userId, int appId) {
191 mContext = context;
192 mService = service;
193 mUserId = userId;
194 mAppId = appId;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700195 mHandler = new Handler(context.getMainLooper(), null, false) {
196 @Override
197 public void handleMessage(Message message) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700198 switch (message.what) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700199 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
200 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700201 PrintJobStateChangeListenerWrapper wrapper =
202 (PrintJobStateChangeListenerWrapper) args.arg1;
203 PrintJobStateChangeListener listener = wrapper.getListener();
204 if (listener != null) {
205 PrintJobId printJobId = (PrintJobId) args.arg2;
206 listener.onPrintJobStateChanged(printJobId);
207 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700208 args.recycle();
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700209 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700210 }
211 }
212 };
213 }
214
215 /**
216 * Creates an instance that can access all print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700217 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700218 * @param userId The user id for which to get all print jobs.
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700219 * @return An instance if the caller has the permission to access all print
220 * jobs, null otherwise.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700221 * @hide
222 */
223 public PrintManager getGlobalPrintManagerForUser(int userId) {
Svetoslav2235a172014-05-08 14:19:01 -0700224 if (mService == null) {
225 Log.w(LOG_TAG, "Feature android.software.print not available");
226 return null;
227 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700228 return new PrintManager(mContext, mService, userId, APP_ID_ANY);
229 }
230
Svetoslav2fbd2a72013-09-16 17:53:51 -0700231 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700232 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700233 return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700234 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700235 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700236 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700237 }
238
239 /**
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700240 * Adds a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700241 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700242 * @param listener The listener to add.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700243 * @hide
244 */
245 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700246 if (mService == null) {
247 Log.w(LOG_TAG, "Feature android.software.print not available");
248 return;
249 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700250 if (mPrintJobStateChangeListeners == null) {
251 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
252 PrintJobStateChangeListenerWrapper>();
253 }
254 PrintJobStateChangeListenerWrapper wrappedListener =
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700255 new PrintJobStateChangeListenerWrapper(listener, mHandler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700256 try {
257 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
258 mPrintJobStateChangeListeners.put(listener, wrappedListener);
259 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700260 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700261 }
262 }
263
264 /**
265 * Removes a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700266 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700267 * @param listener The listener to remove.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700268 * @hide
269 */
270 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700271 if (mService == null) {
272 Log.w(LOG_TAG, "Feature android.software.print not available");
273 return;
274 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700275 if (mPrintJobStateChangeListeners == null) {
276 return;
277 }
278 PrintJobStateChangeListenerWrapper wrappedListener =
279 mPrintJobStateChangeListeners.remove(listener);
280 if (wrappedListener == null) {
281 return;
282 }
283 if (mPrintJobStateChangeListeners.isEmpty()) {
284 mPrintJobStateChangeListeners = null;
285 }
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700286 wrappedListener.destroy();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700287 try {
288 mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
289 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700290 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700291 }
292 }
293
294 /**
295 * Gets a print job given its id.
Svetoslav2235a172014-05-08 14:19:01 -0700296 *
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800297 * @param printJobId The id of the print job.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700298 * @return The print job list.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700299 * @see PrintJob
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700300 * @hide
301 */
302 public PrintJob getPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700303 if (mService == null) {
304 Log.w(LOG_TAG, "Feature android.software.print not available");
305 return null;
306 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700307 try {
308 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
309 if (printJob != null) {
310 return new PrintJob(printJob, this);
311 }
312 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700313 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700314 }
315 return null;
316 }
317
318 /**
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800319 * Get the custom icon for a printer. If the icon is not cached, the icon is
320 * requested asynchronously. Once it is available the printer is updated.
321 *
322 * @param printerId the id of the printer the icon should be loaded for
323 * @return the custom icon to be used for the printer or null if the icon is
324 * not yet available
325 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
326 * @hide
327 */
328 public Icon getCustomPrinterIcon(PrinterId printerId) {
329 if (mService == null) {
330 Log.w(LOG_TAG, "Feature android.software.print not available");
331 return null;
332 }
333 try {
334 return mService.getCustomPrinterIcon(printerId, mUserId);
335 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700336 throw re.rethrowFromSystemServer();
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800337 }
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800338 }
339
340 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700341 * Gets the print jobs for this application.
Svetoslav2235a172014-05-08 14:19:01 -0700342 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700343 * @return The print job list.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700344 * @see PrintJob
345 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800346 public @NonNull List<PrintJob> getPrintJobs() {
Svetoslav2235a172014-05-08 14:19:01 -0700347 if (mService == null) {
348 Log.w(LOG_TAG, "Feature android.software.print not available");
349 return Collections.emptyList();
350 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700351 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700352 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700353 if (printJobInfos == null) {
354 return Collections.emptyList();
355 }
356 final int printJobCount = printJobInfos.size();
357 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
358 for (int i = 0; i < printJobCount; i++) {
359 printJobs.add(new PrintJob(printJobInfos.get(i), this));
360 }
361 return printJobs;
362 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700363 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700364 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700365 }
366
Svetoslav2fbd2a72013-09-16 17:53:51 -0700367 void cancelPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700368 if (mService == null) {
369 Log.w(LOG_TAG, "Feature android.software.print not available");
370 return;
371 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700372 try {
373 mService.cancelPrintJob(printJobId, mAppId, mUserId);
374 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700375 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700376 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700377 }
378
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700379 void restartPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700380 if (mService == null) {
381 Log.w(LOG_TAG, "Feature android.software.print not available");
382 return;
383 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700384 try {
385 mService.restartPrintJob(printJobId, mAppId, mUserId);
386 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700387 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700388 }
389 }
390
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700391 /**
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700392 * Creates a print job for printing a {@link PrintDocumentAdapter} with
393 * default print attributes.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700394 * <p>
395 * Calling this method brings the print UI allowing the user to customize
396 * the print job and returns a {@link PrintJob} object without waiting for the
397 * user to customize or confirm the print job. The returned print job instance
398 * is in a {@link PrintJobInfo#STATE_CREATED created} state.
399 * <p>
400 * This method can be called only from an {@link Activity}. The rationale is that
401 * printing from a service will create an inconsistent user experience as the print
402 * UI would appear without any context.
403 * </p>
404 * <p>
405 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
406 * your activity is finished. The rationale is that once the activity that
407 * initiated printing is finished, the provided adapter may be in an inconsistent
408 * state as it may depend on the UI presented by the activity.
409 * </p>
410 * <p>
411 * The default print attributes are a hint to the system how the data is to
412 * be printed. For example, a photo editor may look at the photo aspect ratio
413 * to determine the default orientation and provide a hint whether the printing
414 * should be in portrait or landscape. The system will do a best effort to
415 * selected the hinted options in the print dialog, given the current printer
416 * supports them.
417 * </p>
Svetoslav81f14b92014-03-18 10:37:20 -0700418 * <p>
419 * <strong>Note:</strong> Calling this method will bring the print dialog and
420 * the system will connect to the provided {@link PrintDocumentAdapter}. If a
Svetoslav85a85a02014-03-18 10:48:51 -0700421 * configuration change occurs that you application does not handle, for example
422 * a rotation change, the system will drop the connection to the adapter as the
423 * activity has to be recreated and the old adapter may be invalid in this context,
424 * hence a new adapter instance is required. As a consequence, if your activity
425 * does not handle configuration changes (default behavior), you have to save the
426 * state that you were printing and call this method again when your activity
427 * is recreated.
Svetoslav81f14b92014-03-18 10:37:20 -0700428 * </p>
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700429 *
430 * @param printJobName A name for the new print job which is shown to the user.
Svetoslav Ganova0027152013-06-25 14:59:53 -0700431 * @param documentAdapter An adapter that emits the document to print.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700432 * @param attributes The default print job attributes or <code>null</code>.
Svetoslav Ganovd26d4892013-08-28 14:37:54 -0700433 * @return The created print job on success or null on failure.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700434 * @throws IllegalStateException If not called from an {@link Activity}.
435 * @throws IllegalArgumentException If the print job name is empty or the
436 * document adapter is null.
437 *
Svetoslavfd906512013-06-24 09:04:48 -0700438 * @see PrintJob
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700439 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800440 public @NonNull PrintJob print(@NonNull String printJobName,
441 @NonNull PrintDocumentAdapter documentAdapter,
442 @Nullable PrintAttributes attributes) {
Svetoslav2235a172014-05-08 14:19:01 -0700443 if (mService == null) {
444 Log.w(LOG_TAG, "Feature android.software.print not available");
445 return null;
446 }
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700447 if (!(mContext instanceof Activity)) {
448 throw new IllegalStateException("Can print only from an activity");
449 }
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700450 if (TextUtils.isEmpty(printJobName)) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700451 throw new IllegalArgumentException("printJobName cannot be empty");
452 }
453 if (documentAdapter == null) {
454 throw new IllegalArgumentException("documentAdapter cannot be null");
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700455 }
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700456 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700457 (Activity) mContext, documentAdapter);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700458 try {
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700459 Bundle result = mService.print(printJobName, delegate,
460 attributes, mContext.getPackageName(), mAppId, mUserId);
461 if (result != null) {
462 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
463 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
464 if (printJob == null || intent == null) {
465 return null;
466 }
467 try {
468 mContext.startIntentSender(intent, null, 0, 0, 0);
469 return new PrintJob(printJob, this);
470 } catch (SendIntentException sie) {
471 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
472 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700473 }
474 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700475 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700476 }
477 return null;
478 }
479
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700480 /**
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700481 * Gets the list of enabled print services.
Svetoslav2235a172014-05-08 14:19:01 -0700482 *
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700483 * @return The enabled service list or an empty list.
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700484 * @hide
485 */
486 public List<PrintServiceInfo> getEnabledPrintServices() {
Svetoslav2235a172014-05-08 14:19:01 -0700487 if (mService == null) {
488 Log.w(LOG_TAG, "Feature android.software.print not available");
489 return Collections.emptyList();
490 }
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700491 try {
492 List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
493 if (enabledServices != null) {
494 return enabledServices;
495 }
496 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700497 throw re.rethrowFromSystemServer();
Svetoslavd8dbc132013-09-27 18:29:53 -0700498 }
499 return Collections.emptyList();
500 }
501
502 /**
503 * Gets the list of installed print services.
Svetoslav2235a172014-05-08 14:19:01 -0700504 *
Svetoslavd8dbc132013-09-27 18:29:53 -0700505 * @return The installed service list or an empty list.
Svetoslavd8dbc132013-09-27 18:29:53 -0700506 * @hide
507 */
508 public List<PrintServiceInfo> getInstalledPrintServices() {
Svetoslav2235a172014-05-08 14:19:01 -0700509 if (mService == null) {
510 Log.w(LOG_TAG, "Feature android.software.print not available");
511 return Collections.emptyList();
512 }
Svetoslavd8dbc132013-09-27 18:29:53 -0700513 try {
514 List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
515 if (installedServices != null) {
516 return installedServices;
517 }
518 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700519 throw re.rethrowFromSystemServer();
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700520 }
521 return Collections.emptyList();
522 }
523
524 /**
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700525 * @hide
526 */
527 public PrinterDiscoverySession createPrinterDiscoverySession() {
Svetoslav2235a172014-05-08 14:19:01 -0700528 if (mService == null) {
529 Log.w(LOG_TAG, "Feature android.software.print not available");
530 return null;
531 }
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700532 return new PrinterDiscoverySession(mService, mContext, mUserId);
533 }
534
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -0800535 /**
536 * @hide
537 */
538 public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700539 implements ActivityLifecycleCallbacks {
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700540 private final Object mLock = new Object();
541
Svetoslava798c0a2014-05-15 10:47:19 -0700542 private Activity mActivity; // Strong reference OK - cleared in destroy
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700543
Svetoslava798c0a2014-05-15 10:47:19 -0700544 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700545
Svetoslava798c0a2014-05-15 10:47:19 -0700546 private Handler mHandler; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700547
Svetoslava798c0a2014-05-15 10:47:19 -0700548 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700549
Svetoslava798c0a2014-05-15 10:47:19 -0700550 private DestroyableCallback mPendingCallback;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700551
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700552 public PrintDocumentAdapterDelegate(Activity activity,
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700553 PrintDocumentAdapter documentAdapter) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700554 mActivity = activity;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700555 mDocumentAdapter = documentAdapter;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700556 mHandler = new MyHandler(mActivity.getMainLooper());
557 mActivity.getApplication().registerActivityLifecycleCallbacks(this);
558 }
559
560 @Override
561 public void setObserver(IPrintDocumentAdapterObserver observer) {
562 final boolean destroyed;
563 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700564 mObserver = observer;
565 destroyed = isDestroyedLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700566 }
Svetoslava798c0a2014-05-15 10:47:19 -0700567
Svetoslav6552bf32014-09-03 21:15:55 -0700568 if (destroyed && observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700569 try {
570 observer.onDestroy();
571 } catch (RemoteException re) {
572 Log.e(LOG_TAG, "Error announcing destroyed state", re);
573 }
574 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700575 }
576
577 @Override
578 public void start() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700579 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700580 // If destroyed the handler is null.
581 if (!isDestroyedLocked()) {
582 mHandler.obtainMessage(MyHandler.MSG_ON_START,
583 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700584 }
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700585 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700586 }
587
588 @Override
Svetoslav62836082013-07-17 14:52:35 -0700589 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700590 ILayoutResultCallback callback, Bundle metadata, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700591
Svetoslava798c0a2014-05-15 10:47:19 -0700592 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
593 try {
594 callback.onLayoutStarted(cancellationTransport, sequence);
595 } catch (RemoteException re) {
596 // The spooler is dead - can't recover.
597 Log.e(LOG_TAG, "Error notifying for layout start", re);
598 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700599 }
Svetoslava798c0a2014-05-15 10:47:19 -0700600
601 synchronized (mLock) {
602 // If destroyed the handler is null.
603 if (isDestroyedLocked()) {
604 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700605 }
Svetoslava798c0a2014-05-15 10:47:19 -0700606
607 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
608 cancellationTransport);
609
610 SomeArgs args = SomeArgs.obtain();
611 args.arg1 = mDocumentAdapter;
612 args.arg2 = oldAttributes;
613 args.arg3 = newAttributes;
614 args.arg4 = cancellationSignal;
615 args.arg5 = new MyLayoutResultCallback(callback, sequence);
616 args.arg6 = metadata;
617
618 mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700619 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700620 }
621
622 @Override
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700623 public void write(PageRange[] pages, ParcelFileDescriptor fd,
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700624 IWriteResultCallback callback, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700625
Svetoslava798c0a2014-05-15 10:47:19 -0700626 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
627 try {
628 callback.onWriteStarted(cancellationTransport, sequence);
629 } catch (RemoteException re) {
630 // The spooler is dead - can't recover.
631 Log.e(LOG_TAG, "Error notifying for write start", re);
632 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700633 }
Svetoslava798c0a2014-05-15 10:47:19 -0700634
635 synchronized (mLock) {
636 // If destroyed the handler is null.
637 if (isDestroyedLocked()) {
638 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700639 }
Svetoslava798c0a2014-05-15 10:47:19 -0700640
641 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
642 cancellationTransport);
643
644 SomeArgs args = SomeArgs.obtain();
645 args.arg1 = mDocumentAdapter;
646 args.arg2 = pages;
647 args.arg3 = fd;
648 args.arg4 = cancellationSignal;
649 args.arg5 = new MyWriteResultCallback(callback, fd, sequence);
650
651 mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700652 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700653 }
654
655 @Override
656 public void finish() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700657 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700658 // If destroyed the handler is null.
659 if (!isDestroyedLocked()) {
660 mHandler.obtainMessage(MyHandler.MSG_ON_FINISH,
661 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700662 }
Svetoslavd270cb92013-10-31 14:27:16 -0700663 }
664 }
665
666 @Override
Svet Ganovfce84f02014-10-31 16:56:52 -0700667 public void kill(String reason) {
668 synchronized (mLock) {
669 // If destroyed the handler is null.
670 if (!isDestroyedLocked()) {
671 mHandler.obtainMessage(MyHandler.MSG_ON_KILL,
672 reason).sendToTarget();
673 }
674 }
675 }
676
677 @Override
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700678 public void onActivityPaused(Activity activity) {
679 /* do nothing */
680 }
681
682 @Override
683 public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
684 /* do nothing */
685 }
686
687 @Override
688 public void onActivityStarted(Activity activity) {
689 /* do nothing */
690 }
691
692 @Override
693 public void onActivityResumed(Activity activity) {
694 /* do nothing */
695 }
696
697 @Override
698 public void onActivityStopped(Activity activity) {
699 /* do nothing */
700 }
701
702 @Override
703 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
704 /* do nothing */
705 }
706
707 @Override
708 public void onActivityDestroyed(Activity activity) {
709 // We really care only if the activity is being destroyed to
710 // notify the the print spooler so it can close the print dialog.
711 // Note the the spooler has a death recipient that observes if
712 // this process gets killed so we cover the case of onDestroy not
713 // being called due to this process being killed to reclaim memory.
Svetoslava798c0a2014-05-15 10:47:19 -0700714 IPrintDocumentAdapterObserver observer = null;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700715 synchronized (mLock) {
716 if (activity == mActivity) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700717 observer = mObserver;
Svetoslava798c0a2014-05-15 10:47:19 -0700718 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700719 }
720 }
721 if (observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700722 try {
723 observer.onDestroy();
724 } catch (RemoteException re) {
725 Log.e(LOG_TAG, "Error announcing destroyed state", re);
726 }
727 }
728 }
729
Svetoslava798c0a2014-05-15 10:47:19 -0700730 private boolean isDestroyedLocked() {
731 return (mActivity == null);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700732 }
733
Svetoslava798c0a2014-05-15 10:47:19 -0700734 private void destroyLocked() {
735 mActivity.getApplication().unregisterActivityLifecycleCallbacks(
736 PrintDocumentAdapterDelegate.this);
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700737 mActivity = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700738
Svetoslav Ganova0027152013-06-25 14:59:53 -0700739 mDocumentAdapter = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700740
741 // This method is only called from the main thread, so
742 // clearing the messages guarantees that any time a
743 // message is handled we are not in a destroyed state.
744 mHandler.removeMessages(MyHandler.MSG_ON_START);
745 mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
746 mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
747 mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700748 mHandler = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700749
750 mObserver = null;
751
752 if (mPendingCallback != null) {
753 mPendingCallback.destroy();
754 mPendingCallback = null;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700755 }
756 }
757
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700758 private final class MyHandler extends Handler {
Svetoslava798c0a2014-05-15 10:47:19 -0700759 public static final int MSG_ON_START = 1;
760 public static final int MSG_ON_LAYOUT = 2;
761 public static final int MSG_ON_WRITE = 3;
762 public static final int MSG_ON_FINISH = 4;
Svet Ganovfce84f02014-10-31 16:56:52 -0700763 public static final int MSG_ON_KILL = 5;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700764
765 public MyHandler(Looper looper) {
766 super(looper, null, true);
767 }
768
769 @Override
770 public void handleMessage(Message message) {
771 switch (message.what) {
Svetoslava798c0a2014-05-15 10:47:19 -0700772 case MSG_ON_START: {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700773 if (DEBUG) {
Svetoslava798c0a2014-05-15 10:47:19 -0700774 Log.i(LOG_TAG, "onStart()");
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700775 }
Svetoslava798c0a2014-05-15 10:47:19 -0700776
777 ((PrintDocumentAdapter) message.obj).onStart();
778 } break;
779
780 case MSG_ON_LAYOUT: {
781 SomeArgs args = (SomeArgs) message.obj;
782 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
783 PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
784 PrintAttributes newAttributes = (PrintAttributes) args.arg3;
785 CancellationSignal cancellation = (CancellationSignal) args.arg4;
786 LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
787 Bundle metadata = (Bundle) args.arg6;
788 args.recycle();
789
790 if (DEBUG) {
791 StringBuilder builder = new StringBuilder();
792 builder.append("PrintDocumentAdapter#onLayout() {\n");
793 builder.append("\n oldAttributes:").append(oldAttributes);
794 builder.append("\n newAttributes:").append(newAttributes);
795 builder.append("\n preview:").append(metadata.getBoolean(
796 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
797 builder.append("\n}");
798 Log.i(LOG_TAG, builder.toString());
799 }
800
801 adapter.onLayout(oldAttributes, newAttributes, cancellation,
802 callback, metadata);
803 } break;
804
805 case MSG_ON_WRITE: {
806 SomeArgs args = (SomeArgs) message.obj;
807 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
808 PageRange[] pages = (PageRange[]) args.arg2;
809 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
810 CancellationSignal cancellation = (CancellationSignal) args.arg4;
811 WriteResultCallback callback = (WriteResultCallback) args.arg5;
812 args.recycle();
813
814 if (DEBUG) {
815 StringBuilder builder = new StringBuilder();
816 builder.append("PrintDocumentAdapter#onWrite() {\n");
817 builder.append("\n pages:").append(Arrays.toString(pages));
818 builder.append("\n}");
819 Log.i(LOG_TAG, builder.toString());
820 }
821
822 adapter.onWrite(pages, fd, cancellation, callback);
823 } break;
824
825 case MSG_ON_FINISH: {
826 if (DEBUG) {
827 Log.i(LOG_TAG, "onFinish()");
828 }
829
830 ((PrintDocumentAdapter) message.obj).onFinish();
831
832 // Done printing, so destroy this instance as it
833 // should not be used anymore.
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700834 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700835 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700836 }
837 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700838
Svet Ganovfce84f02014-10-31 16:56:52 -0700839 case MSG_ON_KILL: {
840 if (DEBUG) {
841 Log.i(LOG_TAG, "onKill()");
842 }
843
844 String reason = (String) message.obj;
845 throw new RuntimeException(reason);
846 }
847
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700848 default: {
849 throw new IllegalArgumentException("Unknown message: "
850 + message.what);
851 }
852 }
853 }
854 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700855
Svetoslava798c0a2014-05-15 10:47:19 -0700856 private interface DestroyableCallback {
857 public void destroy();
858 }
859
860 private final class MyLayoutResultCallback extends LayoutResultCallback
861 implements DestroyableCallback {
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700862 private ILayoutResultCallback mCallback;
863 private final int mSequence;
864
865 public MyLayoutResultCallback(ILayoutResultCallback callback,
866 int sequence) {
867 mCallback = callback;
868 mSequence = sequence;
869 }
870
871 @Override
872 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
873 final ILayoutResultCallback callback;
874 synchronized (mLock) {
875 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700876 }
Svetoslava798c0a2014-05-15 10:47:19 -0700877
878 // If the callback is null we are destroyed.
879 if (callback == null) {
880 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
881 + "finish the printing activity before print completion "
882 + "or did you invoke a callback after finish?");
883 return;
884 }
885
886 try {
887 if (info == null) {
888 throw new NullPointerException("document info cannot be null");
889 }
890
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700891 try {
892 callback.onLayoutFinished(info, changed, mSequence);
893 } catch (RemoteException re) {
894 Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
895 }
Svetoslava798c0a2014-05-15 10:47:19 -0700896 } finally {
897 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700898 }
899 }
900
901 @Override
902 public void onLayoutFailed(CharSequence error) {
903 final ILayoutResultCallback callback;
904 synchronized (mLock) {
905 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700906 }
Svetoslava798c0a2014-05-15 10:47:19 -0700907
908 // If the callback is null we are destroyed.
909 if (callback == null) {
910 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
911 + "finish the printing activity before print completion "
912 + "or did you invoke a callback after finish?");
913 return;
914 }
915
916 try {
917 callback.onLayoutFailed(error, mSequence);
918 } catch (RemoteException re) {
919 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
920 } finally {
921 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700922 }
923 }
924
925 @Override
926 public void onLayoutCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -0700927 final ILayoutResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700928 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700929 callback = mCallback;
930 }
931
932 // If the callback is null we are destroyed.
933 if (callback == null) {
934 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
935 + "finish the printing activity before print completion "
936 + "or did you invoke a callback after finish?");
937 return;
938 }
939
940 try {
941 callback.onLayoutCanceled(mSequence);
942 } catch (RemoteException re) {
943 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
944 } finally {
945 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700946 }
947 }
948
Svetoslava798c0a2014-05-15 10:47:19 -0700949 @Override
950 public void destroy() {
951 synchronized (mLock) {
952 mCallback = null;
953 mPendingCallback = null;
954 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700955 }
956 }
957
Svetoslava798c0a2014-05-15 10:47:19 -0700958 private final class MyWriteResultCallback extends WriteResultCallback
959 implements DestroyableCallback {
Svetoslav Ganovd26d4892013-08-28 14:37:54 -0700960 private ParcelFileDescriptor mFd;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700961 private IWriteResultCallback mCallback;
Svetoslava798c0a2014-05-15 10:47:19 -0700962 private final int mSequence;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700963
964 public MyWriteResultCallback(IWriteResultCallback callback,
Svetoslav Ganovd26d4892013-08-28 14:37:54 -0700965 ParcelFileDescriptor fd, int sequence) {
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700966 mFd = fd;
967 mSequence = sequence;
968 mCallback = callback;
969 }
970
971 @Override
972 public void onWriteFinished(PageRange[] pages) {
973 final IWriteResultCallback callback;
974 synchronized (mLock) {
975 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700976 }
Svetoslava798c0a2014-05-15 10:47:19 -0700977
978 // If the callback is null we are destroyed.
979 if (callback == null) {
980 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
981 + "finish the printing activity before print completion "
982 + "or did you invoke a callback after finish?");
983 return;
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700984 }
Svetoslava798c0a2014-05-15 10:47:19 -0700985
986 try {
987 if (pages == null) {
988 throw new IllegalArgumentException("pages cannot be null");
989 }
990 if (pages.length == 0) {
991 throw new IllegalArgumentException("pages cannot be empty");
992 }
993
Svetoslav Ganov14db9652013-08-06 14:40:46 -0700994 try {
995 callback.onWriteFinished(pages, mSequence);
996 } catch (RemoteException re) {
997 Log.e(LOG_TAG, "Error calling onWriteFinished", re);
998 }
Svetoslava798c0a2014-05-15 10:47:19 -0700999 } finally {
1000 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001001 }
1002 }
1003
1004 @Override
1005 public void onWriteFailed(CharSequence error) {
1006 final IWriteResultCallback callback;
1007 synchronized (mLock) {
1008 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001009 }
Svetoslava798c0a2014-05-15 10:47:19 -07001010
1011 // If the callback is null we are destroyed.
1012 if (callback == null) {
1013 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1014 + "finish the printing activity before print completion "
1015 + "or did you invoke a callback after finish?");
1016 return;
1017 }
1018
1019 try {
1020 callback.onWriteFailed(error, mSequence);
1021 } catch (RemoteException re) {
1022 Log.e(LOG_TAG, "Error calling onWriteFailed", re);
1023 } finally {
1024 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001025 }
1026 }
1027
1028 @Override
1029 public void onWriteCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -07001030 final IWriteResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001031 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001032 callback = mCallback;
1033 }
1034
1035 // If the callback is null we are destroyed.
1036 if (callback == null) {
1037 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1038 + "finish the printing activity before print completion "
1039 + "or did you invoke a callback after finish?");
1040 return;
1041 }
1042
1043 try {
1044 callback.onWriteCanceled(mSequence);
1045 } catch (RemoteException re) {
1046 Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
1047 } finally {
1048 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001049 }
1050 }
1051
Svetoslava798c0a2014-05-15 10:47:19 -07001052 @Override
1053 public void destroy() {
1054 synchronized (mLock) {
1055 IoUtils.closeQuietly(mFd);
1056 mCallback = null;
1057 mFd = null;
1058 mPendingCallback = null;
1059 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001060 }
1061 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001062 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001063
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -08001064 /**
1065 * @hide
1066 */
1067 public static final class PrintJobStateChangeListenerWrapper extends
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001068 IPrintJobStateChangeListener.Stub {
1069 private final WeakReference<PrintJobStateChangeListener> mWeakListener;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001070 private final WeakReference<Handler> mWeakHandler;
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001071
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001072 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1073 Handler handler) {
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001074 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001075 mWeakHandler = new WeakReference<Handler>(handler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001076 }
1077
1078 @Override
1079 public void onPrintJobStateChanged(PrintJobId printJobId) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001080 Handler handler = mWeakHandler.get();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001081 PrintJobStateChangeListener listener = mWeakListener.get();
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001082 if (handler != null && listener != null) {
1083 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001084 args.arg1 = this;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001085 args.arg2 = printJobId;
1086 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1087 args).sendToTarget();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001088 }
1089 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001090
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001091 public void destroy() {
1092 mWeakListener.clear();
1093 }
1094
1095 public PrintJobStateChangeListener getListener() {
1096 return mWeakListener.get();
1097 }
1098 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001099}