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