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