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