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