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