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