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