blob: 71869323fc086ea603b8de92675dc2373dcfe2e7 [file] [log] [blame]
Svetoslav Ganova0027152013-06-25 14:59:53 -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 com.android.printspooler;
18
19import android.os.AsyncTask;
Svetoslav62836082013-07-17 14:52:35 -070020import android.os.Bundle;
Svetoslav Ganova0027152013-06-25 14:59:53 -070021import android.os.ICancellationSignal;
22import android.os.ParcelFileDescriptor;
23import android.os.RemoteException;
24import android.print.ILayoutResultCallback;
25import android.print.IPrintDocumentAdapter;
26import android.print.IWriteResultCallback;
27import android.print.PageRange;
28import android.print.PrintAttributes;
29import android.print.PrintDocumentAdapter.LayoutResultCallback;
30import android.print.PrintDocumentAdapter.WriteResultCallback;
31import android.print.PrintDocumentInfo;
32import android.util.Log;
33import android.util.Slog;
34
Svetoslav Ganova0027152013-06-25 14:59:53 -070035import java.io.File;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.OutputStream;
41import java.util.ArrayList;
42import java.util.List;
43
Svetoslav62836082013-07-17 14:52:35 -070044import libcore.io.IoUtils;
45
Svetoslav Ganova0027152013-06-25 14:59:53 -070046/**
47 * This class represents a remote print document adapter instance.
48 */
49final class RemotePrintDocumentAdapter {
50 private static final String LOG_TAG = "RemotePrintDocumentAdapter";
51
52 private static final boolean DEBUG = true;
53
54 public static final int STATE_INITIALIZED = 0;
55 public static final int STATE_START_COMPLETED = 1;
56 public static final int STATE_LAYOUT_COMPLETED = 2;
57 public static final int STATE_WRITE_COMPLETED = 3;
58 public static final int STATE_FINISH_COMPLETED = 4;
59
60 private final Object mLock = new Object();
61
62 private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>();
63
64 private final IPrintDocumentAdapter mRemoteInterface;
65
66 private final File mFile;
67
68 private int mState = STATE_INITIALIZED;
69
70 public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
71 mRemoteInterface = printAdatper;
72 mFile = file;
73 }
74
75 public File getFile() {
76 if (DEBUG) {
77 Log.i(LOG_TAG, "getFile()");
78 }
79 synchronized (mLock) {
80 if (mState < STATE_WRITE_COMPLETED) {
81 throw new IllegalStateException("Write not completed");
82 }
83 return mFile;
84 }
85 }
86
87 public void cancel() {
88 synchronized (mLock) {
89 final int taskCount = mTaskQueue.size();
90 for (int i = 0; i < taskCount; i++) {
91 mTaskQueue.remove(i).cancel();
92 }
93 }
94 }
95
96 public void start() {
97 QueuedAsyncTask task = new QueuedAsyncTask() {
98 @Override
99 protected Void doInBackground(Void... params) {
100 if (DEBUG) {
101 Log.i(LOG_TAG, "start()");
102 }
103 synchronized (mLock) {
104 if (mState != STATE_INITIALIZED) {
105 throw new IllegalStateException("Invalid state: " + mState);
106 }
107 }
108 try {
109 mRemoteInterface.start();
110 synchronized (mLock) {
111 mState = STATE_START_COMPLETED;
112 }
113 } catch (RemoteException re) {
114 Log.e(LOG_TAG, "Error reading file", re);
115 }
116 return null;
117 }
118 };
119 synchronized (mLock) {
120 mTaskQueue.add(task);
121 task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
122 }
123 }
124
125 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
Svetoslav62836082013-07-17 14:52:35 -0700126 LayoutResultCallback callback, Bundle metadata) {
127 LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback,
128 metadata);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700129 synchronized (mLock) {
130 mTaskQueue.add(task);
131 task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
132 }
133 }
134
135 public void write(List<PageRange> pages, WriteResultCallback callback) {
136 WriteAsyncTask task = new WriteAsyncTask(pages, callback);
137 mTaskQueue.add(task);
138 task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
139 }
140
141 public void finish() {
142 QueuedAsyncTask task = new QueuedAsyncTask() {
143 @Override
144 protected Void doInBackground(Void... params) {
145 if (DEBUG) {
146 Log.i(LOG_TAG, "finish");
147 }
148 synchronized (mLock) {
149 if (mState != STATE_LAYOUT_COMPLETED
150 && mState != STATE_WRITE_COMPLETED) {
151 throw new IllegalStateException("Invalid state: " + mState);
152 }
153 }
154 try {
155 mRemoteInterface.finish();
156 synchronized (mLock) {
157 mState = STATE_FINISH_COMPLETED;
158 }
159 } catch (RemoteException re) {
160 Log.e(LOG_TAG, "Error reading file", re);
161 mState = STATE_INITIALIZED;
162 }
163 return null;
164 }
165 };
166 synchronized (mLock) {
167 mTaskQueue.add(task);
168 task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
169 }
170 }
171
172 private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> {
173 public void cancel() {
174 super.cancel(true);
175 }
176 }
177
178 private final class LayoutAsyncTask extends QueuedAsyncTask {
179
180 private final PrintAttributes mOldAttributes;
181
182 private final PrintAttributes mNewAttributes;
183
184 private final LayoutResultCallback mCallback;
185
Svetoslav62836082013-07-17 14:52:35 -0700186 private final Bundle mMetadata;
187
Svetoslav Ganova0027152013-06-25 14:59:53 -0700188 private final ILayoutResultCallback mILayoutResultCallback =
189 new ILayoutResultCallback.Stub() {
190 @Override
191 public void onLayoutStarted(ICancellationSignal cancellationSignal) {
192 synchronized (mLock) {
193 mCancellationSignal = cancellationSignal;
194 if (isCancelled()) {
195 cancelSignalQuietlyLocked();
196 }
197 }
198 }
199
200 @Override
201 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
202 synchronized (mLock) {
203 mCancellationSignal = null;
204 mCompleted = true;
205 mLock.notifyAll();
206 }
207 mCallback.onLayoutFinished(info, changed);
208 }
209
210 @Override
211 public void onLayoutFailed(CharSequence error) {
212 synchronized (mLock) {
213 mCancellationSignal = null;
214 mCompleted = true;
215 mLock.notifyAll();
216 }
217 Slog.e(LOG_TAG, "Error laying out print document: " + error);
218 mCallback.onLayoutFailed(error);
219 }
220 };
221
222 private ICancellationSignal mCancellationSignal;
223
224 private boolean mCompleted;
225
Svetoslav62836082013-07-17 14:52:35 -0700226 public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes,
227 LayoutResultCallback callback, Bundle metadata) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700228 mOldAttributes = oldAttributes;
229 mNewAttributes = newAttributes;
230 mCallback = callback;
Svetoslav62836082013-07-17 14:52:35 -0700231 mMetadata = metadata;
Svetoslav Ganova0027152013-06-25 14:59:53 -0700232 }
233
234 @Override
235 public void cancel() {
236 synchronized (mLock) {
237 throwIfCancelledLocked();
238 cancelSignalQuietlyLocked();
239 }
240 super.cancel();
241 }
242
243 @Override
244 protected Void doInBackground(Void... params) {
245 synchronized (mLock) {
246 if (mState != STATE_START_COMPLETED
247 && mState != STATE_LAYOUT_COMPLETED
248 && mState != STATE_WRITE_COMPLETED) {
249 throw new IllegalStateException("Invalid state: " + mState);
250 }
251 }
252 try {
253 mRemoteInterface.layout(mOldAttributes, mNewAttributes,
Svetoslav62836082013-07-17 14:52:35 -0700254 mILayoutResultCallback, mMetadata);
Svetoslav Ganova0027152013-06-25 14:59:53 -0700255 synchronized (mLock) {
256 while (true) {
257 if (isCancelled()) {
258 mState = STATE_INITIALIZED;
259 mTaskQueue.remove(this);
260 break;
261 }
262 if (mCompleted) {
263 mState = STATE_LAYOUT_COMPLETED;
264 mTaskQueue.remove(this);
265 break;
266 }
267 try {
268 mLock.wait();
269 } catch (InterruptedException ie) {
270 /* ignore */
271 }
272 }
273 }
274 } catch (RemoteException re) {
275 Slog.e(LOG_TAG, "Error calling layout", re);
276 mState = STATE_INITIALIZED;
277 }
278 return null;
279 }
280
281 private void cancelSignalQuietlyLocked() {
282 if (mCancellationSignal != null) {
283 try {
284 mCancellationSignal.cancel();
285 } catch (RemoteException re) {
286 Slog.e(LOG_TAG, "Error cancelling layout", re);
287 }
288 }
289 }
290
291 private void throwIfCancelledLocked() {
292 if (isCancelled()) {
293 throw new IllegalStateException("Already cancelled");
294 }
295 }
296 }
297
298 private final class WriteAsyncTask extends QueuedAsyncTask {
299
300 private final List<PageRange> mPages;
301
302 private final WriteResultCallback mCallback;
303
304 private final IWriteResultCallback mIWriteResultCallback =
305 new IWriteResultCallback.Stub() {
306 @Override
307 public void onWriteStarted(ICancellationSignal cancellationSignal) {
308 synchronized (mLock) {
309 mCancellationSignal = cancellationSignal;
310 if (isCancelled()) {
311 cancelSignalQuietlyLocked();
312 }
313 }
314 }
315
316 @Override
317 public void onWriteFinished(List<PageRange> pages) {
318 synchronized (mLock) {
319 mCancellationSignal = null;
320 mCompleted = true;
321 mLock.notifyAll();
322 }
323 mCallback.onWriteFinished(pages);
324 }
325
326 @Override
327 public void onWriteFailed(CharSequence error) {
328 synchronized (mLock) {
329 mCancellationSignal = null;
330 mCompleted = true;
331 mLock.notifyAll();
332 }
333 Slog.e(LOG_TAG, "Error writing print document: " + error);
334 mCallback.onWriteFailed(error);
335 }
336 };
337
338 private ICancellationSignal mCancellationSignal;
339
340 private boolean mCompleted;
341
342 private Thread mWriteThread;
343
344 public WriteAsyncTask(List<PageRange> pages, WriteResultCallback callback) {
345 mPages = pages;
346 mCallback = callback;
347 }
348
349 @Override
350 public void cancel() {
351 synchronized (mLock) {
352 throwIfCancelledLocked();
353 cancelSignalQuietlyLocked();
354 mWriteThread.interrupt();
355 }
356 super.cancel();
357 }
358
359 @Override
360 protected Void doInBackground(Void... params) {
361 if (DEBUG) {
362 Log.i(LOG_TAG, "print()");
363 }
364 synchronized (mLock) {
365 if (mState != STATE_LAYOUT_COMPLETED) {
366 throw new IllegalStateException("Invalid state: " + mState);
367 }
368 }
369 InputStream in = null;
370 OutputStream out = null;
371 ParcelFileDescriptor source = null;
372 ParcelFileDescriptor sink = null;
373 synchronized (mLock) {
374 mWriteThread = Thread.currentThread();
375 }
376 try {
377 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
378 source = pipe[0];
379 sink = pipe[1];
380
381 in = new FileInputStream(source.getFileDescriptor());
382 out = new FileOutputStream(mFile);
383
384 // Async call to initiate the other process writing the data.
385 mRemoteInterface.write(mPages, sink, mIWriteResultCallback);
386
387 // Close the source. It is now held by the client.
388 sink.close();
389 sink = null;
390
391 final byte[] buffer = new byte[8192];
392 while (true) {
393 if (Thread.currentThread().isInterrupted()) {
394 Thread.currentThread().interrupt();
395 break;
396 }
397 final int readByteCount = in.read(buffer);
398 if (readByteCount < 0) {
399 break;
400 }
401 out.write(buffer, 0, readByteCount);
402 }
403 synchronized (mLock) {
404 while (true) {
405 if (isCancelled()) {
406 mState = STATE_INITIALIZED;
407 mTaskQueue.remove(this);
408 break;
409 }
410 if (mCompleted) {
411 mState = STATE_WRITE_COMPLETED;
412 mTaskQueue.remove(this);
413 break;
414 }
415 try {
416 mLock.wait();
417 } catch (InterruptedException ie) {
418 /* ignore */
419 }
420 }
421 }
422 } catch (RemoteException re) {
423 Slog.e(LOG_TAG, "Error writing print document", re);
424 mState = STATE_INITIALIZED;
425 } catch (IOException ioe) {
426 Slog.e(LOG_TAG, "Error writing print document", ioe);
427 mState = STATE_INITIALIZED;
428 } finally {
429 IoUtils.closeQuietly(in);
430 IoUtils.closeQuietly(out);
431 IoUtils.closeQuietly(sink);
432 IoUtils.closeQuietly(source);
433 }
434 return null;
435 }
436
437 private void cancelSignalQuietlyLocked() {
438 if (mCancellationSignal != null) {
439 try {
440 mCancellationSignal.cancel();
441 } catch (RemoteException re) {
442 Slog.e(LOG_TAG, "Error cancelling layout", re);
443 }
444 }
445 }
446
447 private void throwIfCancelledLocked() {
448 if (isCancelled()) {
449 throw new IllegalStateException("Already cancelled");
450 }
451 }
452 }
453}