| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.printspooler.ui; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.ParcelFileDescriptor; |
| import android.print.PageRange; |
| import android.print.PrintAttributes.MediaSize; |
| import android.print.PrintAttributes.Margins; |
| import android.print.PrintDocumentInfo; |
| import android.support.v7.widget.RecyclerView.Adapter; |
| import android.support.v7.widget.RecyclerView.ViewHolder; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.View.MeasureSpec; |
| import android.widget.TextView; |
| import com.android.printspooler.R; |
| import com.android.printspooler.model.OpenDocumentCallback; |
| import com.android.printspooler.model.PageContentRepository; |
| import com.android.printspooler.model.PageContentRepository.PageContentProvider; |
| import com.android.printspooler.util.PageRangeUtils; |
| import com.android.printspooler.widget.PageContentView; |
| import com.android.printspooler.widget.PreviewPageFrame; |
| import dalvik.system.CloseGuard; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * This class represents the adapter for the pages in the print preview list. |
| */ |
| public final class PageAdapter extends Adapter<ViewHolder> { |
| private static final String LOG_TAG = "PageAdapter"; |
| |
| private static final int MAX_PREVIEW_PAGES_BATCH = 50; |
| |
| private static final boolean DEBUG = false; |
| |
| private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] { |
| PageRange.ALL_PAGES |
| }; |
| |
| private static final int INVALID_PAGE_INDEX = -1; |
| |
| private static final int STATE_CLOSED = 0; |
| private static final int STATE_OPENED = 1; |
| private static final int STATE_DESTROYED = 2; |
| |
| private final CloseGuard mCloseGuard = CloseGuard.get(); |
| |
| private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>(); |
| private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>(); |
| |
| private final PageClickListener mPageClickListener = new PageClickListener(); |
| |
| private final Context mContext; |
| private final LayoutInflater mLayoutInflater; |
| |
| private final ContentCallbacks mCallbacks; |
| private final PageContentRepository mPageContentRepository; |
| private final PreviewArea mPreviewArea; |
| |
| // Which document pages to be written. |
| private PageRange[] mRequestedPages; |
| // Pages written in the current file. |
| private PageRange[] mWrittenPages; |
| // Pages the user selected in the UI. |
| private PageRange[] mSelectedPages; |
| |
| private BitmapDrawable mEmptyState; |
| private BitmapDrawable mErrorState; |
| |
| private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; |
| private int mSelectedPageCount; |
| |
| private int mPreviewPageMargin; |
| private int mPreviewPageMinWidth; |
| private int mPreviewListPadding; |
| private int mFooterHeight; |
| |
| private int mColumnCount; |
| |
| private MediaSize mMediaSize; |
| private Margins mMinMargins; |
| |
| private int mState; |
| |
| private int mPageContentWidth; |
| private int mPageContentHeight; |
| |
| public interface ContentCallbacks { |
| public void onRequestContentUpdate(); |
| public void onMalformedPdfFile(); |
| public void onSecurePdfFile(); |
| } |
| |
| public interface PreviewArea { |
| public int getWidth(); |
| public int getHeight(); |
| public void setColumnCount(int columnCount); |
| public void setPadding(int left, int top, int right, int bottom); |
| } |
| |
| public PageAdapter(Context context, ContentCallbacks callbacks, PreviewArea previewArea) { |
| mContext = context; |
| mCallbacks = callbacks; |
| mLayoutInflater = (LayoutInflater) context.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| mPageContentRepository = new PageContentRepository(context); |
| |
| mPreviewPageMargin = mContext.getResources().getDimensionPixelSize( |
| R.dimen.preview_page_margin); |
| |
| mPreviewPageMinWidth = mContext.getResources().getDimensionPixelSize( |
| R.dimen.preview_page_min_width); |
| |
| mPreviewListPadding = mContext.getResources().getDimensionPixelSize( |
| R.dimen.preview_list_padding); |
| |
| mColumnCount = mContext.getResources().getInteger( |
| R.integer.preview_page_per_row_count); |
| |
| mFooterHeight = mContext.getResources().getDimensionPixelSize( |
| R.dimen.preview_page_footer_height); |
| |
| mPreviewArea = previewArea; |
| |
| mCloseGuard.open("destroy"); |
| |
| setHasStableIds(true); |
| |
| mState = STATE_CLOSED; |
| if (DEBUG) { |
| Log.i(LOG_TAG, "STATE_CLOSED"); |
| } |
| } |
| |
| public void onOrientationChanged() { |
| mColumnCount = mContext.getResources().getInteger( |
| R.integer.preview_page_per_row_count); |
| notifyDataSetChanged(); |
| } |
| |
| public boolean isOpened() { |
| return mState == STATE_OPENED; |
| } |
| |
| public int getFilePageCount() { |
| return mPageContentRepository.getFilePageCount(); |
| } |
| |
| public void open(ParcelFileDescriptor source, final Runnable callback) { |
| throwIfNotClosed(); |
| mState = STATE_OPENED; |
| if (DEBUG) { |
| Log.i(LOG_TAG, "STATE_OPENED"); |
| } |
| mPageContentRepository.open(source, new OpenDocumentCallback() { |
| @Override |
| public void onSuccess() { |
| notifyDataSetChanged(); |
| callback.run(); |
| } |
| |
| @Override |
| public void onFailure(int error) { |
| switch (error) { |
| case OpenDocumentCallback.ERROR_MALFORMED_PDF_FILE: { |
| mCallbacks.onMalformedPdfFile(); |
| } break; |
| |
| case OpenDocumentCallback.ERROR_SECURE_PDF_FILE: { |
| mCallbacks.onSecurePdfFile(); |
| } break; |
| } |
| } |
| }); |
| } |
| |
| public void update(PageRange[] writtenPages, PageRange[] selectedPages, |
| int documentPageCount, MediaSize mediaSize, Margins minMargins) { |
| boolean documentChanged = false; |
| boolean updatePreviewAreaAndPageSize = false; |
| boolean clearSelectedPages = false; |
| |
| // If the app does not tell how many pages are in the document we cannot |
| // optimize and ask for all pages whose count we get from the renderer. |
| if (documentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { |
| if (writtenPages == null) { |
| // If we already requested all pages, just wait. |
| if (!Arrays.equals(ALL_PAGES_ARRAY, mRequestedPages)) { |
| mRequestedPages = ALL_PAGES_ARRAY; |
| mCallbacks.onRequestContentUpdate(); |
| } |
| return; |
| } else { |
| documentPageCount = mPageContentRepository.getFilePageCount(); |
| if (documentPageCount <= 0) { |
| return; |
| } |
| } |
| } |
| |
| if (mDocumentPageCount != documentPageCount) { |
| mDocumentPageCount = documentPageCount; |
| documentChanged = true; |
| clearSelectedPages = true; |
| } |
| |
| if (mMediaSize == null || !mMediaSize.equals(mediaSize)) { |
| mMediaSize = mediaSize; |
| updatePreviewAreaAndPageSize = true; |
| documentChanged = true; |
| |
| clearSelectedPages = true; |
| } |
| |
| if (mMinMargins == null || !mMinMargins.equals(minMargins)) { |
| mMinMargins = minMargins; |
| updatePreviewAreaAndPageSize = true; |
| documentChanged = true; |
| |
| clearSelectedPages = true; |
| } |
| |
| if (clearSelectedPages) { |
| mSelectedPages = PageRange.ALL_PAGES_ARRAY; |
| mSelectedPageCount = documentPageCount; |
| setConfirmedPages(mSelectedPages, documentPageCount); |
| updatePreviewAreaAndPageSize = true; |
| documentChanged = true; |
| } else if (!Arrays.equals(mSelectedPages, selectedPages)) { |
| mSelectedPages = selectedPages; |
| mSelectedPageCount = PageRangeUtils.getNormalizedPageCount( |
| mSelectedPages, documentPageCount); |
| setConfirmedPages(mSelectedPages, documentPageCount); |
| updatePreviewAreaAndPageSize = true; |
| documentChanged = true; |
| } |
| |
| // If *all pages* is selected we need to convert that to absolute |
| // range as we will be checking if some pages are written or not. |
| if (writtenPages != null) { |
| // If we get all pages, this means all pages that we requested. |
| if (PageRangeUtils.isAllPages(writtenPages)) { |
| writtenPages = mRequestedPages; |
| } |
| if (!Arrays.equals(mWrittenPages, writtenPages)) { |
| // TODO: Do a surgical invalidation of only written pages changed. |
| mWrittenPages = writtenPages; |
| documentChanged = true; |
| } |
| } |
| |
| if (updatePreviewAreaAndPageSize) { |
| updatePreviewAreaPageSizeAndEmptyState(); |
| } |
| |
| if (documentChanged) { |
| notifyDataSetChanged(); |
| } |
| } |
| |
| public void close(Runnable callback) { |
| throwIfNotOpened(); |
| mState = STATE_CLOSED; |
| if (DEBUG) { |
| Log.i(LOG_TAG, "STATE_CLOSED"); |
| } |
| mPageContentRepository.close(callback); |
| } |
| |
| @Override |
| public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| View page = mLayoutInflater.inflate(R.layout.preview_page, parent, false); |
| return new MyViewHolder(page); |
| } |
| |
| @Override |
| public void onBindViewHolder(ViewHolder holder, int position) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Binding holder: " + holder + " with id: " + getItemId(position) |
| + " for position: " + position); |
| } |
| |
| MyViewHolder myHolder = (MyViewHolder) holder; |
| |
| PreviewPageFrame page = (PreviewPageFrame) holder.itemView; |
| page.setOnClickListener(mPageClickListener); |
| |
| page.setTag(holder); |
| |
| myHolder.mPageInAdapter = position; |
| |
| final int pageInDocument = computePageIndexInDocument(position); |
| final int pageIndexInFile = computePageIndexInFile(pageInDocument); |
| |
| PageContentView content = (PageContentView) page.findViewById(R.id.page_content); |
| |
| LayoutParams params = content.getLayoutParams(); |
| params.width = mPageContentWidth; |
| params.height = mPageContentHeight; |
| |
| PageContentProvider provider = content.getPageContentProvider(); |
| |
| if (pageIndexInFile != INVALID_PAGE_INDEX) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Binding provider:" |
| + " pageIndexInAdapter: " + position |
| + ", pageInDocument: " + pageInDocument |
| + ", pageIndexInFile: " + pageIndexInFile); |
| } |
| |
| provider = mPageContentRepository.acquirePageContentProvider( |
| pageIndexInFile, content); |
| mBoundPagesInAdapter.put(position, null); |
| } else { |
| onSelectedPageNotInFile(pageInDocument); |
| } |
| content.init(provider, mEmptyState, mErrorState, mMediaSize, mMinMargins); |
| |
| if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) { |
| page.setSelected(true, false); |
| } else { |
| page.setSelected(false, false); |
| } |
| |
| page.setContentDescription(mContext.getString(R.string.page_description_template, |
| pageInDocument + 1, mDocumentPageCount)); |
| |
| TextView pageNumberView = (TextView) page.findViewById(R.id.page_number); |
| String text = mContext.getString(R.string.current_page_template, |
| pageInDocument + 1, mDocumentPageCount); |
| pageNumberView.setText(text); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mSelectedPageCount; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return computePageIndexInDocument(position); |
| } |
| |
| @Override |
| public void onViewRecycled(ViewHolder holder) { |
| MyViewHolder myHolder = (MyViewHolder) holder; |
| PageContentView content = (PageContentView) holder.itemView |
| .findViewById(R.id.page_content); |
| recyclePageView(content, myHolder.mPageInAdapter); |
| myHolder.mPageInAdapter = INVALID_PAGE_INDEX; |
| } |
| |
| public PageRange[] getRequestedPages() { |
| return mRequestedPages; |
| } |
| |
| public PageRange[] getSelectedPages() { |
| PageRange[] selectedPages = computeSelectedPages(); |
| if (!Arrays.equals(mSelectedPages, selectedPages)) { |
| mSelectedPages = selectedPages; |
| mSelectedPageCount = PageRangeUtils.getNormalizedPageCount( |
| mSelectedPages, mDocumentPageCount); |
| updatePreviewAreaPageSizeAndEmptyState(); |
| notifyDataSetChanged(); |
| } |
| return mSelectedPages; |
| } |
| |
| public void onPreviewAreaSizeChanged() { |
| if (mMediaSize != null) { |
| updatePreviewAreaPageSizeAndEmptyState(); |
| notifyDataSetChanged(); |
| } |
| } |
| |
| private void updatePreviewAreaPageSizeAndEmptyState() { |
| if (mMediaSize == null) { |
| return; |
| } |
| |
| final int availableWidth = mPreviewArea.getWidth(); |
| final int availableHeight = mPreviewArea.getHeight(); |
| |
| // Page aspect ratio to keep. |
| final float pageAspectRatio = (float) mMediaSize.getWidthMils() |
| / mMediaSize.getHeightMils(); |
| |
| // Make sure we have no empty columns. |
| final int columnCount = Math.min(mSelectedPageCount, mColumnCount); |
| mPreviewArea.setColumnCount(columnCount); |
| |
| // Compute max page width. |
| final int horizontalMargins = 2 * columnCount * mPreviewPageMargin; |
| final int horizontalPaddingAndMargins = horizontalMargins + 2 * mPreviewListPadding; |
| final int pageContentDesiredWidth = (int) ((((float) availableWidth |
| - horizontalPaddingAndMargins) / columnCount) + 0.5f); |
| |
| // Compute max page height. |
| final int pageContentDesiredHeight = (int) ((pageContentDesiredWidth |
| / pageAspectRatio) + 0.5f); |
| |
| // If the page does not fit entirely in a vertical direction, |
| // we shirk it but not less than the minimal page width. |
| final int pageContentMinHeight = (int) (mPreviewPageMinWidth / pageAspectRatio + 0.5f); |
| final int pageContentMaxHeight = Math.max(pageContentMinHeight, |
| availableHeight - 2 * (mPreviewListPadding + mPreviewPageMargin) - mFooterHeight); |
| |
| mPageContentHeight = Math.min(pageContentDesiredHeight, pageContentMaxHeight); |
| mPageContentWidth = (int) ((mPageContentHeight * pageAspectRatio) + 0.5f); |
| |
| final int totalContentWidth = columnCount * mPageContentWidth + horizontalMargins; |
| final int horizontalPadding = (availableWidth - totalContentWidth) / 2; |
| |
| final int rowCount = mSelectedPageCount / columnCount |
| + ((mSelectedPageCount % columnCount) > 0 ? 1 : 0); |
| final int totalContentHeight = rowCount * (mPageContentHeight + mFooterHeight + 2 |
| * mPreviewPageMargin); |
| |
| final int verticalPadding; |
| if (mPageContentHeight + mFooterHeight + mPreviewListPadding |
| + 2 * mPreviewPageMargin > availableHeight) { |
| verticalPadding = Math.max(0, |
| (availableHeight - mPageContentHeight - mFooterHeight) / 2 |
| - mPreviewPageMargin); |
| } else { |
| verticalPadding = Math.max(mPreviewListPadding, |
| (availableHeight - totalContentHeight) / 2); |
| } |
| |
| mPreviewArea.setPadding(horizontalPadding, verticalPadding, |
| horizontalPadding, verticalPadding); |
| |
| // Now update the empty state drawable, as it depends on the page |
| // size and is reused for all views for better performance. |
| LayoutInflater inflater = LayoutInflater.from(mContext); |
| View loadingContent = inflater.inflate(R.layout.preview_page_loading, null, false); |
| loadingContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), |
| MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY)); |
| loadingContent.layout(0, 0, loadingContent.getMeasuredWidth(), |
| loadingContent.getMeasuredHeight()); |
| |
| Bitmap loadingBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, |
| Bitmap.Config.ARGB_8888); |
| loadingContent.draw(new Canvas(loadingBitmap)); |
| |
| // Do not recycle the old bitmap if such as it may be set as an empty |
| // state to any of the page views. Just let the GC take care of it. |
| mEmptyState = new BitmapDrawable(mContext.getResources(), loadingBitmap); |
| |
| // Now update the empty state drawable, as it depends on the page |
| // size and is reused for all views for better performance. |
| View errorContent = inflater.inflate(R.layout.preview_page_error, null, false); |
| errorContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), |
| MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY)); |
| errorContent.layout(0, 0, errorContent.getMeasuredWidth(), |
| errorContent.getMeasuredHeight()); |
| |
| Bitmap errorBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, |
| Bitmap.Config.ARGB_8888); |
| errorContent.draw(new Canvas(errorBitmap)); |
| |
| // Do not recycle the old bitmap if such as it may be set as an error |
| // state to any of the page views. Just let the GC take care of it. |
| mErrorState = new BitmapDrawable(mContext.getResources(), errorBitmap); |
| } |
| |
| private PageRange[] computeSelectedPages() { |
| ArrayList<PageRange> selectedPagesList = new ArrayList<>(); |
| |
| int startPageIndex = INVALID_PAGE_INDEX; |
| int endPageIndex = INVALID_PAGE_INDEX; |
| |
| final int pageCount = mConfirmedPagesInDocument.size(); |
| for (int i = 0; i < pageCount; i++) { |
| final int pageIndex = mConfirmedPagesInDocument.keyAt(i); |
| if (startPageIndex == INVALID_PAGE_INDEX) { |
| startPageIndex = endPageIndex = pageIndex; |
| } |
| if (endPageIndex + 1 < pageIndex) { |
| PageRange pageRange = new PageRange(startPageIndex, endPageIndex); |
| selectedPagesList.add(pageRange); |
| startPageIndex = pageIndex; |
| } |
| endPageIndex = pageIndex; |
| } |
| |
| if (startPageIndex != INVALID_PAGE_INDEX |
| && endPageIndex != INVALID_PAGE_INDEX) { |
| PageRange pageRange = new PageRange(startPageIndex, endPageIndex); |
| selectedPagesList.add(pageRange); |
| } |
| |
| PageRange[] selectedPages = new PageRange[selectedPagesList.size()]; |
| selectedPagesList.toArray(selectedPages); |
| |
| return selectedPages; |
| } |
| |
| public void destroy(Runnable callback) { |
| mCloseGuard.close(); |
| mState = STATE_DESTROYED; |
| if (DEBUG) { |
| Log.i(LOG_TAG, "STATE_DESTROYED"); |
| } |
| mPageContentRepository.destroy(callback); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| if (mState != STATE_DESTROYED) { |
| mCloseGuard.warnIfOpen(); |
| destroy(null); |
| } |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| private int computePageIndexInDocument(int indexInAdapter) { |
| int skippedAdapterPages = 0; |
| final int selectedPagesCount = mSelectedPages.length; |
| for (int i = 0; i < selectedPagesCount; i++) { |
| PageRange pageRange = PageRangeUtils.asAbsoluteRange( |
| mSelectedPages[i], mDocumentPageCount); |
| skippedAdapterPages += pageRange.getSize(); |
| if (skippedAdapterPages > indexInAdapter) { |
| final int overshoot = skippedAdapterPages - indexInAdapter - 1; |
| return pageRange.getEnd() - overshoot; |
| } |
| } |
| return INVALID_PAGE_INDEX; |
| } |
| |
| private int computePageIndexInFile(int pageIndexInDocument) { |
| if (!PageRangeUtils.contains(mSelectedPages, pageIndexInDocument)) { |
| return INVALID_PAGE_INDEX; |
| } |
| if (mWrittenPages == null) { |
| return INVALID_PAGE_INDEX; |
| } |
| |
| int indexInFile = INVALID_PAGE_INDEX; |
| final int rangeCount = mWrittenPages.length; |
| for (int i = 0; i < rangeCount; i++) { |
| PageRange pageRange = mWrittenPages[i]; |
| if (!pageRange.contains(pageIndexInDocument)) { |
| indexInFile += pageRange.getSize(); |
| } else { |
| indexInFile += pageIndexInDocument - pageRange.getStart() + 1; |
| return indexInFile; |
| } |
| } |
| return INVALID_PAGE_INDEX; |
| } |
| |
| private void setConfirmedPages(PageRange[] pagesInDocument, int documentPageCount) { |
| mConfirmedPagesInDocument.clear(); |
| final int rangeCount = pagesInDocument.length; |
| for (int i = 0; i < rangeCount; i++) { |
| PageRange pageRange = PageRangeUtils.asAbsoluteRange(pagesInDocument[i], |
| documentPageCount); |
| for (int j = pageRange.getStart(); j <= pageRange.getEnd(); j++) { |
| mConfirmedPagesInDocument.put(j, null); |
| } |
| } |
| } |
| |
| private void onSelectedPageNotInFile(int pageInDocument) { |
| PageRange[] requestedPages = computeRequestedPages(pageInDocument); |
| if (!Arrays.equals(mRequestedPages, requestedPages)) { |
| mRequestedPages = requestedPages; |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Requesting pages: " + Arrays.toString(mRequestedPages)); |
| } |
| |
| // This call might come from a recylerview that is currently updating. Hence delay to |
| // after the update |
| (new Handler(Looper.getMainLooper())).post(new Runnable() { |
| @Override public void run() { |
| mCallbacks.onRequestContentUpdate(); |
| } |
| }); |
| } |
| } |
| |
| private PageRange[] computeRequestedPages(int pageInDocument) { |
| if (mRequestedPages != null && |
| PageRangeUtils.contains(mRequestedPages, pageInDocument)) { |
| return mRequestedPages; |
| } |
| |
| List<PageRange> pageRangesList = new ArrayList<>(); |
| |
| int remainingPagesToRequest = MAX_PREVIEW_PAGES_BATCH; |
| final int selectedPagesCount = mSelectedPages.length; |
| |
| // We always request the pages that are bound, i.e. shown on screen. |
| PageRange[] boundPagesInDocument = computeBoundPagesInDocument(); |
| |
| final int boundRangeCount = boundPagesInDocument.length; |
| for (int i = 0; i < boundRangeCount; i++) { |
| PageRange boundRange = boundPagesInDocument[i]; |
| pageRangesList.add(boundRange); |
| } |
| remainingPagesToRequest -= PageRangeUtils.getNormalizedPageCount( |
| boundPagesInDocument, mDocumentPageCount); |
| |
| final boolean requestFromStart = mRequestedPages == null |
| || pageInDocument > mRequestedPages[mRequestedPages.length - 1].getEnd(); |
| |
| if (!requestFromStart) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Requesting from end"); |
| } |
| |
| // Reminder that ranges are always normalized. |
| for (int i = selectedPagesCount - 1; i >= 0; i--) { |
| if (remainingPagesToRequest <= 0) { |
| break; |
| } |
| |
| PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i], |
| mDocumentPageCount); |
| if (pageInDocument < selectedRange.getStart()) { |
| continue; |
| } |
| |
| PageRange pagesInRange; |
| int rangeSpan; |
| |
| if (selectedRange.contains(pageInDocument)) { |
| rangeSpan = pageInDocument - selectedRange.getStart() + 1; |
| rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); |
| final int fromPage = Math.max(pageInDocument - rangeSpan - 1, 0); |
| rangeSpan = Math.max(rangeSpan, 0); |
| pagesInRange = new PageRange(fromPage, pageInDocument); |
| } else { |
| rangeSpan = selectedRange.getSize(); |
| rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); |
| rangeSpan = Math.max(rangeSpan, 0); |
| final int fromPage = Math.max(selectedRange.getEnd() - rangeSpan - 1, 0); |
| final int toPage = selectedRange.getEnd(); |
| pagesInRange = new PageRange(fromPage, toPage); |
| } |
| |
| pageRangesList.add(pagesInRange); |
| remainingPagesToRequest -= rangeSpan; |
| } |
| } else { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Requesting from start"); |
| } |
| |
| // Reminder that ranges are always normalized. |
| for (int i = 0; i < selectedPagesCount; i++) { |
| if (remainingPagesToRequest <= 0) { |
| break; |
| } |
| |
| PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i], |
| mDocumentPageCount); |
| if (pageInDocument > selectedRange.getEnd()) { |
| continue; |
| } |
| |
| PageRange pagesInRange; |
| int rangeSpan; |
| |
| if (selectedRange.contains(pageInDocument)) { |
| rangeSpan = selectedRange.getEnd() - pageInDocument + 1; |
| rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); |
| final int toPage = Math.min(pageInDocument + rangeSpan - 1, |
| mDocumentPageCount - 1); |
| pagesInRange = new PageRange(pageInDocument, toPage); |
| } else { |
| rangeSpan = selectedRange.getSize(); |
| rangeSpan = Math.min(rangeSpan, remainingPagesToRequest); |
| final int fromPage = selectedRange.getStart(); |
| final int toPage = Math.min(selectedRange.getStart() + rangeSpan - 1, |
| mDocumentPageCount - 1); |
| pagesInRange = new PageRange(fromPage, toPage); |
| } |
| |
| if (DEBUG) { |
| Log.i(LOG_TAG, "computeRequestedPages() Adding range:" + pagesInRange); |
| } |
| pageRangesList.add(pagesInRange); |
| remainingPagesToRequest -= rangeSpan; |
| } |
| } |
| |
| PageRange[] pageRanges = new PageRange[pageRangesList.size()]; |
| pageRangesList.toArray(pageRanges); |
| |
| return PageRangeUtils.normalize(pageRanges); |
| } |
| |
| private PageRange[] computeBoundPagesInDocument() { |
| List<PageRange> pagesInDocumentList = new ArrayList<>(); |
| |
| int fromPage = INVALID_PAGE_INDEX; |
| int toPage = INVALID_PAGE_INDEX; |
| |
| final int boundPageCount = mBoundPagesInAdapter.size(); |
| for (int i = 0; i < boundPageCount; i++) { |
| // The container is a sparse array, so keys are sorted in ascending order. |
| final int boundPageInAdapter = mBoundPagesInAdapter.keyAt(i); |
| final int boundPageInDocument = computePageIndexInDocument(boundPageInAdapter); |
| |
| if (fromPage == INVALID_PAGE_INDEX) { |
| fromPage = boundPageInDocument; |
| } |
| |
| if (toPage == INVALID_PAGE_INDEX) { |
| toPage = boundPageInDocument; |
| } |
| |
| if (boundPageInDocument > toPage + 1) { |
| PageRange pageRange = new PageRange(fromPage, toPage); |
| pagesInDocumentList.add(pageRange); |
| fromPage = toPage = boundPageInDocument; |
| } else { |
| toPage = boundPageInDocument; |
| } |
| } |
| |
| if (fromPage != INVALID_PAGE_INDEX && toPage != INVALID_PAGE_INDEX) { |
| PageRange pageRange = new PageRange(fromPage, toPage); |
| pagesInDocumentList.add(pageRange); |
| } |
| |
| PageRange[] pageInDocument = new PageRange[pagesInDocumentList.size()]; |
| pagesInDocumentList.toArray(pageInDocument); |
| |
| if (DEBUG) { |
| Log.i(LOG_TAG, "Bound pages: " + Arrays.toString(pageInDocument)); |
| } |
| |
| return pageInDocument; |
| } |
| |
| private void recyclePageView(PageContentView page, int pageIndexInAdapter) { |
| PageContentProvider provider = page.getPageContentProvider(); |
| if (provider != null) { |
| page.init(null, mEmptyState, mErrorState, mMediaSize, mMinMargins); |
| mPageContentRepository.releasePageContentProvider(provider); |
| } |
| mBoundPagesInAdapter.remove(pageIndexInAdapter); |
| page.setTag(null); |
| } |
| |
| public void startPreloadContent(PageRange pageRangeInAdapter) { |
| final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart()); |
| final int startPageInFile = computePageIndexInFile(startPageInDocument); |
| final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd()); |
| final int endPageInFile = computePageIndexInFile(endPageInDocument); |
| if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) { |
| mPageContentRepository.startPreload(startPageInFile, endPageInFile); |
| } |
| } |
| |
| public void stopPreloadContent() { |
| mPageContentRepository.stopPreload(); |
| } |
| |
| private void throwIfNotOpened() { |
| if (mState != STATE_OPENED) { |
| throw new IllegalStateException("Not opened"); |
| } |
| } |
| |
| private void throwIfNotClosed() { |
| if (mState != STATE_CLOSED) { |
| throw new IllegalStateException("Not closed"); |
| } |
| } |
| |
| private final class MyViewHolder extends ViewHolder { |
| int mPageInAdapter; |
| |
| private MyViewHolder(View itemView) { |
| super(itemView); |
| } |
| } |
| |
| private final class PageClickListener implements OnClickListener { |
| @Override |
| public void onClick(View view) { |
| PreviewPageFrame page = (PreviewPageFrame) view; |
| MyViewHolder holder = (MyViewHolder) page.getTag(); |
| final int pageInAdapter = holder.mPageInAdapter; |
| final int pageInDocument = computePageIndexInDocument(pageInAdapter); |
| if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) { |
| mConfirmedPagesInDocument.put(pageInDocument, null); |
| page.setSelected(true, true); |
| } else { |
| if (mConfirmedPagesInDocument.size() <= 1) { |
| return; |
| } |
| mConfirmedPagesInDocument.remove(pageInDocument); |
| page.setSelected(false, true); |
| } |
| } |
| } |
| } |