PageContentRepository.java revision 9424b733c7f777fc806a751a7f00d234c11c0e7c
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(Runnable callback) { 114 throwIfNotClosed(); 115 mState = STATE_DESTROYED; 116 if (DEBUG) { 117 Log.i(LOG_TAG, "STATE_DESTROYED"); 118 } 119 doDestroy(callback); 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(null); 167 } 168 } finally { 169 super.finalize(); 170 } 171 } 172 173 private void doDestroy(Runnable callback) { 174 mState = STATE_DESTROYED; 175 if (DEBUG) { 176 Log.i(LOG_TAG, "STATE_DESTROYED"); 177 } 178 mRenderer.destroy(callback); 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(final Runnable callback) { 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 if (callback != null) { 555 callback.run(); 556 } 557 558 } 559 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 560 } 561 562 public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) { 563 if (DEBUG) { 564 Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage 565 + "-" + lastShownPage + "]"); 566 } 567 568 final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight 569 * BYTES_PER_PIXEL; 570 final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes() 571 / bitmapSizeInBytes; 572 final int halfPreloadCount = (maxCachedPageCount 573 - (lastShownPage - firstShownPage)) / 2 - 1; 574 575 final int excessFromStart; 576 if (firstShownPage - halfPreloadCount < 0) { 577 excessFromStart = halfPreloadCount - firstShownPage; 578 } else { 579 excessFromStart = 0; 580 } 581 582 final int excessFromEnd; 583 if (lastShownPage + halfPreloadCount >= mPageCount) { 584 excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount; 585 } else { 586 excessFromEnd = 0; 587 } 588 589 final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0); 590 final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart, 591 mPageCount - 1); 592 593 for (int i = fromIndex; i <= toIndex; i++) { 594 renderPage(i, renderSpec, null); 595 } 596 } 597 598 public void stopPreload() { 599 final int taskCount = mPageToRenderTaskMap.size(); 600 for (int i = 0; i < taskCount; i++) { 601 RenderPageTask task = mPageToRenderTaskMap.valueAt(i); 602 if (task.isPreload() && !task.isCancelled()) { 603 task.cancel(true); 604 } 605 } 606 } 607 608 public int getPageCount() { 609 return mPageCount; 610 } 611 612 public void getCachedPage(int pageIndex, RenderSpec renderSpec, 613 OnPageContentAvailableCallback callback) { 614 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex); 615 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED 616 && renderedPage.renderSpec.equals(renderSpec)) { 617 if (DEBUG) { 618 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex); 619 } 620 621 // Announce if needed. 622 if (callback != null) { 623 callback.onPageContentAvailable(renderedPage.content); 624 } 625 } 626 } 627 628 public void renderPage(int pageIndex, RenderSpec renderSpec, 629 OnPageContentAvailableCallback callback) { 630 // First, check if we have a rendered page for this index. 631 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex); 632 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) { 633 // If we have rendered page with same constraints - done. 634 if (renderedPage.renderSpec.equals(renderSpec)) { 635 if (DEBUG) { 636 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex); 637 } 638 639 // Announce if needed. 640 if (callback != null) { 641 callback.onPageContentAvailable(renderedPage.content); 642 } 643 return; 644 } else { 645 // If the constraints changed, mark the page obsolete. 646 renderedPage.state = RenderedPage.STATE_SCRAP; 647 } 648 } 649 650 // Next, check if rendering this page is scheduled. 651 RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex); 652 if (renderTask != null && !renderTask.isCancelled()) { 653 // If not rendered and constraints same.... 654 if (renderTask.mRenderSpec.equals(renderSpec)) { 655 if (renderTask.mCallback != null) { 656 // If someone else is already waiting for this page - bad state. 657 if (callback != null && renderTask.mCallback != callback) { 658 throw new IllegalStateException("Page rendering not cancelled"); 659 } 660 } else { 661 // No callback means we are preloading so just let the argument 662 // callback be attached to our work in progress. 663 renderTask.mCallback = callback; 664 } 665 return; 666 } else { 667 // If not rendered and constraints changed - cancel rendering. 668 renderTask.cancel(true); 669 } 670 } 671 672 // Oh well, we will have work to do... 673 renderTask = new RenderPageTask(pageIndex, renderSpec, callback); 674 mPageToRenderTaskMap.put(pageIndex, renderTask); 675 renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 676 } 677 678 public void cancelRendering(int pageIndex) { 679 RenderPageTask task = mPageToRenderTaskMap.get(pageIndex); 680 if (task != null && !task.isCancelled()) { 681 task.cancel(true); 682 } 683 } 684 685 private void cancelAllRendering() { 686 final int taskCount = mPageToRenderTaskMap.size(); 687 for (int i = 0; i < taskCount; i++) { 688 RenderPageTask task = mPageToRenderTaskMap.valueAt(i); 689 if (!task.isCancelled()) { 690 task.cancel(true); 691 } 692 } 693 } 694 695 private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> { 696 final int mPageIndex; 697 final RenderSpec mRenderSpec; 698 OnPageContentAvailableCallback mCallback; 699 RenderedPage mRenderedPage; 700 701 public RenderPageTask(int pageIndex, RenderSpec renderSpec, 702 OnPageContentAvailableCallback callback) { 703 mPageIndex = pageIndex; 704 mRenderSpec = renderSpec; 705 mCallback = callback; 706 } 707 708 @Override 709 protected void onPreExecute() { 710 mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex); 711 if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) { 712 throw new IllegalStateException("Trying to render a rendered page"); 713 } 714 715 // Reuse bitmap for the page only if the right size. 716 if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) { 717 if (DEBUG) { 718 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex 719 + " with different size."); 720 } 721 mPageContentCache.removeRenderedPage(mPageIndex); 722 mRenderedPage = null; 723 } 724 725 final int bitmapSizeInBytes = mRenderSpec.bitmapWidth 726 * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL; 727 728 // Try to find a bitmap to reuse. 729 while (mRenderedPage == null) { 730 731 // Fill the cache greedily. 732 if (mPageContentCache.getSizeInBytes() <= 0 733 || mPageContentCache.getSizeInBytes() + bitmapSizeInBytes 734 <= mPageContentCache.getMaxSizeInBytes()) { 735 break; 736 } 737 738 RenderedPage renderedPage = mPageContentCache.removeLeastNeeded(); 739 740 if (!mRenderSpec.hasSameSize(renderedPage)) { 741 if (DEBUG) { 742 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex 743 + " with different size."); 744 } 745 continue; 746 } 747 748 mRenderedPage = renderedPage; 749 renderedPage.erase(); 750 751 if (DEBUG) { 752 Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: " 753 + mPageContentCache.getSizeInBytes() + " bytes"); 754 } 755 756 break; 757 } 758 759 if (mRenderedPage == null) { 760 if (DEBUG) { 761 Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: " 762 + mPageContentCache.getSizeInBytes() + " bytes"); 763 } 764 Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth, 765 mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888); 766 bitmap.eraseColor(Color.WHITE); 767 BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap); 768 mRenderedPage = new RenderedPage(content); 769 } 770 771 mRenderedPage.renderSpec = mRenderSpec; 772 mRenderedPage.state = RenderedPage.STATE_RENDERING; 773 774 mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage); 775 } 776 777 @Override 778 protected RenderedPage doInBackground(Void... params) { 779 if (isCancelled()) { 780 return mRenderedPage; 781 } 782 783 Bitmap bitmap = mRenderedPage.content.getBitmap(); 784 785 ParcelFileDescriptor[] pipe = null; 786 try { 787 pipe = ParcelFileDescriptor.createPipe(); 788 ParcelFileDescriptor source = pipe[0]; 789 ParcelFileDescriptor destination = pipe[1]; 790 791 mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(), 792 mRenderSpec.printAttributes, destination); 793 794 // We passed the file descriptor to the other side which took 795 // ownership, so close our copy for the write to complete. 796 destination.close(); 797 798 BitmapSerializeUtils.readBitmapPixels(bitmap, source); 799 } catch (IOException|RemoteException e) { 800 Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e); 801 } finally { 802 IoUtils.closeQuietly(pipe[0]); 803 IoUtils.closeQuietly(pipe[1]); 804 } 805 806 return mRenderedPage; 807 } 808 809 @Override 810 public void onPostExecute(RenderedPage renderedPage) { 811 if (DEBUG) { 812 Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex); 813 } 814 815 // This task is done. 816 mPageToRenderTaskMap.remove(mPageIndex); 817 818 // Take a note that the content is rendered. 819 renderedPage.state = RenderedPage.STATE_RENDERED; 820 821 // Announce success if needed. 822 if (mCallback != null) { 823 mCallback.onPageContentAvailable(renderedPage.content); 824 } 825 } 826 827 @Override 828 protected void onCancelled(RenderedPage renderedPage) { 829 if (DEBUG) { 830 Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex); 831 } 832 833 // This task is done. 834 mPageToRenderTaskMap.remove(mPageIndex); 835 836 // If canceled before on pre-execute. 837 if (renderedPage == null) { 838 return; 839 } 840 841 // Take a note that the content is not rendered. 842 renderedPage.state = RenderedPage.STATE_SCRAP; 843 } 844 845 public boolean isPreload() { 846 return mCallback == null; 847 } 848 } 849 } 850} 851