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.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