blob: 71f0bd615206391f05480da899bf313e14734cb4 [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;
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -070039import android.printservice.recommendation.IRecommendationsChangeListener;
40import android.printservice.recommendation.RecommendationInfo;
Svetoslav Ganov88d19912013-07-22 12:32:03 -070041import android.text.TextUtils;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070042import android.util.ArrayMap;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070043import android.util.Log;
44
45import com.android.internal.os.SomeArgs;
46
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -070047import com.android.internal.util.Preconditions;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070048import libcore.io.IoUtils;
49
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070050import java.lang.ref.WeakReference;
51import java.util.ArrayList;
Svetoslava798c0a2014-05-15 10:47:19 -070052import java.util.Arrays;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070053import java.util.Collections;
54import java.util.List;
Svetoslav Ganov704697b2013-09-21 20:30:24 -070055import java.util.Map;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070056
57/**
58 * System level service for accessing the printing capabilities of the platform.
59 * <p>
60 * To obtain a handle to the print manager do the following:
61 * </p>
Svetoslava798c0a2014-05-15 10:47:19 -070062 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070063 * <pre>
64 * PrintManager printManager =
65 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
66 * </pre>
Svetoslava798c0a2014-05-15 10:47:19 -070067 *
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -070068 * <h3>Print mechanics</h3>
69 * <p>
70 * The key idea behind printing on the platform is that the content to be printed
71 * should be laid out for the currently selected print options resulting in an
72 * optimized output and higher user satisfaction. To achieve this goal the platform
73 * declares a contract that the printing application has to follow which is defined
74 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that
75 * when the user selects some options from the print UI that may affect the way
76 * content is laid out, for example page size, the application receives a callback
77 * allowing it to layout the content to better fit these new constraints. After a
78 * layout pass the system may ask the application to render one or more pages one
79 * or more times. For example, an application may produce a single column list for
80 * smaller page sizes and a multi-column table for larger page sizes.
81 * </p>
82 * <h3>Print jobs</h3>
83 * <p>
84 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter,
85 * PrintAttributes)} from an activity which results in bringing up the system print
86 * UI. Once the print UI is up, when the user changes a selected print option that
87 * affects the way content is laid out the system starts to interact with the
88 * application following the mechanics described the section above.
89 * </p>
90 * <p>
91 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link
92 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started},
93 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED
94 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link
95 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated
96 * system spooler until they are handled which is they are cancelled or completed.
97 * Active print jobs, ones that are not cancelled or completed, are considered failed
98 * if the device reboots as the new boot may be after a very long time. The user may
99 * choose to restart such print jobs. Once a print job is queued all relevant content
100 * is stored in the system spooler and its lifecycle becomes detached from this of
101 * the application that created it.
102 * </p>
103 * <p>
104 * An applications can query the print spooler for current print jobs it created
105 * but not print jobs created by other applications.
106 * </p>
107 *
108 * @see PrintJob
109 * @see PrintJobInfo
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700110 */
111public final class PrintManager {
112
113 private static final String LOG_TAG = "PrintManager";
114
Svetoslav15cbc8a2014-07-11 09:45:07 -0700115 private static final boolean DEBUG = false;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700116
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700117 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800118 private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2;
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700119 private static final int MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED = 3;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800120
121 /**
122 * Package name of print spooler.
123 *
124 * @hide
125 */
126 public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
127
128 /**
129 * Select enabled services.
130 * </p>
131 * @see #getPrintServices
132 * @hide
133 */
134 public static final int ENABLED_SERVICES = 1 << 0;
135
136 /**
137 * Select disabled services.
138 * </p>
139 * @see #getPrintServices
140 * @hide
141 */
142 public static final int DISABLED_SERVICES = 1 << 1;
143
144 /**
145 * Select all services.
146 * </p>
147 * @see #getPrintServices
148 * @hide
149 */
150 public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES;
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700151
152 /**
153 * The action for launching the print dialog activity.
154 *
155 * @hide
156 */
157 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
158
159 /**
160 * Extra with the intent for starting the print dialog.
161 * <p>
162 * <strong>Type:</strong> {@link android.content.IntentSender}
163 * </p>
164 *
165 * @hide
166 */
167 public static final String EXTRA_PRINT_DIALOG_INTENT =
168 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
169
170 /**
171 * Extra with a print job.
172 * <p>
173 * <strong>Type:</strong> {@link android.print.PrintJobInfo}
174 * </p>
175 *
176 * @hide
177 */
178 public static final String EXTRA_PRINT_JOB =
179 "android.print.intent.extra.EXTRA_PRINT_JOB";
180
181 /**
182 * Extra with the print document adapter to be printed.
183 * <p>
184 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
185 * </p>
186 *
187 * @hide
188 */
189 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
190 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700191
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700192 /** @hide */
193 public static final int APP_ID_ANY = -2;
194
195 private final Context mContext;
196
197 private final IPrintManager mService;
198
199 private final int mUserId;
200
201 private final int mAppId;
202
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700203 private final Handler mHandler;
204
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800205 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper>
206 mPrintJobStateChangeListeners;
207 private Map<PrintServicesChangeListener, PrintServicesChangeListenerWrapper>
208 mPrintServicesChangeListeners;
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700209 private Map<PrintServiceRecommendationsChangeListener,
210 PrintServiceRecommendationsChangeListenerWrapper>
211 mPrintServiceRecommendationsChangeListeners;
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700212
213 /** @hide */
214 public interface PrintJobStateChangeListener {
215
216 /**
217 * Callback notifying that a print job state changed.
Svetoslav2235a172014-05-08 14:19:01 -0700218 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700219 * @param printJobId The print job id.
220 */
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700221 public void onPrintJobStateChanged(PrintJobId printJobId);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700222 }
223
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800224 /** @hide */
225 public interface PrintServicesChangeListener {
226
227 /**
228 * Callback notifying that the print services changed.
229 */
230 public void onPrintServicesChanged();
231 }
232
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700233 /** @hide */
234 public interface PrintServiceRecommendationsChangeListener {
235
236 /**
237 * Callback notifying that the print service recommendations changed.
238 */
239 void onPrintServiceRecommendationsChanged();
240 }
241
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700242 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700243 * Creates a new instance.
Svetoslav2235a172014-05-08 14:19:01 -0700244 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700245 * @param context The current context in which to operate.
246 * @param service The backing system service.
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800247 * @param userId The user id in which to operate.
248 * @param appId The application id in which to operate.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700249 * @hide
250 */
251 public PrintManager(Context context, IPrintManager service, int userId, int appId) {
252 mContext = context;
253 mService = service;
254 mUserId = userId;
255 mAppId = appId;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700256 mHandler = new Handler(context.getMainLooper(), null, false) {
257 @Override
258 public void handleMessage(Message message) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700259 switch (message.what) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700260 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
261 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700262 PrintJobStateChangeListenerWrapper wrapper =
263 (PrintJobStateChangeListenerWrapper) args.arg1;
264 PrintJobStateChangeListener listener = wrapper.getListener();
265 if (listener != null) {
266 PrintJobId printJobId = (PrintJobId) args.arg2;
267 listener.onPrintJobStateChanged(printJobId);
268 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700269 args.recycle();
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700270 } break;
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800271 case MSG_NOTIFY_PRINT_SERVICES_CHANGED: {
272 PrintServicesChangeListenerWrapper wrapper =
273 (PrintServicesChangeListenerWrapper) message.obj;
274 PrintServicesChangeListener listener = wrapper.getListener();
275 if (listener != null) {
276 listener.onPrintServicesChanged();
277 }
278 } break;
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700279 case MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED: {
280 PrintServiceRecommendationsChangeListenerWrapper wrapper =
281 (PrintServiceRecommendationsChangeListenerWrapper) message.obj;
282 PrintServiceRecommendationsChangeListener listener = wrapper.getListener();
283 if (listener != null) {
284 listener.onPrintServiceRecommendationsChanged();
285 }
286 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700287 }
288 }
289 };
290 }
291
292 /**
293 * Creates an instance that can access all print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700294 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700295 * @param userId The user id for which to get all print jobs.
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700296 * @return An instance if the caller has the permission to access all print
297 * jobs, null otherwise.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700298 * @hide
299 */
300 public PrintManager getGlobalPrintManagerForUser(int userId) {
Svetoslav2235a172014-05-08 14:19:01 -0700301 if (mService == null) {
302 Log.w(LOG_TAG, "Feature android.software.print not available");
303 return null;
304 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700305 return new PrintManager(mContext, mService, userId, APP_ID_ANY);
306 }
307
Svetoslav2fbd2a72013-09-16 17:53:51 -0700308 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700309 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700310 return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700311 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700312 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700313 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700314 }
315
316 /**
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700317 * Adds a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700318 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700319 * @param listener The listener to add.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700320 * @hide
321 */
322 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700323 if (mService == null) {
324 Log.w(LOG_TAG, "Feature android.software.print not available");
325 return;
326 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700327 if (mPrintJobStateChangeListeners == null) {
328 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
329 PrintJobStateChangeListenerWrapper>();
330 }
331 PrintJobStateChangeListenerWrapper wrappedListener =
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700332 new PrintJobStateChangeListenerWrapper(listener, mHandler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700333 try {
334 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
335 mPrintJobStateChangeListeners.put(listener, wrappedListener);
336 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700337 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700338 }
339 }
340
341 /**
342 * Removes a listener for observing the state of print jobs.
Svetoslav2235a172014-05-08 14:19:01 -0700343 *
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700344 * @param listener The listener to remove.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700345 * @hide
346 */
347 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
Svetoslav2235a172014-05-08 14:19:01 -0700348 if (mService == null) {
349 Log.w(LOG_TAG, "Feature android.software.print not available");
350 return;
351 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700352 if (mPrintJobStateChangeListeners == null) {
353 return;
354 }
355 PrintJobStateChangeListenerWrapper wrappedListener =
356 mPrintJobStateChangeListeners.remove(listener);
357 if (wrappedListener == null) {
358 return;
359 }
360 if (mPrintJobStateChangeListeners.isEmpty()) {
361 mPrintJobStateChangeListeners = null;
362 }
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -0700363 wrappedListener.destroy();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700364 try {
365 mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
366 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700367 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700368 }
369 }
370
371 /**
372 * Gets a print job given its id.
Svetoslav2235a172014-05-08 14:19:01 -0700373 *
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800374 * @param printJobId The id of the print job.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700375 * @return The print job list.
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700376 * @see PrintJob
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700377 * @hide
378 */
379 public PrintJob getPrintJob(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 null;
383 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700384 try {
385 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
386 if (printJob != null) {
387 return new PrintJob(printJob, this);
388 }
389 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700390 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700391 }
392 return null;
393 }
394
395 /**
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800396 * Get the custom icon for a printer. If the icon is not cached, the icon is
397 * requested asynchronously. Once it is available the printer is updated.
398 *
399 * @param printerId the id of the printer the icon should be loaded for
400 * @return the custom icon to be used for the printer or null if the icon is
401 * not yet available
402 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
403 * @hide
404 */
405 public Icon getCustomPrinterIcon(PrinterId printerId) {
406 if (mService == null) {
407 Log.w(LOG_TAG, "Feature android.software.print not available");
408 return null;
409 }
410 try {
411 return mService.getCustomPrinterIcon(printerId, mUserId);
412 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700413 throw re.rethrowFromSystemServer();
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800414 }
Philip P. Moltmannbb9f6862015-12-01 14:44:24 -0800415 }
416
417 /**
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700418 * Gets the print jobs for this application.
Svetoslav2235a172014-05-08 14:19:01 -0700419 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700420 * @return The print job list.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700421 * @see PrintJob
422 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800423 public @NonNull List<PrintJob> getPrintJobs() {
Svetoslav2235a172014-05-08 14:19:01 -0700424 if (mService == null) {
425 Log.w(LOG_TAG, "Feature android.software.print not available");
426 return Collections.emptyList();
427 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700428 try {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700429 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700430 if (printJobInfos == null) {
431 return Collections.emptyList();
432 }
433 final int printJobCount = printJobInfos.size();
434 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
435 for (int i = 0; i < printJobCount; i++) {
436 printJobs.add(new PrintJob(printJobInfos.get(i), this));
437 }
438 return printJobs;
439 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700440 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700441 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700442 }
443
Svetoslav2fbd2a72013-09-16 17:53:51 -0700444 void cancelPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700445 if (mService == null) {
446 Log.w(LOG_TAG, "Feature android.software.print not available");
447 return;
448 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700449 try {
450 mService.cancelPrintJob(printJobId, mAppId, mUserId);
451 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700452 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700453 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700454 }
455
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700456 void restartPrintJob(PrintJobId printJobId) {
Svetoslav2235a172014-05-08 14:19:01 -0700457 if (mService == null) {
458 Log.w(LOG_TAG, "Feature android.software.print not available");
459 return;
460 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700461 try {
462 mService.restartPrintJob(printJobId, mAppId, mUserId);
463 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700464 throw re.rethrowFromSystemServer();
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700465 }
466 }
467
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700468 /**
Svetoslav Ganova18661d2013-10-09 22:55:49 -0700469 * Creates a print job for printing a {@link PrintDocumentAdapter} with
470 * default print attributes.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700471 * <p>
472 * Calling this method brings the print UI allowing the user to customize
473 * the print job and returns a {@link PrintJob} object without waiting for the
474 * user to customize or confirm the print job. The returned print job instance
475 * is in a {@link PrintJobInfo#STATE_CREATED created} state.
476 * <p>
477 * This method can be called only from an {@link Activity}. The rationale is that
478 * printing from a service will create an inconsistent user experience as the print
479 * UI would appear without any context.
480 * </p>
481 * <p>
482 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
483 * your activity is finished. The rationale is that once the activity that
484 * initiated printing is finished, the provided adapter may be in an inconsistent
485 * state as it may depend on the UI presented by the activity.
486 * </p>
487 * <p>
488 * The default print attributes are a hint to the system how the data is to
489 * be printed. For example, a photo editor may look at the photo aspect ratio
490 * to determine the default orientation and provide a hint whether the printing
491 * should be in portrait or landscape. The system will do a best effort to
492 * selected the hinted options in the print dialog, given the current printer
493 * supports them.
494 * </p>
Svetoslav81f14b92014-03-18 10:37:20 -0700495 * <p>
496 * <strong>Note:</strong> Calling this method will bring the print dialog and
497 * the system will connect to the provided {@link PrintDocumentAdapter}. If a
Svetoslav85a85a02014-03-18 10:48:51 -0700498 * configuration change occurs that you application does not handle, for example
499 * a rotation change, the system will drop the connection to the adapter as the
500 * activity has to be recreated and the old adapter may be invalid in this context,
501 * hence a new adapter instance is required. As a consequence, if your activity
502 * does not handle configuration changes (default behavior), you have to save the
503 * state that you were printing and call this method again when your activity
504 * is recreated.
Svetoslav81f14b92014-03-18 10:37:20 -0700505 * </p>
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700506 *
507 * @param printJobName A name for the new print job which is shown to the user.
Svetoslav Ganova0027152013-06-25 14:59:53 -0700508 * @param documentAdapter An adapter that emits the document to print.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700509 * @param attributes The default print job attributes or <code>null</code>.
Svetoslav Ganovd26d4892013-08-28 14:37:54 -0700510 * @return The created print job on success or null on failure.
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700511 * @throws IllegalStateException If not called from an {@link Activity}.
512 * @throws IllegalArgumentException If the print job name is empty or the
513 * document adapter is null.
514 *
Svetoslavfd906512013-06-24 09:04:48 -0700515 * @see PrintJob
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700516 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800517 public @NonNull PrintJob print(@NonNull String printJobName,
518 @NonNull PrintDocumentAdapter documentAdapter,
519 @Nullable PrintAttributes attributes) {
Svetoslav2235a172014-05-08 14:19:01 -0700520 if (mService == null) {
521 Log.w(LOG_TAG, "Feature android.software.print not available");
522 return null;
523 }
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700524 if (!(mContext instanceof Activity)) {
525 throw new IllegalStateException("Can print only from an activity");
526 }
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700527 if (TextUtils.isEmpty(printJobName)) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700528 throw new IllegalArgumentException("printJobName cannot be empty");
529 }
530 if (documentAdapter == null) {
531 throw new IllegalArgumentException("documentAdapter cannot be null");
Svetoslav Ganov88d19912013-07-22 12:32:03 -0700532 }
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700533 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700534 (Activity) mContext, documentAdapter);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700535 try {
Svetoslav7bfbbcb2013-10-10 13:36:23 -0700536 Bundle result = mService.print(printJobName, delegate,
537 attributes, mContext.getPackageName(), mAppId, mUserId);
538 if (result != null) {
539 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
540 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
541 if (printJob == null || intent == null) {
542 return null;
543 }
544 try {
545 mContext.startIntentSender(intent, null, 0, 0, 0);
546 return new PrintJob(printJob, this);
547 } catch (SendIntentException sie) {
548 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
549 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700550 }
551 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700552 throw re.rethrowFromSystemServer();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700553 }
554 return null;
555 }
556
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700557 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800558 * Listen for changes to the installed and enabled print services.
Svetoslav2235a172014-05-08 14:19:01 -0700559 *
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800560 * @param listener the listener to add
561 *
562 * @see android.print.PrintManager#getPrintServices
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700563 */
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800564 void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700565 Preconditions.checkNotNull(listener);
566
Svetoslav2235a172014-05-08 14:19:01 -0700567 if (mService == null) {
568 Log.w(LOG_TAG, "Feature android.software.print not available");
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800569 return;
Svetoslav2235a172014-05-08 14:19:01 -0700570 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800571 if (mPrintServicesChangeListeners == null) {
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700572 mPrintServicesChangeListeners = new ArrayMap<>();
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800573 }
574 PrintServicesChangeListenerWrapper wrappedListener =
575 new PrintServicesChangeListenerWrapper(listener, mHandler);
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700576 try {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800577 mService.addPrintServicesChangeListener(wrappedListener, mUserId);
578 mPrintServicesChangeListeners.put(listener, wrappedListener);
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700579 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700580 throw re.rethrowFromSystemServer();
Svetoslavd8dbc132013-09-27 18:29:53 -0700581 }
Svetoslavd8dbc132013-09-27 18:29:53 -0700582 }
583
584 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800585 * Stop listening for changes to the installed and enabled print services.
Svetoslav2235a172014-05-08 14:19:01 -0700586 *
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800587 * @param listener the listener to remove
588 *
589 * @see android.print.PrintManager#getPrintServices
Svetoslavd8dbc132013-09-27 18:29:53 -0700590 */
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800591 void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700592 Preconditions.checkNotNull(listener);
593
Svetoslav2235a172014-05-08 14:19:01 -0700594 if (mService == null) {
595 Log.w(LOG_TAG, "Feature android.software.print not available");
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800596 return;
Svetoslav2235a172014-05-08 14:19:01 -0700597 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800598 if (mPrintServicesChangeListeners == null) {
599 return;
600 }
601 PrintServicesChangeListenerWrapper wrappedListener =
602 mPrintServicesChangeListeners.remove(listener);
603 if (wrappedListener == null) {
604 return;
605 }
606 if (mPrintServicesChangeListeners.isEmpty()) {
607 mPrintServicesChangeListeners = null;
608 }
609 wrappedListener.destroy();
Svetoslavd8dbc132013-09-27 18:29:53 -0700610 try {
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800611 mService.removePrintServicesChangeListener(wrappedListener, mUserId);
612 } catch (RemoteException re) {
613 Log.e(LOG_TAG, "Error removing print services change listener", re);
614 }
615 }
616
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800617 /**
618 * Gets the list of print services, but does not register for updates. The user has to register
619 * for updates by itself, or use {@link PrintServicesLoader}.
620 *
621 * @param selectionFlags flags selecting which services to get. Either
622 * {@link #ENABLED_SERVICES},{@link #DISABLED_SERVICES}, or both.
623 *
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700624 * @return The print service list or an empty list.
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800625 *
626 * @see #addPrintServicesChangeListener(PrintServicesChangeListener)
627 * @see #removePrintServicesChangeListener(PrintServicesChangeListener)
628 *
629 * @hide
630 */
631 public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) {
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700632 Preconditions.checkFlagsArgument(selectionFlags, ALL_SERVICES);
633
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800634 try {
635 List<PrintServiceInfo> services = mService.getPrintServices(selectionFlags, mUserId);
636 if (services != null) {
637 return services;
Svetoslavd8dbc132013-09-27 18:29:53 -0700638 }
639 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700640 throw re.rethrowFromSystemServer();
Svetoslav Ganov860f8a62013-09-14 00:59:03 -0700641 }
642 return Collections.emptyList();
643 }
644
645 /**
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -0700646 * Listen for changes to the print service recommendations.
647 *
648 * @param listener the listener to add
649 *
650 * @see android.print.PrintManager#getPrintServiceRecommendations
651 */
652 void addPrintServiceRecommendationsChangeListener(
653 @NonNull PrintServiceRecommendationsChangeListener listener) {
654 Preconditions.checkNotNull(listener);
655
656 if (mService == null) {
657 Log.w(LOG_TAG, "Feature android.software.print not available");
658 return;
659 }
660 if (mPrintServiceRecommendationsChangeListeners == null) {
661 mPrintServiceRecommendationsChangeListeners = new ArrayMap<>();
662 }
663 PrintServiceRecommendationsChangeListenerWrapper wrappedListener =
664 new PrintServiceRecommendationsChangeListenerWrapper(listener, mHandler);
665 try {
666 mService.addPrintServiceRecommendationsChangeListener(wrappedListener, mUserId);
667 mPrintServiceRecommendationsChangeListeners.put(listener, wrappedListener);
668 } catch (RemoteException re) {
669 throw re.rethrowFromSystemServer();
670 }
671 }
672
673 /**
674 * Stop listening for changes to the print service recommendations.
675 *
676 * @param listener the listener to remove
677 *
678 * @see android.print.PrintManager#getPrintServiceRecommendations
679 */
680 void removePrintServiceRecommendationsChangeListener(
681 @NonNull PrintServiceRecommendationsChangeListener listener) {
682 Preconditions.checkNotNull(listener);
683
684 if (mService == null) {
685 Log.w(LOG_TAG, "Feature android.software.print not available");
686 return;
687 }
688 if (mPrintServiceRecommendationsChangeListeners == null) {
689 return;
690 }
691 PrintServiceRecommendationsChangeListenerWrapper wrappedListener =
692 mPrintServiceRecommendationsChangeListeners.remove(listener);
693 if (wrappedListener == null) {
694 return;
695 }
696 if (mPrintServiceRecommendationsChangeListeners.isEmpty()) {
697 mPrintServiceRecommendationsChangeListeners = null;
698 }
699 wrappedListener.destroy();
700 try {
701 mService.removePrintServiceRecommendationsChangeListener(wrappedListener, mUserId);
702 } catch (RemoteException re) {
703 throw re.rethrowFromSystemServer();
704 }
705 }
706
707 /**
708 * Gets the list of print service recommendations, but does not register for updates. The user
709 * has to register for updates by itself, or use {@link PrintServiceRecommendationsLoader}.
710 *
711 * @return The print service recommendations list or an empty list.
712 *
713 * @see #addPrintServiceRecommendationsChangeListener
714 * @see #removePrintServiceRecommendationsChangeListener
715 *
716 * @hide
717 */
718 public @NonNull List<RecommendationInfo> getPrintServiceRecommendations() {
719 try {
720 List<RecommendationInfo> recommendations =
721 mService.getPrintServiceRecommendations(mUserId);
722 if (recommendations != null) {
723 return recommendations;
724 }
725 } catch (RemoteException re) {
726 throw re.rethrowFromSystemServer();
727 }
728 return Collections.emptyList();
729 }
730
731 /**
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700732 * @hide
733 */
734 public PrinterDiscoverySession createPrinterDiscoverySession() {
Svetoslav2235a172014-05-08 14:19:01 -0700735 if (mService == null) {
736 Log.w(LOG_TAG, "Feature android.software.print not available");
737 return null;
738 }
Svetoslav Ganov44720af2013-08-20 16:32:53 -0700739 return new PrinterDiscoverySession(mService, mContext, mUserId);
740 }
741
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -0800742 /**
Philip P. Moltmann66c96592016-02-24 11:32:43 -0800743 * Enable or disable a print service.
744 *
745 * @param service The service to enabled or disable
746 * @param isEnabled whether the service should be enabled or disabled
747 *
748 * @hide
749 */
750 public void setPrintServiceEnabled(@NonNull ComponentName service, boolean isEnabled) {
751 if (mService == null) {
752 Log.w(LOG_TAG, "Feature android.software.print not available");
753 return;
754 }
755 try {
756 mService.setPrintServiceEnabled(service, isEnabled, mUserId);
757 } catch (RemoteException re) {
758 Log.e(LOG_TAG, "Error enabling or disabling " + service, re);
759 }
760 }
761
762 /**
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -0800763 * @hide
764 */
765 public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700766 implements ActivityLifecycleCallbacks {
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700767 private final Object mLock = new Object();
768
Svetoslava798c0a2014-05-15 10:47:19 -0700769 private Activity mActivity; // Strong reference OK - cleared in destroy
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700770
Svetoslava798c0a2014-05-15 10:47:19 -0700771 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700772
Svetoslava798c0a2014-05-15 10:47:19 -0700773 private Handler mHandler; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700774
Svetoslava798c0a2014-05-15 10:47:19 -0700775 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700776
Svetoslava798c0a2014-05-15 10:47:19 -0700777 private DestroyableCallback mPendingCallback;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700778
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700779 public PrintDocumentAdapterDelegate(Activity activity,
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700780 PrintDocumentAdapter documentAdapter) {
Svetoslav Ganov4d4c66d2013-10-24 18:04:39 -0700781 mActivity = activity;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700782 mDocumentAdapter = documentAdapter;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700783 mHandler = new MyHandler(mActivity.getMainLooper());
784 mActivity.getApplication().registerActivityLifecycleCallbacks(this);
785 }
786
787 @Override
788 public void setObserver(IPrintDocumentAdapterObserver observer) {
789 final boolean destroyed;
790 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700791 mObserver = observer;
792 destroyed = isDestroyedLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700793 }
Svetoslava798c0a2014-05-15 10:47:19 -0700794
Svetoslav6552bf32014-09-03 21:15:55 -0700795 if (destroyed && observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700796 try {
797 observer.onDestroy();
798 } catch (RemoteException re) {
799 Log.e(LOG_TAG, "Error announcing destroyed state", re);
800 }
801 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700802 }
803
804 @Override
805 public void start() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700806 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700807 // If destroyed the handler is null.
808 if (!isDestroyedLocked()) {
809 mHandler.obtainMessage(MyHandler.MSG_ON_START,
810 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700811 }
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700812 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700813 }
814
815 @Override
Svetoslav62836082013-07-17 14:52:35 -0700816 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700817 ILayoutResultCallback callback, Bundle metadata, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700818
Svetoslava798c0a2014-05-15 10:47:19 -0700819 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
820 try {
821 callback.onLayoutStarted(cancellationTransport, sequence);
822 } catch (RemoteException re) {
823 // The spooler is dead - can't recover.
824 Log.e(LOG_TAG, "Error notifying for layout start", re);
825 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700826 }
Svetoslava798c0a2014-05-15 10:47:19 -0700827
828 synchronized (mLock) {
829 // If destroyed the handler is null.
830 if (isDestroyedLocked()) {
831 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700832 }
Svetoslava798c0a2014-05-15 10:47:19 -0700833
834 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
835 cancellationTransport);
836
837 SomeArgs args = SomeArgs.obtain();
838 args.arg1 = mDocumentAdapter;
839 args.arg2 = oldAttributes;
840 args.arg3 = newAttributes;
841 args.arg4 = cancellationSignal;
842 args.arg5 = new MyLayoutResultCallback(callback, sequence);
843 args.arg6 = metadata;
844
845 mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700846 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700847 }
848
849 @Override
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700850 public void write(PageRange[] pages, ParcelFileDescriptor fd,
Svetoslav Ganov704697b2013-09-21 20:30:24 -0700851 IWriteResultCallback callback, int sequence) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700852
Svetoslava798c0a2014-05-15 10:47:19 -0700853 ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
854 try {
855 callback.onWriteStarted(cancellationTransport, sequence);
856 } catch (RemoteException re) {
857 // The spooler is dead - can't recover.
858 Log.e(LOG_TAG, "Error notifying for write start", re);
859 return;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700860 }
Svetoslava798c0a2014-05-15 10:47:19 -0700861
862 synchronized (mLock) {
863 // If destroyed the handler is null.
864 if (isDestroyedLocked()) {
865 return;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700866 }
Svetoslava798c0a2014-05-15 10:47:19 -0700867
868 CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
869 cancellationTransport);
870
871 SomeArgs args = SomeArgs.obtain();
872 args.arg1 = mDocumentAdapter;
873 args.arg2 = pages;
874 args.arg3 = fd;
875 args.arg4 = cancellationSignal;
876 args.arg5 = new MyWriteResultCallback(callback, fd, sequence);
877
878 mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget();
Svetoslav Ganov85b1f882013-07-24 17:00:06 -0700879 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700880 }
881
882 @Override
883 public void finish() {
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700884 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -0700885 // If destroyed the handler is null.
886 if (!isDestroyedLocked()) {
887 mHandler.obtainMessage(MyHandler.MSG_ON_FINISH,
888 mDocumentAdapter).sendToTarget();
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700889 }
Svetoslavd270cb92013-10-31 14:27:16 -0700890 }
891 }
892
893 @Override
Svet Ganovfce84f02014-10-31 16:56:52 -0700894 public void kill(String reason) {
895 synchronized (mLock) {
896 // If destroyed the handler is null.
897 if (!isDestroyedLocked()) {
898 mHandler.obtainMessage(MyHandler.MSG_ON_KILL,
899 reason).sendToTarget();
900 }
901 }
902 }
903
904 @Override
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700905 public void onActivityPaused(Activity activity) {
906 /* do nothing */
907 }
908
909 @Override
910 public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
911 /* do nothing */
912 }
913
914 @Override
915 public void onActivityStarted(Activity activity) {
916 /* do nothing */
917 }
918
919 @Override
920 public void onActivityResumed(Activity activity) {
921 /* do nothing */
922 }
923
924 @Override
925 public void onActivityStopped(Activity activity) {
926 /* do nothing */
927 }
928
929 @Override
930 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
931 /* do nothing */
932 }
933
934 @Override
935 public void onActivityDestroyed(Activity activity) {
936 // We really care only if the activity is being destroyed to
937 // notify the the print spooler so it can close the print dialog.
938 // Note the the spooler has a death recipient that observes if
939 // this process gets killed so we cover the case of onDestroy not
940 // being called due to this process being killed to reclaim memory.
Svetoslava798c0a2014-05-15 10:47:19 -0700941 IPrintDocumentAdapterObserver observer = null;
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700942 synchronized (mLock) {
943 if (activity == mActivity) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700944 observer = mObserver;
Svetoslava798c0a2014-05-15 10:47:19 -0700945 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700946 }
947 }
948 if (observer != null) {
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700949 try {
950 observer.onDestroy();
951 } catch (RemoteException re) {
952 Log.e(LOG_TAG, "Error announcing destroyed state", re);
953 }
954 }
955 }
956
Svetoslava798c0a2014-05-15 10:47:19 -0700957 private boolean isDestroyedLocked() {
958 return (mActivity == null);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700959 }
960
Svetoslava798c0a2014-05-15 10:47:19 -0700961 private void destroyLocked() {
962 mActivity.getApplication().unregisterActivityLifecycleCallbacks(
963 PrintDocumentAdapterDelegate.this);
Svetoslav Ganov858a1852013-10-17 22:20:40 -0700964 mActivity = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700965
Svetoslav Ganova0027152013-06-25 14:59:53 -0700966 mDocumentAdapter = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700967
968 // This method is only called from the main thread, so
969 // clearing the messages guarantees that any time a
970 // message is handled we are not in a destroyed state.
971 mHandler.removeMessages(MyHandler.MSG_ON_START);
972 mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
973 mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
974 mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700975 mHandler = null;
Svetoslava798c0a2014-05-15 10:47:19 -0700976
977 mObserver = null;
978
979 if (mPendingCallback != null) {
980 mPendingCallback.destroy();
981 mPendingCallback = null;
Svetoslav Ganovdb636772013-10-07 14:31:18 -0700982 }
983 }
984
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700985 private final class MyHandler extends Handler {
Svetoslava798c0a2014-05-15 10:47:19 -0700986 public static final int MSG_ON_START = 1;
987 public static final int MSG_ON_LAYOUT = 2;
988 public static final int MSG_ON_WRITE = 3;
989 public static final int MSG_ON_FINISH = 4;
Svet Ganovfce84f02014-10-31 16:56:52 -0700990 public static final int MSG_ON_KILL = 5;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700991
992 public MyHandler(Looper looper) {
993 super(looper, null, true);
994 }
995
996 @Override
997 public void handleMessage(Message message) {
998 switch (message.what) {
Svetoslava798c0a2014-05-15 10:47:19 -0700999 case MSG_ON_START: {
Svetoslav Ganovdb636772013-10-07 14:31:18 -07001000 if (DEBUG) {
Svetoslava798c0a2014-05-15 10:47:19 -07001001 Log.i(LOG_TAG, "onStart()");
Svetoslav Ganovdb636772013-10-07 14:31:18 -07001002 }
Svetoslava798c0a2014-05-15 10:47:19 -07001003
1004 ((PrintDocumentAdapter) message.obj).onStart();
1005 } break;
1006
1007 case MSG_ON_LAYOUT: {
1008 SomeArgs args = (SomeArgs) message.obj;
1009 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
1010 PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
1011 PrintAttributes newAttributes = (PrintAttributes) args.arg3;
1012 CancellationSignal cancellation = (CancellationSignal) args.arg4;
1013 LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
1014 Bundle metadata = (Bundle) args.arg6;
1015 args.recycle();
1016
1017 if (DEBUG) {
1018 StringBuilder builder = new StringBuilder();
1019 builder.append("PrintDocumentAdapter#onLayout() {\n");
1020 builder.append("\n oldAttributes:").append(oldAttributes);
1021 builder.append("\n newAttributes:").append(newAttributes);
1022 builder.append("\n preview:").append(metadata.getBoolean(
1023 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
1024 builder.append("\n}");
1025 Log.i(LOG_TAG, builder.toString());
1026 }
1027
1028 adapter.onLayout(oldAttributes, newAttributes, cancellation,
1029 callback, metadata);
1030 } break;
1031
1032 case MSG_ON_WRITE: {
1033 SomeArgs args = (SomeArgs) message.obj;
1034 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
1035 PageRange[] pages = (PageRange[]) args.arg2;
1036 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
1037 CancellationSignal cancellation = (CancellationSignal) args.arg4;
1038 WriteResultCallback callback = (WriteResultCallback) args.arg5;
1039 args.recycle();
1040
1041 if (DEBUG) {
1042 StringBuilder builder = new StringBuilder();
1043 builder.append("PrintDocumentAdapter#onWrite() {\n");
1044 builder.append("\n pages:").append(Arrays.toString(pages));
1045 builder.append("\n}");
1046 Log.i(LOG_TAG, builder.toString());
1047 }
1048
1049 adapter.onWrite(pages, fd, cancellation, callback);
1050 } break;
1051
1052 case MSG_ON_FINISH: {
1053 if (DEBUG) {
1054 Log.i(LOG_TAG, "onFinish()");
1055 }
1056
1057 ((PrintDocumentAdapter) message.obj).onFinish();
1058
1059 // Done printing, so destroy this instance as it
1060 // should not be used anymore.
Svetoslav Ganov858a1852013-10-17 22:20:40 -07001061 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001062 destroyLocked();
Svetoslav Ganov858a1852013-10-17 22:20:40 -07001063 }
1064 } break;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001065
Svet Ganovfce84f02014-10-31 16:56:52 -07001066 case MSG_ON_KILL: {
1067 if (DEBUG) {
1068 Log.i(LOG_TAG, "onKill()");
1069 }
1070
1071 String reason = (String) message.obj;
1072 throw new RuntimeException(reason);
1073 }
1074
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001075 default: {
1076 throw new IllegalArgumentException("Unknown message: "
1077 + message.what);
1078 }
1079 }
1080 }
1081 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001082
Svetoslava798c0a2014-05-15 10:47:19 -07001083 private interface DestroyableCallback {
1084 public void destroy();
1085 }
1086
1087 private final class MyLayoutResultCallback extends LayoutResultCallback
1088 implements DestroyableCallback {
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001089 private ILayoutResultCallback mCallback;
1090 private final int mSequence;
1091
1092 public MyLayoutResultCallback(ILayoutResultCallback callback,
1093 int sequence) {
1094 mCallback = callback;
1095 mSequence = sequence;
1096 }
1097
1098 @Override
1099 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
1100 final ILayoutResultCallback callback;
1101 synchronized (mLock) {
1102 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001103 }
Svetoslava798c0a2014-05-15 10:47:19 -07001104
1105 // If the callback is null we are destroyed.
1106 if (callback == null) {
1107 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1108 + "finish the printing activity before print completion "
1109 + "or did you invoke a callback after finish?");
1110 return;
1111 }
1112
1113 try {
1114 if (info == null) {
1115 throw new NullPointerException("document info cannot be null");
1116 }
1117
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001118 try {
1119 callback.onLayoutFinished(info, changed, mSequence);
1120 } catch (RemoteException re) {
1121 Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
1122 }
Svetoslava798c0a2014-05-15 10:47:19 -07001123 } finally {
1124 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001125 }
1126 }
1127
1128 @Override
1129 public void onLayoutFailed(CharSequence error) {
1130 final ILayoutResultCallback callback;
1131 synchronized (mLock) {
1132 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001133 }
Svetoslava798c0a2014-05-15 10:47:19 -07001134
1135 // If the callback is null we are destroyed.
1136 if (callback == null) {
1137 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1138 + "finish the printing activity before print completion "
1139 + "or did you invoke a callback after finish?");
1140 return;
1141 }
1142
1143 try {
1144 callback.onLayoutFailed(error, mSequence);
1145 } catch (RemoteException re) {
1146 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1147 } finally {
1148 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001149 }
1150 }
1151
1152 @Override
1153 public void onLayoutCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -07001154 final ILayoutResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001155 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001156 callback = mCallback;
1157 }
1158
1159 // If the callback is null we are destroyed.
1160 if (callback == null) {
1161 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1162 + "finish the printing activity before print completion "
1163 + "or did you invoke a callback after finish?");
1164 return;
1165 }
1166
1167 try {
1168 callback.onLayoutCanceled(mSequence);
1169 } catch (RemoteException re) {
1170 Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1171 } finally {
1172 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001173 }
1174 }
1175
Svetoslava798c0a2014-05-15 10:47:19 -07001176 @Override
1177 public void destroy() {
1178 synchronized (mLock) {
1179 mCallback = null;
1180 mPendingCallback = null;
1181 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001182 }
1183 }
1184
Svetoslava798c0a2014-05-15 10:47:19 -07001185 private final class MyWriteResultCallback extends WriteResultCallback
1186 implements DestroyableCallback {
Svetoslav Ganovd26d4892013-08-28 14:37:54 -07001187 private ParcelFileDescriptor mFd;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001188 private IWriteResultCallback mCallback;
Svetoslava798c0a2014-05-15 10:47:19 -07001189 private final int mSequence;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001190
1191 public MyWriteResultCallback(IWriteResultCallback callback,
Svetoslav Ganovd26d4892013-08-28 14:37:54 -07001192 ParcelFileDescriptor fd, int sequence) {
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001193 mFd = fd;
1194 mSequence = sequence;
1195 mCallback = callback;
1196 }
1197
1198 @Override
1199 public void onWriteFinished(PageRange[] pages) {
1200 final IWriteResultCallback callback;
1201 synchronized (mLock) {
1202 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001203 }
Svetoslava798c0a2014-05-15 10:47:19 -07001204
1205 // If the callback is null we are destroyed.
1206 if (callback == null) {
1207 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1208 + "finish the printing activity before print completion "
1209 + "or did you invoke a callback after finish?");
1210 return;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001211 }
Svetoslava798c0a2014-05-15 10:47:19 -07001212
1213 try {
1214 if (pages == null) {
1215 throw new IllegalArgumentException("pages cannot be null");
1216 }
1217 if (pages.length == 0) {
1218 throw new IllegalArgumentException("pages cannot be empty");
1219 }
1220
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001221 try {
1222 callback.onWriteFinished(pages, mSequence);
1223 } catch (RemoteException re) {
1224 Log.e(LOG_TAG, "Error calling onWriteFinished", re);
1225 }
Svetoslava798c0a2014-05-15 10:47:19 -07001226 } finally {
1227 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001228 }
1229 }
1230
1231 @Override
1232 public void onWriteFailed(CharSequence error) {
1233 final IWriteResultCallback callback;
1234 synchronized (mLock) {
1235 callback = mCallback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001236 }
Svetoslava798c0a2014-05-15 10:47:19 -07001237
1238 // If the callback is null we are destroyed.
1239 if (callback == null) {
1240 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1241 + "finish the printing activity before print completion "
1242 + "or did you invoke a callback after finish?");
1243 return;
1244 }
1245
1246 try {
1247 callback.onWriteFailed(error, mSequence);
1248 } catch (RemoteException re) {
1249 Log.e(LOG_TAG, "Error calling onWriteFailed", re);
1250 } finally {
1251 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001252 }
1253 }
1254
1255 @Override
1256 public void onWriteCancelled() {
Svetoslava798c0a2014-05-15 10:47:19 -07001257 final IWriteResultCallback callback;
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001258 synchronized (mLock) {
Svetoslava798c0a2014-05-15 10:47:19 -07001259 callback = mCallback;
1260 }
1261
1262 // If the callback is null we are destroyed.
1263 if (callback == null) {
1264 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1265 + "finish the printing activity before print completion "
1266 + "or did you invoke a callback after finish?");
1267 return;
1268 }
1269
1270 try {
1271 callback.onWriteCanceled(mSequence);
1272 } catch (RemoteException re) {
1273 Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
1274 } finally {
1275 destroy();
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001276 }
1277 }
1278
Svetoslava798c0a2014-05-15 10:47:19 -07001279 @Override
1280 public void destroy() {
1281 synchronized (mLock) {
1282 IoUtils.closeQuietly(mFd);
1283 mCallback = null;
1284 mFd = null;
1285 mPendingCallback = null;
1286 }
Svetoslav Ganov14db9652013-08-06 14:40:46 -07001287 }
1288 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001289 }
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001290
Philip P. Moltmann76d7e3e2016-01-15 13:22:13 -08001291 /**
1292 * @hide
1293 */
1294 public static final class PrintJobStateChangeListenerWrapper extends
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001295 IPrintJobStateChangeListener.Stub {
1296 private final WeakReference<PrintJobStateChangeListener> mWeakListener;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001297 private final WeakReference<Handler> mWeakHandler;
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001298
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001299 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1300 Handler handler) {
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001301 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001302 mWeakHandler = new WeakReference<Handler>(handler);
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001303 }
1304
1305 @Override
1306 public void onPrintJobStateChanged(PrintJobId printJobId) {
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001307 Handler handler = mWeakHandler.get();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001308 PrintJobStateChangeListener listener = mWeakListener.get();
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001309 if (handler != null && listener != null) {
1310 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001311 args.arg1 = this;
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001312 args.arg2 = printJobId;
1313 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1314 args).sendToTarget();
Svetoslav Ganov704697b2013-09-21 20:30:24 -07001315 }
1316 }
Svetoslav Ganova18661d2013-10-09 22:55:49 -07001317
Svetoslav Ganovd91cb3e2013-10-12 15:44:42 -07001318 public void destroy() {
1319 mWeakListener.clear();
1320 }
1321
1322 public PrintJobStateChangeListener getListener() {
1323 return mWeakListener.get();
1324 }
1325 }
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001326
1327 /**
1328 * @hide
1329 */
1330 public static final class PrintServicesChangeListenerWrapper extends
1331 IPrintServicesChangeListener.Stub {
1332 private final WeakReference<PrintServicesChangeListener> mWeakListener;
1333 private final WeakReference<Handler> mWeakHandler;
1334
1335 public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener,
1336 Handler handler) {
1337 mWeakListener = new WeakReference<>(listener);
1338 mWeakHandler = new WeakReference<>(handler);
1339 }
1340
1341 @Override
1342 public void onPrintServicesChanged() {
1343 Handler handler = mWeakHandler.get();
1344 PrintServicesChangeListener listener = mWeakListener.get();
1345 if (handler != null && listener != null) {
1346 handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget();
1347 }
1348 }
1349
1350 public void destroy() {
1351 mWeakListener.clear();
1352 }
1353
1354 public PrintServicesChangeListener getListener() {
1355 return mWeakListener.get();
1356 }
1357 }
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -07001358
1359 /**
1360 * @hide
1361 */
1362 public static final class PrintServiceRecommendationsChangeListenerWrapper extends
1363 IRecommendationsChangeListener.Stub {
1364 private final WeakReference<PrintServiceRecommendationsChangeListener> mWeakListener;
1365 private final WeakReference<Handler> mWeakHandler;
1366
1367 public PrintServiceRecommendationsChangeListenerWrapper(
1368 PrintServiceRecommendationsChangeListener listener, Handler handler) {
1369 mWeakListener = new WeakReference<>(listener);
1370 mWeakHandler = new WeakReference<>(handler);
1371 }
1372
1373 @Override
1374 public void onRecommendationsChanged() {
1375 Handler handler = mWeakHandler.get();
1376 PrintServiceRecommendationsChangeListener listener = mWeakListener.get();
1377 if (handler != null && listener != null) {
1378 handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED,
1379 this).sendToTarget();
1380 }
1381 }
1382
1383 public void destroy() {
1384 mWeakListener.clear();
1385 }
1386
1387 public PrintServiceRecommendationsChangeListener getListener() {
1388 return mWeakListener.get();
1389 }
1390 }
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001391}