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