blob: bdbfdd9b0ac4be39dd1cf9fbe207102c1c521826 [file] [log] [blame]
Svet Ganov525a66b2014-06-14 22:29:00 -07001/*
2 * Copyright (C) 2014 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.model;
18
19import android.app.ActivityManager;
Svet Ganov13f542c2014-08-29 15:35:49 -070020import android.content.ComponentName;
Svet Ganov525a66b2014-06-14 22:29:00 -070021import android.content.Context;
Svet Ganov13f542c2014-08-29 15:35:49 -070022import android.content.Intent;
23import android.content.ServiceConnection;
Svet Ganov525a66b2014-06-14 22:29:00 -070024import android.graphics.Bitmap;
25import android.graphics.Color;
Svet Ganov525a66b2014-06-14 22:29:00 -070026import android.graphics.drawable.BitmapDrawable;
Svet Ganov525a66b2014-06-14 22:29:00 -070027import android.os.AsyncTask;
Svet Ganov13f542c2014-08-29 15:35:49 -070028import android.os.IBinder;
Svet Ganov525a66b2014-06-14 22:29:00 -070029import android.os.ParcelFileDescriptor;
Svet Ganov13f542c2014-08-29 15:35:49 -070030import android.os.RemoteException;
31import android.print.PrintAttributes;
Svet Ganov525a66b2014-06-14 22:29:00 -070032import android.print.PrintDocumentInfo;
33import android.util.ArrayMap;
34import android.util.Log;
35import android.view.View;
Svet Ganov13f542c2014-08-29 15:35:49 -070036import com.android.internal.annotations.GuardedBy;
37import com.android.printspooler.renderer.IPdfRenderer;
38import com.android.printspooler.renderer.PdfRendererService;
Svet Ganovdf644492014-08-29 15:35:49 -070039import com.android.printspooler.util.BitmapSerializeUtils;
Svet Ganov525a66b2014-06-14 22:29:00 -070040import dalvik.system.CloseGuard;
Svet Ganov13f542c2014-08-29 15:35:49 -070041import libcore.io.IoUtils;
Svet Ganov525a66b2014-06-14 22:29:00 -070042
43import java.io.IOException;
44import java.util.Iterator;
45import java.util.LinkedHashMap;
46import java.util.Map;
47
48public final class PageContentRepository {
49 private static final String LOG_TAG = "PageContentRepository";
50
Svetoslav15cbc8a2014-07-11 09:45:07 -070051 private static final boolean DEBUG = false;
Svet Ganov525a66b2014-06-14 22:29:00 -070052
53 private static final int INVALID_PAGE_INDEX = -1;
54
55 private static final int STATE_CLOSED = 0;
56 private static final int STATE_OPENED = 1;
57 private static final int STATE_DESTROYED = 2;
58
59 private static final int BYTES_PER_PIXEL = 4;
60
61 private static final int BYTES_PER_MEGABYTE = 1048576;
62
Svet Ganov525a66b2014-06-14 22:29:00 -070063 private final CloseGuard mCloseGuard = CloseGuard.get();
64
65 private final ArrayMap<Integer, PageContentProvider> mPageContentProviders =
66 new ArrayMap<>();
67
68 private final AsyncRenderer mRenderer;
69
70 private RenderSpec mLastRenderSpec;
71
72 private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
Svet Ganov525a66b2014-06-14 22:29:00 -070073 private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
74
75 private int mState;
76
77 public interface OnPageContentAvailableCallback {
78 public void onPageContentAvailable(BitmapDrawable content);
79 }
80
Svetoslav5ef522b2014-07-23 20:15:09 -070081 public interface OnMalformedPdfFileListener {
82 public void onMalformedPdfFile();
83 }
84
85 public PageContentRepository(Context context,
86 OnMalformedPdfFileListener malformedPdfFileListener) {
87 mRenderer = new AsyncRenderer(context, malformedPdfFileListener);
Svet Ganov525a66b2014-06-14 22:29:00 -070088 mState = STATE_CLOSED;
89 if (DEBUG) {
90 Log.i(LOG_TAG, "STATE_CLOSED");
91 }
92 mCloseGuard.open("destroy");
93 }
94
95 public void open(ParcelFileDescriptor source, final Runnable callback) {
96 throwIfNotClosed();
97 mState = STATE_OPENED;
98 if (DEBUG) {
99 Log.i(LOG_TAG, "STATE_OPENED");
100 }
101 mRenderer.open(source, callback);
102 }
103
104 public void close(Runnable callback) {
105 throwIfNotOpened();
106 mState = STATE_CLOSED;
107 if (DEBUG) {
108 Log.i(LOG_TAG, "STATE_CLOSED");
109 }
110
111 mRenderer.close(callback);
112 }
113
114 public void destroy() {
115 throwIfNotClosed();
116 mState = STATE_DESTROYED;
117 if (DEBUG) {
118 Log.i(LOG_TAG, "STATE_DESTROYED");
119 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700120 doDestroy();
121 }
122
123 public void startPreload(int firstShownPage, int lastShownPage) {
124 // If we do not have a render spec we have no clue what size the
125 // preloaded bitmaps should be, so just take a note for what to do.
126 if (mLastRenderSpec == null) {
127 mScheduledPreloadFirstShownPage = firstShownPage;
128 mScheduledPreloadLastShownPage = lastShownPage;
129 } else {
130 mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
131 }
132 }
133
134 public void stopPreload() {
135 mRenderer.stopPreload();
136 }
137
138 public int getFilePageCount() {
139 return mRenderer.getPageCount();
140 }
141
142 public PageContentProvider peekPageContentProvider(int pageIndex) {
143 return mPageContentProviders.get(pageIndex);
144 }
145
146 public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) {
147 throwIfDestroyed();
148
149 if (DEBUG) {
150 Log.i(LOG_TAG, "Acquiring provider for page: " + pageIndex);
151 }
152
153 if (mPageContentProviders.get(pageIndex)!= null) {
154 throw new IllegalStateException("Already acquired for page: " + pageIndex);
155 }
156
157 PageContentProvider provider = new PageContentProvider(pageIndex, owner);
158
159 mPageContentProviders.put(pageIndex, provider);
160
161 return provider;
162 }
163
164 public void releasePageContentProvider(PageContentProvider provider) {
165 throwIfDestroyed();
166
167 if (DEBUG) {
168 Log.i(LOG_TAG, "Releasing provider for page: " + provider.mPageIndex);
169 }
170
171 if (mPageContentProviders.remove(provider.mPageIndex) == null) {
172 throw new IllegalStateException("Not acquired");
173 }
174
175 provider.cancelLoad();
176 }
177
178 @Override
179 protected void finalize() throws Throwable {
180 try {
181 if (mState != STATE_DESTROYED) {
182 mCloseGuard.warnIfOpen();
183 doDestroy();
184 }
185 } finally {
186 super.finalize();
187 }
188 }
189
190 private void doDestroy() {
191 mState = STATE_DESTROYED;
192 if (DEBUG) {
193 Log.i(LOG_TAG, "STATE_DESTROYED");
194 }
195 mRenderer.destroy();
196 }
197
198 private void throwIfNotOpened() {
199 if (mState != STATE_OPENED) {
200 throw new IllegalStateException("Not opened");
201 }
202 }
203
204 private void throwIfNotClosed() {
205 if (mState != STATE_CLOSED) {
206 throw new IllegalStateException("Not closed");
207 }
208 }
209
210 private void throwIfDestroyed() {
211 if (mState == STATE_DESTROYED) {
212 throw new IllegalStateException("Destroyed");
213 }
214 }
215
216 public final class PageContentProvider {
217 private final int mPageIndex;
218 private View mOwner;
219
220 public PageContentProvider(int pageIndex, View owner) {
221 mPageIndex = pageIndex;
222 mOwner = owner;
223 }
224
225 public View getOwner() {
226 return mOwner;
227 }
228
229 public int getPageIndex() {
230 return mPageIndex;
231 }
232
233 public void getPageContent(RenderSpec renderSpec, OnPageContentAvailableCallback callback) {
234 throwIfDestroyed();
235
236 mLastRenderSpec = renderSpec;
237
238 // We tired to preload but didn't know the bitmap size, now
239 // that we know let us do the work.
240 if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
241 && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
242 startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
243 mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
244 mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
245 }
246
247 if (mState == STATE_OPENED) {
248 mRenderer.renderPage(mPageIndex, renderSpec, callback);
249 } else {
250 mRenderer.getCachedPage(mPageIndex, renderSpec, callback);
251 }
252 }
253
254 void cancelLoad() {
255 throwIfDestroyed();
256
257 if (mState == STATE_OPENED) {
258 mRenderer.cancelRendering(mPageIndex);
259 }
260 }
261 }
262
263 private static final class PageContentLruCache {
264 private final LinkedHashMap<Integer, RenderedPage> mRenderedPages =
265 new LinkedHashMap<>();
266
267 private final int mMaxSizeInBytes;
268
269 private int mSizeInBytes;
270
271 public PageContentLruCache(int maxSizeInBytes) {
272 mMaxSizeInBytes = maxSizeInBytes;
273 }
274
275 public RenderedPage getRenderedPage(int pageIndex) {
276 return mRenderedPages.get(pageIndex);
277 }
278
279 public RenderedPage removeRenderedPage(int pageIndex) {
280 RenderedPage page = mRenderedPages.remove(pageIndex);
281 if (page != null) {
282 mSizeInBytes -= page.getSizeInBytes();
283 }
284 return page;
285 }
286
287 public RenderedPage putRenderedPage(int pageIndex, RenderedPage renderedPage) {
288 RenderedPage oldRenderedPage = mRenderedPages.remove(pageIndex);
289 if (oldRenderedPage != null) {
290 if (!oldRenderedPage.renderSpec.equals(renderedPage.renderSpec)) {
291 throw new IllegalStateException("Wrong page size");
292 }
293 } else {
294 final int contentSizeInBytes = renderedPage.getSizeInBytes();
295 if (mSizeInBytes + contentSizeInBytes > mMaxSizeInBytes) {
296 throw new IllegalStateException("Client didn't free space");
297 }
298
299 mSizeInBytes += contentSizeInBytes;
300 }
301 return mRenderedPages.put(pageIndex, renderedPage);
302 }
303
304 public void invalidate() {
305 for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
306 entry.getValue().state = RenderedPage.STATE_SCRAP;
307 }
308 }
309
310 public RenderedPage removeLeastNeeded() {
311 if (mRenderedPages.isEmpty()) {
312 return null;
313 }
314
315 // First try to remove a rendered page that holds invalidated
316 // or incomplete content, i.e. its render spec is null.
317 for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
318 RenderedPage renderedPage = entry.getValue();
319 if (renderedPage.state == RenderedPage.STATE_SCRAP) {
320 Integer pageIndex = entry.getKey();
321 mRenderedPages.remove(pageIndex);
322 mSizeInBytes -= renderedPage.getSizeInBytes();
323 return renderedPage;
324 }
325 }
326
327 // If all rendered pages contain rendered content, then use the oldest.
328 final int pageIndex = mRenderedPages.eldest().getKey();
329 RenderedPage renderedPage = mRenderedPages.remove(pageIndex);
330 mSizeInBytes -= renderedPage.getSizeInBytes();
331 return renderedPage;
332 }
333
334 public int getSizeInBytes() {
335 return mSizeInBytes;
336 }
337
338 public int getMaxSizeInBytes() {
339 return mMaxSizeInBytes;
340 }
341
342 public void clear() {
343 Iterator<Map.Entry<Integer, RenderedPage>> iterator =
344 mRenderedPages.entrySet().iterator();
345 while (iterator.hasNext()) {
346 iterator.next().getValue().recycle();
347 iterator.remove();
348 }
349 }
350 }
351
352 public static final class RenderSpec {
353 final int bitmapWidth;
354 final int bitmapHeight;
Svet Ganov13f542c2014-08-29 15:35:49 -0700355 final PrintAttributes printAttributes;
Svet Ganov525a66b2014-06-14 22:29:00 -0700356
357 public RenderSpec(int bitmapWidth, int bitmapHeight,
Svet Ganov13f542c2014-08-29 15:35:49 -0700358 PrintAttributes printAttributes) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700359 this.bitmapWidth = bitmapWidth;
360 this.bitmapHeight = bitmapHeight;
Svet Ganov13f542c2014-08-29 15:35:49 -0700361 this.printAttributes = printAttributes;
Svet Ganov525a66b2014-06-14 22:29:00 -0700362 }
363
364 @Override
365 public boolean equals(Object obj) {
366 if (this == obj) {
367 return true;
368 }
369 if (obj == null) {
370 return false;
371 }
372 if (getClass() != obj.getClass()) {
373 return false;
374 }
375 RenderSpec other = (RenderSpec) obj;
376 if (bitmapHeight != other.bitmapHeight) {
377 return false;
378 }
379 if (bitmapWidth != other.bitmapWidth) {
380 return false;
381 }
Svet Ganov13f542c2014-08-29 15:35:49 -0700382 if (printAttributes != null) {
383 if (!printAttributes.equals(other.printAttributes)) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700384 return false;
385 }
Svet Ganov13f542c2014-08-29 15:35:49 -0700386 } else if (other.printAttributes != null) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700387 return false;
388 }
389 return true;
390 }
391
392 public boolean hasSameSize(RenderedPage page) {
393 Bitmap bitmap = page.content.getBitmap();
394 return bitmap.getWidth() == bitmapWidth
395 && bitmap.getHeight() == bitmapHeight;
396 }
397
398 @Override
399 public int hashCode() {
400 int result = bitmapWidth;
401 result = 31 * result + bitmapHeight;
Svet Ganov13f542c2014-08-29 15:35:49 -0700402 result = 31 * result + (printAttributes != null ? printAttributes.hashCode() : 0);
Svet Ganov525a66b2014-06-14 22:29:00 -0700403 return result;
404 }
405 }
406
407 private static final class RenderedPage {
408 public static final int STATE_RENDERED = 0;
409 public static final int STATE_RENDERING = 1;
410 public static final int STATE_SCRAP = 2;
411
412 final BitmapDrawable content;
413 RenderSpec renderSpec;
414
415 int state = STATE_SCRAP;
416
417 RenderedPage(BitmapDrawable content) {
418 this.content = content;
419 }
420
421 public int getSizeInBytes() {
422 return content.getBitmap().getByteCount();
423 }
424
425 public void recycle() {
426 content.getBitmap().recycle();
427 }
428
429 public void erase() {
430 content.getBitmap().eraseColor(Color.WHITE);
431 }
432 }
433
Svet Ganov13f542c2014-08-29 15:35:49 -0700434 private static final class AsyncRenderer implements ServiceConnection {
Svetoslav5ef522b2014-07-23 20:15:09 -0700435 private static final int MALFORMED_PDF_FILE_ERROR = -2;
436
Svet Ganov13f542c2014-08-29 15:35:49 -0700437 private final Object mLock = new Object();
438
Svet Ganov525a66b2014-06-14 22:29:00 -0700439 private final Context mContext;
440
441 private final PageContentLruCache mPageContentCache;
442
443 private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>();
444
Svetoslav5ef522b2014-07-23 20:15:09 -0700445 private final OnMalformedPdfFileListener mOnMalformedPdfFileListener;
446
Svet Ganov525a66b2014-06-14 22:29:00 -0700447 private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
448
Svet Ganov13f542c2014-08-29 15:35:49 -0700449 @GuardedBy("mLock")
450 private IPdfRenderer mRenderer;
Svet Ganov525a66b2014-06-14 22:29:00 -0700451
Svetoslav5ef522b2014-07-23 20:15:09 -0700452 public AsyncRenderer(Context context, OnMalformedPdfFileListener malformedPdfFileListener) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700453 mContext = context;
Svetoslav5ef522b2014-07-23 20:15:09 -0700454 mOnMalformedPdfFileListener = malformedPdfFileListener;
Svet Ganov525a66b2014-06-14 22:29:00 -0700455
456 ActivityManager activityManager = (ActivityManager)
457 mContext.getSystemService(Context.ACTIVITY_SERVICE);
458 final int cacheSizeInBytes = activityManager.getMemoryClass() * BYTES_PER_MEGABYTE / 4;
459 mPageContentCache = new PageContentLruCache(cacheSizeInBytes);
460 }
461
Svet Ganov13f542c2014-08-29 15:35:49 -0700462 @Override
463 public void onServiceConnected(ComponentName name, IBinder service) {
464 synchronized (mLock) {
465 mRenderer = IPdfRenderer.Stub.asInterface(service);
466 mLock.notifyAll();
467 }
468 }
469
470 @Override
471 public void onServiceDisconnected(ComponentName name) {
472 synchronized (mLock) {
473 mRenderer = null;
474 }
475 }
476
Svet Ganov525a66b2014-06-14 22:29:00 -0700477 public void open(final ParcelFileDescriptor source, final Runnable callback) {
478 // Opening a new document invalidates the cache as it has pages
479 // from the last document. We keep the cache even when the document
480 // is closed to show pages while the other side is writing the new
481 // document.
482 mPageContentCache.invalidate();
483
484 new AsyncTask<Void, Void, Integer>() {
485 @Override
Svet Ganov13f542c2014-08-29 15:35:49 -0700486 protected void onPreExecute() {
487 Intent intent = new Intent(mContext, PdfRendererService.class);
488 mContext.bindService(intent, AsyncRenderer.this, Context.BIND_AUTO_CREATE);
489 }
490
491 @Override
Svet Ganov525a66b2014-06-14 22:29:00 -0700492 protected Integer doInBackground(Void... params) {
Svet Ganov13f542c2014-08-29 15:35:49 -0700493 synchronized (mLock) {
494 while (mRenderer == null) {
495 try {
496 mLock.wait();
497 } catch (InterruptedException ie) {
498 /* ignore */
499 }
500 }
501 try {
502 return mRenderer.openDocument(source);
503 } catch (RemoteException re) {
504 Log.e(LOG_TAG, "Cannot open PDF document");
505 return MALFORMED_PDF_FILE_ERROR;
506 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700507 }
508 }
509
510 @Override
511 public void onPostExecute(Integer pageCount) {
Svetoslav5ef522b2014-07-23 20:15:09 -0700512 if (pageCount == MALFORMED_PDF_FILE_ERROR) {
513 mOnMalformedPdfFileListener.onMalformedPdfFile();
514 mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
515 } else {
516 mPageCount = pageCount;
517 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700518 if (callback != null) {
519 callback.run();
520 }
521 }
522 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
523 }
524
525 public void close(final Runnable callback) {
526 cancelAllRendering();
527
528 new AsyncTask<Void, Void, Void>() {
529 @Override
530 protected Void doInBackground(Void... params) {
Svet Ganov13f542c2014-08-29 15:35:49 -0700531 synchronized (mLock) {
532 try {
533 mRenderer.closeDocument();
534 } catch (RemoteException re) {
535 /* ignore */
536 }
537 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700538 return null;
539 }
540
541 @Override
542 public void onPostExecute(Void result) {
543 mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
544 if (callback != null) {
545 callback.run();
546 }
547 }
548 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
549 }
550
551 public void destroy() {
Svet Ganov13f542c2014-08-29 15:35:49 -0700552 new AsyncTask<Void, Void, Void>() {
553 @Override
554 protected Void doInBackground(Void... params) {
555 return null;
556 }
557
558 @Override
559 public void onPostExecute(Void result) {
560 mContext.unbindService(AsyncRenderer.this);
561 mPageContentCache.invalidate();
562 mPageContentCache.clear();
563 }
564 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
Svet Ganov525a66b2014-06-14 22:29:00 -0700565 }
566
567 public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
568 if (DEBUG) {
569 Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
570 + "-" + lastShownPage + "]");
571 }
572
573 final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
574 * BYTES_PER_PIXEL;
575 final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
576 / bitmapSizeInBytes;
Svetoslav1710e0312014-07-11 15:19:22 -0700577 final int halfPreloadCount = (maxCachedPageCount
578 - (lastShownPage - firstShownPage)) / 2 - 1;
Svet Ganov525a66b2014-06-14 22:29:00 -0700579
580 final int excessFromStart;
581 if (firstShownPage - halfPreloadCount < 0) {
582 excessFromStart = halfPreloadCount - firstShownPage;
583 } else {
584 excessFromStart = 0;
585 }
586
587 final int excessFromEnd;
588 if (lastShownPage + halfPreloadCount >= mPageCount) {
589 excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
590 } else {
591 excessFromEnd = 0;
592 }
593
594 final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
595 final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
596 mPageCount - 1);
597
598 for (int i = fromIndex; i <= toIndex; i++) {
599 renderPage(i, renderSpec, null);
600 }
601 }
602
603 public void stopPreload() {
604 final int taskCount = mPageToRenderTaskMap.size();
605 for (int i = 0; i < taskCount; i++) {
606 RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
607 if (task.isPreload() && !task.isCancelled()) {
608 task.cancel(true);
609 }
610 }
611 }
612
613 public int getPageCount() {
614 return mPageCount;
615 }
616
617 public void getCachedPage(int pageIndex, RenderSpec renderSpec,
618 OnPageContentAvailableCallback callback) {
619 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
620 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED
621 && renderedPage.renderSpec.equals(renderSpec)) {
622 if (DEBUG) {
623 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
624 }
625
626 // Announce if needed.
627 if (callback != null) {
628 callback.onPageContentAvailable(renderedPage.content);
629 }
630 }
631 }
632
633 public void renderPage(int pageIndex, RenderSpec renderSpec,
634 OnPageContentAvailableCallback callback) {
635 // First, check if we have a rendered page for this index.
636 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
637 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) {
638 // If we have rendered page with same constraints - done.
639 if (renderedPage.renderSpec.equals(renderSpec)) {
640 if (DEBUG) {
641 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
642 }
643
644 // Announce if needed.
645 if (callback != null) {
646 callback.onPageContentAvailable(renderedPage.content);
647 }
648 return;
649 } else {
650 // If the constraints changed, mark the page obsolete.
651 renderedPage.state = RenderedPage.STATE_SCRAP;
652 }
653 }
654
655 // Next, check if rendering this page is scheduled.
656 RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex);
657 if (renderTask != null && !renderTask.isCancelled()) {
658 // If not rendered and constraints same....
659 if (renderTask.mRenderSpec.equals(renderSpec)) {
660 if (renderTask.mCallback != null) {
661 // If someone else is already waiting for this page - bad state.
662 if (callback != null && renderTask.mCallback != callback) {
663 throw new IllegalStateException("Page rendering not cancelled");
664 }
665 } else {
666 // No callback means we are preloading so just let the argument
667 // callback be attached to our work in progress.
668 renderTask.mCallback = callback;
669 }
670 return;
671 } else {
672 // If not rendered and constraints changed - cancel rendering.
673 renderTask.cancel(true);
674 }
675 }
676
677 // Oh well, we will have work to do...
678 renderTask = new RenderPageTask(pageIndex, renderSpec, callback);
679 mPageToRenderTaskMap.put(pageIndex, renderTask);
680 renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
681 }
682
683 public void cancelRendering(int pageIndex) {
684 RenderPageTask task = mPageToRenderTaskMap.get(pageIndex);
685 if (task != null && !task.isCancelled()) {
686 task.cancel(true);
687 }
688 }
689
690 private void cancelAllRendering() {
691 final int taskCount = mPageToRenderTaskMap.size();
692 for (int i = 0; i < taskCount; i++) {
693 RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
694 if (!task.isCancelled()) {
695 task.cancel(true);
696 }
697 }
698 }
699
700 private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> {
701 final int mPageIndex;
702 final RenderSpec mRenderSpec;
703 OnPageContentAvailableCallback mCallback;
704 RenderedPage mRenderedPage;
705
706 public RenderPageTask(int pageIndex, RenderSpec renderSpec,
707 OnPageContentAvailableCallback callback) {
708 mPageIndex = pageIndex;
709 mRenderSpec = renderSpec;
710 mCallback = callback;
711 }
712
713 @Override
714 protected void onPreExecute() {
715 mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex);
716 if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) {
717 throw new IllegalStateException("Trying to render a rendered page");
718 }
719
720 // Reuse bitmap for the page only if the right size.
721 if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) {
722 if (DEBUG) {
723 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
724 + " with different size.");
725 }
726 mPageContentCache.removeRenderedPage(mPageIndex);
727 mRenderedPage.recycle();
728 mRenderedPage = null;
729 }
730
731 final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
732 * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
733
734 // Try to find a bitmap to reuse.
735 while (mRenderedPage == null) {
736
737 // Fill the cache greedily.
738 if (mPageContentCache.getSizeInBytes() <= 0
739 || mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
740 <= mPageContentCache.getMaxSizeInBytes()) {
741 break;
742 }
743
744 RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
745
746 if (!mRenderSpec.hasSameSize(renderedPage)) {
747 if (DEBUG) {
748 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
749 + " with different size.");
750 }
751 renderedPage.recycle();
752 continue;
753 }
754
755 mRenderedPage = renderedPage;
756 renderedPage.erase();
757
758 if (DEBUG) {
759 Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
760 + mPageContentCache.getSizeInBytes() + " bytes");
761 }
762
763 break;
764 }
765
Svet Ganov525a66b2014-06-14 22:29:00 -0700766 if (mRenderedPage == null) {
767 if (DEBUG) {
768 Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
769 + mPageContentCache.getSizeInBytes() + " bytes");
770 }
771 Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth,
772 mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888);
773 bitmap.eraseColor(Color.WHITE);
774 BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap);
775 mRenderedPage = new RenderedPage(content);
776 }
777
778 mRenderedPage.renderSpec = mRenderSpec;
779 mRenderedPage.state = RenderedPage.STATE_RENDERING;
780
781 mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage);
782 }
783
784 @Override
785 protected RenderedPage doInBackground(Void... params) {
786 if (isCancelled()) {
787 return mRenderedPage;
788 }
789
Svet Ganov525a66b2014-06-14 22:29:00 -0700790 Bitmap bitmap = mRenderedPage.content.getBitmap();
791
Svet Ganov13f542c2014-08-29 15:35:49 -0700792 ParcelFileDescriptor[] pipe = null;
793 try {
794 pipe = ParcelFileDescriptor.createPipe();
795 ParcelFileDescriptor source = pipe[0];
796 ParcelFileDescriptor destination = pipe[1];
Svet Ganov525a66b2014-06-14 22:29:00 -0700797
Svet Ganov13f542c2014-08-29 15:35:49 -0700798 mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(),
799 mRenderSpec.printAttributes, destination);
Svet Ganov525a66b2014-06-14 22:29:00 -0700800
Svet Ganov13f542c2014-08-29 15:35:49 -0700801 // We passed the file descriptor to the other side which took
802 // ownership, so close our copy for the write to complete.
803 destination.close();
Svet Ganov525a66b2014-06-14 22:29:00 -0700804
Svet Ganovdf644492014-08-29 15:35:49 -0700805 BitmapSerializeUtils.readBitmapPixels(bitmap, source);
Svet Ganov13f542c2014-08-29 15:35:49 -0700806 } catch (IOException|RemoteException e) {
807 Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e);
808 } finally {
809 IoUtils.closeQuietly(pipe[0]);
810 IoUtils.closeQuietly(pipe[1]);
Svet Ganov525a66b2014-06-14 22:29:00 -0700811 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700812
813 return mRenderedPage;
814 }
815
816 @Override
817 public void onPostExecute(RenderedPage renderedPage) {
818 if (DEBUG) {
819 Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex);
820 }
821
822 // This task is done.
823 mPageToRenderTaskMap.remove(mPageIndex);
824
825 // Take a note that the content is rendered.
826 renderedPage.state = RenderedPage.STATE_RENDERED;
827
828 // Announce success if needed.
829 if (mCallback != null) {
830 mCallback.onPageContentAvailable(renderedPage.content);
831 }
832 }
833
834 @Override
835 protected void onCancelled(RenderedPage renderedPage) {
836 if (DEBUG) {
837 Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex);
838 }
839
840 // This task is done.
841 mPageToRenderTaskMap.remove(mPageIndex);
842
843 // If canceled before on pre-execute.
844 if (renderedPage == null) {
845 return;
846 }
847
848 // Take a note that the content is not rendered.
849 renderedPage.state = RenderedPage.STATE_SCRAP;
850 }
851
852 public boolean isPreload() {
853 return mCallback == null;
854 }
855 }
856 }
857}