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