PageAdapter.java revision f3f963b0bebea91b17f7e60d9b826c458bfde38c
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.ui; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Canvas; 22import android.graphics.drawable.BitmapDrawable; 23import android.os.ParcelFileDescriptor; 24import android.print.PageRange; 25import android.print.PrintAttributes.MediaSize; 26import android.print.PrintAttributes.Margins; 27import android.print.PrintDocumentInfo; 28import android.support.v7.widget.RecyclerView.Adapter; 29import android.support.v7.widget.RecyclerView.ViewHolder; 30import android.util.Log; 31import android.util.SparseArray; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.View.OnClickListener; 35import android.view.ViewGroup; 36import android.view.ViewGroup.LayoutParams; 37import android.view.View.MeasureSpec; 38import android.widget.TextView; 39import com.android.printspooler.R; 40import com.android.printspooler.model.PageContentRepository; 41import com.android.printspooler.model.PageContentRepository.PageContentProvider; 42import com.android.printspooler.util.PageRangeUtils; 43import com.android.printspooler.widget.PageContentView; 44import com.android.printspooler.widget.PreviewPageFrame; 45import dalvik.system.CloseGuard; 46 47import java.util.ArrayList; 48import java.util.Arrays; 49import java.util.List; 50 51/** 52 * This class represents the adapter for the pages in the print preview list. 53 */ 54public final class PageAdapter extends Adapter implements 55 PageContentRepository.OnMalformedPdfFileListener { 56 private static final String LOG_TAG = "PageAdapter"; 57 58 private static final int MAX_PREVIEW_PAGES_BATCH = 50; 59 60 private static final boolean DEBUG = false; 61 62 private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] { 63 PageRange.ALL_PAGES 64 }; 65 66 private static final int INVALID_PAGE_INDEX = -1; 67 68 private static final int STATE_CLOSED = 0; 69 private static final int STATE_OPENED = 1; 70 private static final int STATE_DESTROYED = 2; 71 72 private final CloseGuard mCloseGuard = CloseGuard.get(); 73 74 private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>(); 75 private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>(); 76 77 private final PageClickListener mPageClickListener = new PageClickListener(); 78 79 private final Context mContext; 80 private final LayoutInflater mLayoutInflater; 81 82 private final ContentCallbacks mCallbacks; 83 private final PageContentRepository mPageContentRepository; 84 private final PreviewArea mPreviewArea; 85 86 // Which document pages to be written. 87 private PageRange[] mRequestedPages; 88 // Pages written in the current file. 89 private PageRange[] mWrittenPages; 90 // Pages the user selected in the UI. 91 private PageRange[] mSelectedPages; 92 93 private BitmapDrawable mEmptyState; 94 95 private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; 96 private int mSelectedPageCount; 97 98 private int mPreviewPageMargin; 99 private int mPreviewPageMinWidth; 100 private int mPreviewListPadding; 101 private int mFooterHeight; 102 103 private int mColumnCount; 104 105 private MediaSize mMediaSize; 106 private Margins mMinMargins; 107 108 private int mState; 109 110 private int mPageContentWidth; 111 private int mPageContentHeight; 112 113 public interface ContentCallbacks { 114 public void onRequestContentUpdate(); 115 public void onMalformedPdfFile(); 116 } 117 118 public interface PreviewArea { 119 public int getWidth(); 120 public int getHeight(); 121 public void setColumnCount(int columnCount); 122 public void setPadding(int left, int top, int right, int bottom); 123 } 124 125 public PageAdapter(Context context, ContentCallbacks callbacks, PreviewArea previewArea) { 126 mContext = context; 127 mCallbacks = callbacks; 128 mLayoutInflater = (LayoutInflater) context.getSystemService( 129 Context.LAYOUT_INFLATER_SERVICE); 130 mPageContentRepository = new PageContentRepository(context, this); 131 132 mPreviewPageMargin = mContext.getResources().getDimensionPixelSize( 133 R.dimen.preview_page_margin); 134 135 mPreviewPageMinWidth = mContext.getResources().getDimensionPixelSize( 136 R.dimen.preview_page_min_width); 137 138 mPreviewListPadding = mContext.getResources().getDimensionPixelSize( 139 R.dimen.preview_list_padding); 140 141 mColumnCount = mContext.getResources().getInteger( 142 R.integer.preview_page_per_row_count); 143 144 mFooterHeight = mContext.getResources().getDimensionPixelSize( 145 R.dimen.preview_page_footer_height); 146 147 mPreviewArea = previewArea; 148 149 mCloseGuard.open("destroy"); 150 151 setHasStableIds(true); 152 153 mState = STATE_CLOSED; 154 if (DEBUG) { 155 Log.i(LOG_TAG, "STATE_CLOSED"); 156 } 157 } 158 159 @Override 160 public void onMalformedPdfFile() { 161 mCallbacks.onMalformedPdfFile(); 162 } 163 164 public void onOrientationChanged() { 165 mColumnCount = mContext.getResources().getInteger( 166 R.integer.preview_page_per_row_count); 167 notifyDataSetChanged(); 168 } 169 170 public boolean isOpened() { 171 return mState == STATE_OPENED; 172 } 173 174 public int getFilePageCount() { 175 return mPageContentRepository.getFilePageCount(); 176 } 177 178 public void open(ParcelFileDescriptor source, final Runnable callback) { 179 throwIfNotClosed(); 180 mState = STATE_OPENED; 181 if (DEBUG) { 182 Log.i(LOG_TAG, "STATE_OPENED"); 183 } 184 mPageContentRepository.open(source, new Runnable() { 185 @Override 186 public void run() { 187 notifyDataSetChanged(); 188 callback.run(); 189 } 190 }); 191 } 192 193 public void update(PageRange[] writtenPages, PageRange[] selectedPages, 194 int documentPageCount, MediaSize mediaSize, Margins minMargins) { 195 boolean documentChanged = false; 196 boolean updatePreviewAreaAndPageSize = false; 197 198 // If the app does not tell how many pages are in the document we cannot 199 // optimize and ask for all pages whose count we get from the renderer. 200 if (documentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { 201 if (writtenPages == null) { 202 // If we already requested all pages, just wait. 203 if (!Arrays.equals(ALL_PAGES_ARRAY, mRequestedPages)) { 204 mRequestedPages = ALL_PAGES_ARRAY; 205 mCallbacks.onRequestContentUpdate(); 206 } 207 return; 208 } else { 209 documentPageCount = mPageContentRepository.getFilePageCount(); 210 if (documentPageCount <= 0) { 211 return; 212 } 213 } 214 } 215 216 if (!Arrays.equals(mSelectedPages, selectedPages)) { 217 mSelectedPages = selectedPages; 218 mSelectedPageCount = PageRangeUtils.getNormalizedPageCount( 219 mSelectedPages, documentPageCount); 220 setConfirmedPages(mSelectedPages, documentPageCount); 221 updatePreviewAreaAndPageSize = true; 222 documentChanged = true; 223 } 224 225 if (mDocumentPageCount != documentPageCount) { 226 mDocumentPageCount = documentPageCount; 227 documentChanged = true; 228 } 229 230 if (mMediaSize == null || !mMediaSize.equals(mediaSize)) { 231 mMediaSize = mediaSize; 232 updatePreviewAreaAndPageSize = true; 233 documentChanged = true; 234 } 235 236 if (mMinMargins == null || !mMinMargins.equals(minMargins)) { 237 mMinMargins = minMargins; 238 updatePreviewAreaAndPageSize = true; 239 documentChanged = true; 240 } 241 242 // If *all pages* is selected we need to convert that to absolute 243 // range as we will be checking if some pages are written or not. 244 if (writtenPages != null) { 245 // If we get all pages, this means all pages that we requested. 246 if (PageRangeUtils.isAllPages(writtenPages)) { 247 writtenPages = mRequestedPages; 248 } 249 if (!Arrays.equals(mWrittenPages, writtenPages)) { 250 // TODO: Do a surgical invalidation of only written pages changed. 251 mWrittenPages = writtenPages; 252 documentChanged = true; 253 } 254 } 255 256 if (updatePreviewAreaAndPageSize) { 257 updatePreviewAreaPageSizeAndEmptyState(); 258 } 259 260 if (documentChanged) { 261 notifyDataSetChanged(); 262 } 263 } 264 265 public void close(Runnable callback) { 266 throwIfNotOpened(); 267 mState = STATE_CLOSED; 268 if (DEBUG) { 269 Log.i(LOG_TAG, "STATE_CLOSED"); 270 } 271 mPageContentRepository.close(callback); 272 } 273 274 @Override 275 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 276 View page = mLayoutInflater.inflate(R.layout.preview_page, parent, false); 277 ViewHolder holder = new MyViewHolder(page); 278 holder.setIsRecyclable(true); 279 return holder; 280 } 281 282 @Override 283 public void onBindViewHolder(ViewHolder holder, int position) { 284 if (DEBUG) { 285 Log.i(LOG_TAG, "Binding holder: " + holder + " with id: " + getItemId(position) 286 + " for position: " + position); 287 } 288 289 MyViewHolder myHolder = (MyViewHolder) holder; 290 291 PreviewPageFrame page = (PreviewPageFrame) holder.itemView; 292 page.setOnClickListener(mPageClickListener); 293 294 page.setTag(holder); 295 296 myHolder.mPageInAdapter = position; 297 298 final int pageInDocument = computePageIndexInDocument(position); 299 final int pageIndexInFile = computePageIndexInFile(pageInDocument); 300 301 PageContentView content = (PageContentView) page.findViewById(R.id.page_content); 302 303 LayoutParams params = content.getLayoutParams(); 304 params.width = mPageContentWidth; 305 params.height = mPageContentHeight; 306 307 PageContentProvider provider = content.getPageContentProvider(); 308 309 if (pageIndexInFile != INVALID_PAGE_INDEX) { 310 if (DEBUG) { 311 Log.i(LOG_TAG, "Binding provider:" 312 + " pageIndexInAdapter: " + position 313 + ", pageInDocument: " + pageInDocument 314 + ", pageIndexInFile: " + pageIndexInFile); 315 } 316 317 // OK, there are bugs in recycler view which tries to bind views 318 // without recycling them which would give us a chance to clean up. 319 PageContentProvider boundProvider = mPageContentRepository 320 .peekPageContentProvider(pageIndexInFile); 321 if (boundProvider != null) { 322 PageContentView owner = (PageContentView) boundProvider.getOwner(); 323 owner.init(null, mEmptyState, mMediaSize, mMinMargins); 324 mPageContentRepository.releasePageContentProvider(boundProvider); 325 } 326 327 provider = mPageContentRepository.acquirePageContentProvider( 328 pageIndexInFile, content); 329 mBoundPagesInAdapter.put(position, null); 330 } else { 331 onSelectedPageNotInFile(pageInDocument); 332 } 333 content.init(provider, mEmptyState, mMediaSize, mMinMargins); 334 335 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) { 336 page.setSelected(true, false); 337 } else { 338 page.setSelected(false, false); 339 } 340 341 page.setContentDescription(mContext.getString(R.string.page_description_template, 342 pageInDocument + 1, mDocumentPageCount)); 343 344 TextView pageNumberView = (TextView) page.findViewById(R.id.page_number); 345 String text = mContext.getString(R.string.current_page_template, 346 pageInDocument + 1, mDocumentPageCount); 347 pageNumberView.setText(text); 348 } 349 350 @Override 351 public int getItemCount() { 352 return mSelectedPageCount; 353 } 354 355 @Override 356 public long getItemId(int position) { 357 return computePageIndexInDocument(position); 358 } 359 360 @Override 361 public void onViewRecycled(ViewHolder holder) { 362 MyViewHolder myHolder = (MyViewHolder) holder; 363 PageContentView content = (PageContentView) holder.itemView 364 .findViewById(R.id.page_content); 365 recyclePageView(content, myHolder.mPageInAdapter); 366 myHolder.mPageInAdapter = INVALID_PAGE_INDEX; 367 } 368 369 public PageRange[] getRequestedPages() { 370 return mRequestedPages; 371 } 372 373 public PageRange[] getSelectedPages() { 374 PageRange[] selectedPages = computeSelectedPages(); 375 if (!Arrays.equals(mSelectedPages, selectedPages)) { 376 mSelectedPages = selectedPages; 377 mSelectedPageCount = PageRangeUtils.getNormalizedPageCount( 378 mSelectedPages, mDocumentPageCount); 379 updatePreviewAreaPageSizeAndEmptyState(); 380 notifyDataSetChanged(); 381 } 382 return mSelectedPages; 383 } 384 385 public void onPreviewAreaSizeChanged() { 386 if (mMediaSize != null) { 387 updatePreviewAreaPageSizeAndEmptyState(); 388 notifyDataSetChanged(); 389 } 390 } 391 392 private void updatePreviewAreaPageSizeAndEmptyState() { 393 if (mMediaSize == null) { 394 return; 395 } 396 397 final int availableWidth = mPreviewArea.getWidth(); 398 final int availableHeight = mPreviewArea.getHeight(); 399 400 // Page aspect ratio to keep. 401 final float pageAspectRatio = (float) mMediaSize.getWidthMils() 402 / mMediaSize.getHeightMils(); 403 404 // Make sure we have no empty columns. 405 final int columnCount = Math.min(mSelectedPageCount, mColumnCount); 406 mPreviewArea.setColumnCount(columnCount); 407 408 // Compute max page width. 409 final int horizontalMargins = 2 * columnCount * mPreviewPageMargin; 410 final int horizontalPaddingAndMargins = horizontalMargins + 2 * mPreviewListPadding; 411 final int pageContentDesiredWidth = (int) ((((float) availableWidth 412 - horizontalPaddingAndMargins) / columnCount) + 0.5f); 413 414 // Compute max page height. 415 final int pageContentDesiredHeight = (int) (((float) pageContentDesiredWidth 416 / pageAspectRatio) + 0.5f); 417 418 // If the page does not fit entirely in a vertical direction, 419 // we shirk it but not less than the minimal page width. 420 final int pageContentMinHeight = (int) (mPreviewPageMinWidth / pageAspectRatio + 0.5f); 421 final int pageContentMaxHeight = Math.max(pageContentMinHeight, 422 availableHeight - 2 * (mPreviewListPadding + mPreviewPageMargin) - mFooterHeight); 423 424 mPageContentHeight = Math.min(pageContentDesiredHeight, pageContentMaxHeight); 425 mPageContentWidth = (int) ((mPageContentHeight * pageAspectRatio) + 0.5f); 426 427 final int totalContentWidth = columnCount * mPageContentWidth + horizontalMargins; 428 final int horizontalPadding = (availableWidth - totalContentWidth) / 2; 429 430 final int rowCount = mSelectedPageCount / columnCount 431 + ((mSelectedPageCount % columnCount) > 0 ? 1 : 0); 432 final int totalContentHeight = rowCount * (mPageContentHeight + mFooterHeight + 2 433 * mPreviewPageMargin); 434 435 final int verticalPadding; 436 if (mPageContentHeight + mFooterHeight + mPreviewListPadding 437 + 2 * mPreviewPageMargin > availableHeight) { 438 verticalPadding = Math.max(0, 439 (availableHeight - mPageContentHeight - mFooterHeight) / 2 440 - mPreviewPageMargin); 441 } else { 442 verticalPadding = Math.max(mPreviewListPadding, 443 (availableHeight - totalContentHeight) / 2); 444 } 445 446 mPreviewArea.setPadding(horizontalPadding, verticalPadding, 447 horizontalPadding, verticalPadding); 448 449 // Now update the empty state drawable, as it depends on the page 450 // size and is reused for all views for better performance. 451 LayoutInflater inflater = LayoutInflater.from(mContext); 452 View content = inflater.inflate(R.layout.preview_page_loading, null, false); 453 content.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), 454 MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY)); 455 content.layout(0, 0, content.getMeasuredWidth(), content.getMeasuredHeight()); 456 457 Bitmap bitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, 458 Bitmap.Config.ARGB_8888); 459 Canvas canvas = new Canvas(bitmap); 460 content.draw(canvas); 461 462 // Do not recycle the old bitmap if such as it may be set as an empty 463 // state to any of the page views. Just let the GC take care of it. 464 mEmptyState = new BitmapDrawable(mContext.getResources(), bitmap); 465 } 466 467 private PageRange[] computeSelectedPages() { 468 ArrayList<PageRange> selectedPagesList = new ArrayList<>(); 469 470 int startPageIndex = INVALID_PAGE_INDEX; 471 int endPageIndex = INVALID_PAGE_INDEX; 472 473 final int pageCount = mConfirmedPagesInDocument.size(); 474 for (int i = 0; i < pageCount; i++) { 475 final int pageIndex = mConfirmedPagesInDocument.keyAt(i); 476 if (startPageIndex == INVALID_PAGE_INDEX) { 477 startPageIndex = endPageIndex = pageIndex; 478 } 479 if (endPageIndex + 1 < pageIndex) { 480 PageRange pageRange = new PageRange(startPageIndex, endPageIndex); 481 selectedPagesList.add(pageRange); 482 startPageIndex = pageIndex; 483 } 484 endPageIndex = pageIndex; 485 } 486 487 if (startPageIndex != INVALID_PAGE_INDEX 488 && endPageIndex != INVALID_PAGE_INDEX) { 489 PageRange pageRange = new PageRange(startPageIndex, endPageIndex); 490 selectedPagesList.add(pageRange); 491 } 492 493 PageRange[] selectedPages = new PageRange[selectedPagesList.size()]; 494 selectedPagesList.toArray(selectedPages); 495 496 return selectedPages; 497 } 498 499 public void destroy() { 500 throwIfNotClosed(); 501 doDestroy(); 502 } 503 504 @Override 505 protected void finalize() throws Throwable { 506 try { 507 if (mState != STATE_DESTROYED) { 508 mCloseGuard.warnIfOpen(); 509 doDestroy(); 510 } 511 } finally { 512 super.finalize(); 513 } 514 } 515 516 private int computePageIndexInDocument(int indexInAdapter) { 517 int skippedAdapterPages = 0; 518 final int selectedPagesCount = mSelectedPages.length; 519 for (int i = 0; i < selectedPagesCount; i++) { 520 PageRange pageRange = PageRangeUtils.asAbsoluteRange( 521 mSelectedPages[i], mDocumentPageCount); 522 skippedAdapterPages += pageRange.getSize(); 523 if (skippedAdapterPages > indexInAdapter) { 524 final int overshoot = skippedAdapterPages - indexInAdapter - 1; 525 return pageRange.getEnd() - overshoot; 526 } 527 } 528 return INVALID_PAGE_INDEX; 529 } 530 531 private int computePageIndexInFile(int pageIndexInDocument) { 532 if (!PageRangeUtils.contains(mSelectedPages, pageIndexInDocument)) { 533 return INVALID_PAGE_INDEX; 534 } 535 if (mWrittenPages == null) { 536 return INVALID_PAGE_INDEX; 537 } 538 539 int indexInFile = INVALID_PAGE_INDEX; 540 final int rangeCount = mWrittenPages.length; 541 for (int i = 0; i < rangeCount; i++) { 542 PageRange pageRange = mWrittenPages[i]; 543 if (!pageRange.contains(pageIndexInDocument)) { 544 indexInFile += pageRange.getSize(); 545 } else { 546 indexInFile += pageIndexInDocument - pageRange.getStart() + 1; 547 return indexInFile; 548 } 549 } 550 return INVALID_PAGE_INDEX; 551 } 552 553 private void setConfirmedPages(PageRange[] pagesInDocument, int documentPageCount) { 554 mConfirmedPagesInDocument.clear(); 555 final int rangeCount = pagesInDocument.length; 556 for (int i = 0; i < rangeCount; i++) { 557 PageRange pageRange = PageRangeUtils.asAbsoluteRange(pagesInDocument[i], 558 documentPageCount); 559 for (int j = pageRange.getStart(); j <= pageRange.getEnd(); j++) { 560 mConfirmedPagesInDocument.put(j, null); 561 } 562 } 563 } 564 565 private void onSelectedPageNotInFile(int pageInDocument) { 566 PageRange[] requestedPages = computeRequestedPages(pageInDocument); 567 if (!Arrays.equals(mRequestedPages, requestedPages)) { 568 mRequestedPages = requestedPages; 569 if (DEBUG) { 570 Log.i(LOG_TAG, "Requesting pages: " + Arrays.toString(mRequestedPages)); 571 } 572 mCallbacks.onRequestContentUpdate(); 573 } 574 } 575 576 private PageRange[] computeRequestedPages(int pageInDocument) { 577 if (mRequestedPages != null && 578 PageRangeUtils.contains(mRequestedPages, pageInDocument)) { 579 return mRequestedPages; 580 } 581 582 List<PageRange> pageRangesList = new ArrayList<>(); 583 584 int remainingPagesToRequest = MAX_PREVIEW_PAGES_BATCH; 585 final int selectedPagesCount = mSelectedPages.length; 586 587 // We always request the pages that are bound, i.e. shown on screen. 588 PageRange[] boundPagesInDocument = computeBoundPagesInDocument(); 589 590 final int boundRangeCount = boundPagesInDocument.length; 591 for (int i = 0; i < boundRangeCount; i++) { 592 PageRange boundRange = boundPagesInDocument[i]; 593 pageRangesList.add(boundRange); 594 } 595 remainingPagesToRequest -= PageRangeUtils.getNormalizedPageCount( 596 boundPagesInDocument, mDocumentPageCount); 597 598 final boolean requestFromStart = mRequestedPages == null 599 || pageInDocument > mRequestedPages[mRequestedPages.length - 1].getEnd(); 600 601 if (!requestFromStart) { 602 if (DEBUG) { 603 Log.i(LOG_TAG, "Requesting from end"); 604 } 605 606 // Reminder that ranges are always normalized. 607 for (int i = selectedPagesCount - 1; i >= 0; i--) { 608 if (remainingPagesToRequest <= 0) { 609 break; 610 } 611 612 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i], 613 mDocumentPageCount); 614 if (pageInDocument < selectedRange.getStart()) { 615 continue; 616 } 617 618 PageRange pagesInRange; 619 int rangeSpan; 620 621 if (selectedRange.contains(pageInDocument)) { 622 rangeSpan = pageInDocument - selectedRange.getStart() + 1; 623 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); 624 final int fromPage = Math.max(pageInDocument - rangeSpan - 1, 0); 625 rangeSpan = Math.max(rangeSpan, 0); 626 pagesInRange = new PageRange(fromPage, pageInDocument); 627 } else { 628 rangeSpan = selectedRange.getSize(); 629 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); 630 rangeSpan = Math.max(rangeSpan, 0); 631 final int fromPage = Math.max(selectedRange.getEnd() - rangeSpan - 1, 0); 632 final int toPage = selectedRange.getEnd(); 633 pagesInRange = new PageRange(fromPage, toPage); 634 } 635 636 pageRangesList.add(pagesInRange); 637 remainingPagesToRequest -= rangeSpan; 638 } 639 } else { 640 if (DEBUG) { 641 Log.i(LOG_TAG, "Requesting from start"); 642 } 643 644 // Reminder that ranges are always normalized. 645 for (int i = 0; i < selectedPagesCount; i++) { 646 if (remainingPagesToRequest <= 0) { 647 break; 648 } 649 650 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i], 651 mDocumentPageCount); 652 if (pageInDocument > selectedRange.getEnd()) { 653 continue; 654 } 655 656 PageRange pagesInRange; 657 int rangeSpan; 658 659 if (selectedRange.contains(pageInDocument)) { 660 rangeSpan = selectedRange.getEnd() - pageInDocument + 1; 661 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); 662 final int toPage = Math.min(pageInDocument + rangeSpan - 1, 663 mDocumentPageCount - 1); 664 pagesInRange = new PageRange(pageInDocument, toPage); 665 } else { 666 rangeSpan = selectedRange.getSize(); 667 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); 668 final int fromPage = selectedRange.getStart(); 669 final int toPage = Math.min(selectedRange.getStart() + rangeSpan - 1, 670 mDocumentPageCount - 1); 671 pagesInRange = new PageRange(fromPage, toPage); 672 } 673 674 if (DEBUG) { 675 Log.i(LOG_TAG, "computeRequestedPages() Adding range:" + pagesInRange); 676 } 677 pageRangesList.add(pagesInRange); 678 remainingPagesToRequest -= rangeSpan; 679 } 680 } 681 682 PageRange[] pageRanges = new PageRange[pageRangesList.size()]; 683 pageRangesList.toArray(pageRanges); 684 685 return PageRangeUtils.normalize(pageRanges); 686 } 687 688 private PageRange[] computeBoundPagesInDocument() { 689 List<PageRange> pagesInDocumentList = new ArrayList<>(); 690 691 int fromPage = INVALID_PAGE_INDEX; 692 int toPage = INVALID_PAGE_INDEX; 693 694 final int boundPageCount = mBoundPagesInAdapter.size(); 695 for (int i = 0; i < boundPageCount; i++) { 696 // The container is a sparse array, so keys are sorted in ascending order. 697 final int boundPageInAdapter = mBoundPagesInAdapter.keyAt(i); 698 final int boundPageInDocument = computePageIndexInDocument(boundPageInAdapter); 699 700 if (fromPage == INVALID_PAGE_INDEX) { 701 fromPage = boundPageInDocument; 702 } 703 704 if (toPage == INVALID_PAGE_INDEX) { 705 toPage = boundPageInDocument; 706 } 707 708 if (boundPageInDocument > toPage + 1) { 709 PageRange pageRange = new PageRange(fromPage, toPage); 710 pagesInDocumentList.add(pageRange); 711 fromPage = toPage = boundPageInDocument; 712 } else { 713 toPage = boundPageInDocument; 714 } 715 } 716 717 if (fromPage != INVALID_PAGE_INDEX && toPage != INVALID_PAGE_INDEX) { 718 PageRange pageRange = new PageRange(fromPage, toPage); 719 pagesInDocumentList.add(pageRange); 720 } 721 722 PageRange[] pageInDocument = new PageRange[pagesInDocumentList.size()]; 723 pagesInDocumentList.toArray(pageInDocument); 724 725 if (DEBUG) { 726 Log.i(LOG_TAG, "Bound pages: " + Arrays.toString(pageInDocument)); 727 } 728 729 return pageInDocument; 730 } 731 732 private void recyclePageView(PageContentView page, int pageIndexInAdapter) { 733 PageContentProvider provider = page.getPageContentProvider(); 734 if (provider != null) { 735 page.init(null, null, null, null); 736 mPageContentRepository.releasePageContentProvider(provider); 737 } 738 mBoundPagesInAdapter.remove(pageIndexInAdapter); 739 page.setTag(null); 740 } 741 742 public void startPreloadContent(PageRange pageRangeInAdapter) { 743 final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart()); 744 final int startPageInFile = computePageIndexInFile(startPageInDocument); 745 final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd()); 746 final int endPageInFile = computePageIndexInFile(endPageInDocument); 747 if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) { 748 mPageContentRepository.startPreload(startPageInFile, endPageInFile); 749 } 750 } 751 752 public void stopPreloadContent() { 753 mPageContentRepository.stopPreload(); 754 } 755 756 private void doDestroy() { 757 mPageContentRepository.destroy(); 758 mCloseGuard.close(); 759 mState = STATE_DESTROYED; 760 if (DEBUG) { 761 Log.i(LOG_TAG, "STATE_DESTROYED"); 762 } 763 } 764 765 private void throwIfNotOpened() { 766 if (mState != STATE_OPENED) { 767 throw new IllegalStateException("Not opened"); 768 } 769 } 770 771 private void throwIfNotClosed() { 772 if (mState != STATE_CLOSED) { 773 throw new IllegalStateException("Not closed"); 774 } 775 } 776 777 private final class MyViewHolder extends ViewHolder { 778 int mPageInAdapter; 779 780 private MyViewHolder(View itemView) { 781 super(itemView); 782 } 783 } 784 785 private final class PageClickListener implements OnClickListener { 786 @Override 787 public void onClick(View view) { 788 PreviewPageFrame page = (PreviewPageFrame) view; 789 MyViewHolder holder = (MyViewHolder) page.getTag(); 790 final int pageInAdapter = holder.mPageInAdapter; 791 final int pageInDocument = computePageIndexInDocument(pageInAdapter); 792 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) { 793 mConfirmedPagesInDocument.put(pageInDocument, null); 794 page.setSelected(true, true); 795 } else { 796 if (mConfirmedPagesInDocument.size() <= 1) { 797 return; 798 } 799 mConfirmedPagesInDocument.remove(pageInDocument); 800 page.setSelected(false, true); 801 } 802 } 803 } 804} 805