PageContentRepository.java revision df6444931b030d3cdd9769e23f16f0a16fe9c654
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.PrintDocumentInfo; 33import android.util.ArrayMap; 34import android.util.Log; 35import android.view.View; 36import com.android.internal.annotations.GuardedBy; 37import com.android.printspooler.renderer.IPdfRenderer; 38import com.android.printspooler.renderer.PdfRendererService; 39import com.android.printspooler.util.BitmapSerializeUtils; 40import dalvik.system.CloseGuard; 41import libcore.io.IoUtils; 42 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 51 private static final boolean DEBUG = false; 52 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 63 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; 73 private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX; 74 75 private int mState; 76 77 public interface OnPageContentAvailableCallback { 78 public void onPageContentAvailable(BitmapDrawable content); 79 } 80 81 public interface OnMalformedPdfFileListener { 82 public void onMalformedPdfFile(); 83 } 84 85 public PageContentRepository(Context context, 86 OnMalformedPdfFileListener malformedPdfFileListener) { 87 mRenderer = new AsyncRenderer(context, malformedPdfFileListener); 88 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 } 120 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; 355 final PrintAttributes printAttributes; 356 357 public RenderSpec(int bitmapWidth, int bitmapHeight, 358 PrintAttributes printAttributes) { 359 this.bitmapWidth = bitmapWidth; 360 this.bitmapHeight = bitmapHeight; 361 this.printAttributes = printAttributes; 362 } 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 } 382 if (printAttributes != null) { 383 if (!printAttributes.equals(other.printAttributes)) { 384 return false; 385 } 386 } else if (other.printAttributes != null) { 387 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; 402 result = 31 * result + (printAttributes != null ? printAttributes.hashCode() : 0); 403 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 434 private static final class AsyncRenderer implements ServiceConnection { 435 private static final int MALFORMED_PDF_FILE_ERROR = -2; 436 437 private final Object mLock = new Object(); 438 439 private final Context mContext; 440 441 private final PageContentLruCache mPageContentCache; 442 443 private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>(); 444 445 private final OnMalformedPdfFileListener mOnMalformedPdfFileListener; 446 447 private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; 448 449 @GuardedBy("mLock") 450 private IPdfRenderer mRenderer; 451 452 public AsyncRenderer(Context context, OnMalformedPdfFileListener malformedPdfFileListener) { 453 mContext = context; 454 mOnMalformedPdfFileListener = malformedPdfFileListener; 455 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 462 @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 477 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 486 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 492 protected Integer doInBackground(Void... params) { 493 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 } 507 } 508 } 509 510 @Override 511 public void onPostExecute(Integer pageCount) { 512 if (pageCount == MALFORMED_PDF_FILE_ERROR) { 513 mOnMalformedPdfFileListener.onMalformedPdfFile(); 514 mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; 515 } else { 516 mPageCount = pageCount; 517 } 518 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) { 531 synchronized (mLock) { 532 try { 533 mRenderer.closeDocument(); 534 } catch (RemoteException re) { 535 /* ignore */ 536 } 537 } 538 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() { 552 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); 565 } 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; 577 final int halfPreloadCount = (maxCachedPageCount 578 - (lastShownPage - firstShownPage)) / 2 - 1; 579 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 766 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 790 Bitmap bitmap = mRenderedPage.content.getBitmap(); 791 792 ParcelFileDescriptor[] pipe = null; 793 try { 794 pipe = ParcelFileDescriptor.createPipe(); 795 ParcelFileDescriptor source = pipe[0]; 796 ParcelFileDescriptor destination = pipe[1]; 797 798 mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(), 799 mRenderSpec.printAttributes, destination); 800 801 // 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(); 804 805 BitmapSerializeUtils.readBitmapPixels(bitmap, source); 806 } 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]); 811 } 812 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} 858