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