PageContentRepository.java revision 7fd5ada98aa9e035682531d9fe25633fdd24a058
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 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(); 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 erase() { 429 content.getBitmap().eraseColor(Color.WHITE); 430 } 431 } 432 433 private static final class AsyncRenderer implements ServiceConnection { 434 private static final int MALFORMED_PDF_FILE_ERROR = -2; 435 436 private final Object mLock = new Object(); 437 438 private final Context mContext; 439 440 private final PageContentLruCache mPageContentCache; 441 442 private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>(); 443 444 private final OnMalformedPdfFileListener mOnMalformedPdfFileListener; 445 446 private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; 447 448 @GuardedBy("mLock") 449 private IPdfRenderer mRenderer; 450 451 private boolean mBoundToService; 452 453 public AsyncRenderer(Context context, OnMalformedPdfFileListener malformedPdfFileListener) { 454 mContext = context; 455 mOnMalformedPdfFileListener = malformedPdfFileListener; 456 457 ActivityManager activityManager = (ActivityManager) 458 mContext.getSystemService(Context.ACTIVITY_SERVICE); 459 final int cacheSizeInBytes = activityManager.getMemoryClass() * BYTES_PER_MEGABYTE / 4; 460 mPageContentCache = new PageContentLruCache(cacheSizeInBytes); 461 } 462 463 @Override 464 public void onServiceConnected(ComponentName name, IBinder service) { 465 mBoundToService = true; 466 synchronized (mLock) { 467 mRenderer = IPdfRenderer.Stub.asInterface(service); 468 mLock.notifyAll(); 469 } 470 } 471 472 @Override 473 public void onServiceDisconnected(ComponentName name) { 474 synchronized (mLock) { 475 mRenderer = null; 476 } 477 } 478 479 public void open(final ParcelFileDescriptor source, final Runnable callback) { 480 // Opening a new document invalidates the cache as it has pages 481 // from the last document. We keep the cache even when the document 482 // is closed to show pages while the other side is writing the new 483 // document. 484 mPageContentCache.invalidate(); 485 486 new AsyncTask<Void, Void, Integer>() { 487 @Override 488 protected void onPreExecute() { 489 Intent intent = new Intent(PdfManipulationService.ACTION_GET_RENDERER); 490 intent.setClass(mContext, PdfManipulationService.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); 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); 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 if (mBoundToService) { 568 mBoundToService = false; 569 mContext.unbindService(AsyncRenderer.this); 570 } 571 mPageContentCache.invalidate(); 572 mPageContentCache.clear(); 573 } 574 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 575 } 576 577 public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) { 578 if (DEBUG) { 579 Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage 580 + "-" + lastShownPage + "]"); 581 } 582 583 final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight 584 * BYTES_PER_PIXEL; 585 final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes() 586 / bitmapSizeInBytes; 587 final int halfPreloadCount = (maxCachedPageCount 588 - (lastShownPage - firstShownPage)) / 2 - 1; 589 590 final int excessFromStart; 591 if (firstShownPage - halfPreloadCount < 0) { 592 excessFromStart = halfPreloadCount - firstShownPage; 593 } else { 594 excessFromStart = 0; 595 } 596 597 final int excessFromEnd; 598 if (lastShownPage + halfPreloadCount >= mPageCount) { 599 excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount; 600 } else { 601 excessFromEnd = 0; 602 } 603 604 final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0); 605 final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart, 606 mPageCount - 1); 607 608 for (int i = fromIndex; i <= toIndex; i++) { 609 renderPage(i, renderSpec, null); 610 } 611 } 612 613 public void stopPreload() { 614 final int taskCount = mPageToRenderTaskMap.size(); 615 for (int i = 0; i < taskCount; i++) { 616 RenderPageTask task = mPageToRenderTaskMap.valueAt(i); 617 if (task.isPreload() && !task.isCancelled()) { 618 task.cancel(true); 619 } 620 } 621 } 622 623 public int getPageCount() { 624 return mPageCount; 625 } 626 627 public void getCachedPage(int pageIndex, RenderSpec renderSpec, 628 OnPageContentAvailableCallback callback) { 629 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex); 630 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED 631 && renderedPage.renderSpec.equals(renderSpec)) { 632 if (DEBUG) { 633 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex); 634 } 635 636 // Announce if needed. 637 if (callback != null) { 638 callback.onPageContentAvailable(renderedPage.content); 639 } 640 } 641 } 642 643 public void renderPage(int pageIndex, RenderSpec renderSpec, 644 OnPageContentAvailableCallback callback) { 645 // First, check if we have a rendered page for this index. 646 RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex); 647 if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) { 648 // If we have rendered page with same constraints - done. 649 if (renderedPage.renderSpec.equals(renderSpec)) { 650 if (DEBUG) { 651 Log.i(LOG_TAG, "Cache hit for page: " + pageIndex); 652 } 653 654 // Announce if needed. 655 if (callback != null) { 656 callback.onPageContentAvailable(renderedPage.content); 657 } 658 return; 659 } else { 660 // If the constraints changed, mark the page obsolete. 661 renderedPage.state = RenderedPage.STATE_SCRAP; 662 } 663 } 664 665 // Next, check if rendering this page is scheduled. 666 RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex); 667 if (renderTask != null && !renderTask.isCancelled()) { 668 // If not rendered and constraints same.... 669 if (renderTask.mRenderSpec.equals(renderSpec)) { 670 if (renderTask.mCallback != null) { 671 // If someone else is already waiting for this page - bad state. 672 if (callback != null && renderTask.mCallback != callback) { 673 throw new IllegalStateException("Page rendering not cancelled"); 674 } 675 } else { 676 // No callback means we are preloading so just let the argument 677 // callback be attached to our work in progress. 678 renderTask.mCallback = callback; 679 } 680 return; 681 } else { 682 // If not rendered and constraints changed - cancel rendering. 683 renderTask.cancel(true); 684 } 685 } 686 687 // Oh well, we will have work to do... 688 renderTask = new RenderPageTask(pageIndex, renderSpec, callback); 689 mPageToRenderTaskMap.put(pageIndex, renderTask); 690 renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 691 } 692 693 public void cancelRendering(int pageIndex) { 694 RenderPageTask task = mPageToRenderTaskMap.get(pageIndex); 695 if (task != null && !task.isCancelled()) { 696 task.cancel(true); 697 } 698 } 699 700 private void cancelAllRendering() { 701 final int taskCount = mPageToRenderTaskMap.size(); 702 for (int i = 0; i < taskCount; i++) { 703 RenderPageTask task = mPageToRenderTaskMap.valueAt(i); 704 if (!task.isCancelled()) { 705 task.cancel(true); 706 } 707 } 708 } 709 710 private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> { 711 final int mPageIndex; 712 final RenderSpec mRenderSpec; 713 OnPageContentAvailableCallback mCallback; 714 RenderedPage mRenderedPage; 715 716 public RenderPageTask(int pageIndex, RenderSpec renderSpec, 717 OnPageContentAvailableCallback callback) { 718 mPageIndex = pageIndex; 719 mRenderSpec = renderSpec; 720 mCallback = callback; 721 } 722 723 @Override 724 protected void onPreExecute() { 725 mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex); 726 if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) { 727 throw new IllegalStateException("Trying to render a rendered page"); 728 } 729 730 // Reuse bitmap for the page only if the right size. 731 if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) { 732 if (DEBUG) { 733 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex 734 + " with different size."); 735 } 736 mPageContentCache.removeRenderedPage(mPageIndex); 737 mRenderedPage = null; 738 } 739 740 final int bitmapSizeInBytes = mRenderSpec.bitmapWidth 741 * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL; 742 743 // Try to find a bitmap to reuse. 744 while (mRenderedPage == null) { 745 746 // Fill the cache greedily. 747 if (mPageContentCache.getSizeInBytes() <= 0 748 || mPageContentCache.getSizeInBytes() + bitmapSizeInBytes 749 <= mPageContentCache.getMaxSizeInBytes()) { 750 break; 751 } 752 753 RenderedPage renderedPage = mPageContentCache.removeLeastNeeded(); 754 755 if (!mRenderSpec.hasSameSize(renderedPage)) { 756 if (DEBUG) { 757 Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex 758 + " with different size."); 759 } 760 continue; 761 } 762 763 mRenderedPage = renderedPage; 764 renderedPage.erase(); 765 766 if (DEBUG) { 767 Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: " 768 + mPageContentCache.getSizeInBytes() + " bytes"); 769 } 770 771 break; 772 } 773 774 if (mRenderedPage == null) { 775 if (DEBUG) { 776 Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: " 777 + mPageContentCache.getSizeInBytes() + " bytes"); 778 } 779 Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth, 780 mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888); 781 bitmap.eraseColor(Color.WHITE); 782 BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap); 783 mRenderedPage = new RenderedPage(content); 784 } 785 786 mRenderedPage.renderSpec = mRenderSpec; 787 mRenderedPage.state = RenderedPage.STATE_RENDERING; 788 789 mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage); 790 } 791 792 @Override 793 protected RenderedPage doInBackground(Void... params) { 794 if (isCancelled()) { 795 return mRenderedPage; 796 } 797 798 Bitmap bitmap = mRenderedPage.content.getBitmap(); 799 800 ParcelFileDescriptor[] pipe = null; 801 try { 802 pipe = ParcelFileDescriptor.createPipe(); 803 ParcelFileDescriptor source = pipe[0]; 804 ParcelFileDescriptor destination = pipe[1]; 805 806 mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(), 807 mRenderSpec.printAttributes, destination); 808 809 // We passed the file descriptor to the other side which took 810 // ownership, so close our copy for the write to complete. 811 destination.close(); 812 813 BitmapSerializeUtils.readBitmapPixels(bitmap, source); 814 } catch (IOException|RemoteException e) { 815 Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e); 816 } finally { 817 IoUtils.closeQuietly(pipe[0]); 818 IoUtils.closeQuietly(pipe[1]); 819 } 820 821 return mRenderedPage; 822 } 823 824 @Override 825 public void onPostExecute(RenderedPage renderedPage) { 826 if (DEBUG) { 827 Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex); 828 } 829 830 // This task is done. 831 mPageToRenderTaskMap.remove(mPageIndex); 832 833 // Take a note that the content is rendered. 834 renderedPage.state = RenderedPage.STATE_RENDERED; 835 836 // Announce success if needed. 837 if (mCallback != null) { 838 mCallback.onPageContentAvailable(renderedPage.content); 839 } 840 } 841 842 @Override 843 protected void onCancelled(RenderedPage renderedPage) { 844 if (DEBUG) { 845 Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex); 846 } 847 848 // This task is done. 849 mPageToRenderTaskMap.remove(mPageIndex); 850 851 // If canceled before on pre-execute. 852 if (renderedPage == null) { 853 return; 854 } 855 856 // Take a note that the content is not rendered. 857 renderedPage.state = RenderedPage.STATE_SCRAP; 858 } 859 860 public boolean isPreload() { 861 return mCallback == null; 862 } 863 } 864 } 865} 866