blob: da8160ac81417a0ca7179019d8f5e4372f8ddf75 [file] [log] [blame]
Svet Ganov525a66b2014-06-14 22:29:00 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.printspooler.ui;
18
19import android.content.Context;
Svetoslav6552bf32014-09-03 21:15:55 -070020import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.drawable.BitmapDrawable;
Svet Ganov525a66b2014-06-14 22:29:00 -070023import android.os.ParcelFileDescriptor;
24import android.print.PageRange;
25import android.print.PrintAttributes.MediaSize;
26import android.print.PrintAttributes.Margins;
27import android.print.PrintDocumentInfo;
28import android.support.v7.widget.RecyclerView.Adapter;
29import android.support.v7.widget.RecyclerView.ViewHolder;
30import android.util.Log;
31import android.util.SparseArray;
Svet Ganov525a66b2014-06-14 22:29:00 -070032import android.view.LayoutInflater;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.view.ViewGroup;
36import android.view.ViewGroup.LayoutParams;
Svetoslav6552bf32014-09-03 21:15:55 -070037import android.view.View.MeasureSpec;
Svet Ganov525a66b2014-06-14 22:29:00 -070038import android.widget.TextView;
39import com.android.printspooler.R;
40import com.android.printspooler.model.PageContentRepository;
41import com.android.printspooler.model.PageContentRepository.PageContentProvider;
42import com.android.printspooler.util.PageRangeUtils;
43import com.android.printspooler.widget.PageContentView;
Svetoslave652b022014-09-09 22:11:10 -070044import com.android.printspooler.widget.PreviewPageFrame;
Svet Ganov525a66b2014-06-14 22:29:00 -070045import dalvik.system.CloseGuard;
46
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.List;
50
51/**
52 * This class represents the adapter for the pages in the print preview list.
53 */
Svetoslav5ef522b2014-07-23 20:15:09 -070054public final class PageAdapter extends Adapter implements
Svetoslav6552bf32014-09-03 21:15:55 -070055 PageContentRepository.OnMalformedPdfFileListener {
Svet Ganov525a66b2014-06-14 22:29:00 -070056 private static final String LOG_TAG = "PageAdapter";
57
58 private static final int MAX_PREVIEW_PAGES_BATCH = 50;
59
Svetoslavf3f963b2014-09-15 21:03:28 -070060 private static final boolean DEBUG = false;
Svet Ganov525a66b2014-06-14 22:29:00 -070061
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<>();
Svet Ganov525a66b2014-06-14 22:29:00 -070075 private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>();
76
77 private final PageClickListener mPageClickListener = new PageClickListener();
78
Svetoslav15cbc8a2014-07-11 09:45:07 -070079 private final Context mContext;
Svet Ganov525a66b2014-06-14 22:29:00 -070080 private final LayoutInflater mLayoutInflater;
81
Svetoslav5ef522b2014-07-23 20:15:09 -070082 private final ContentCallbacks mCallbacks;
Svet Ganov525a66b2014-06-14 22:29:00 -070083 private final PageContentRepository mPageContentRepository;
Svet Ganov525a66b2014-06-14 22:29:00 -070084 private final PreviewArea mPreviewArea;
85
Svet Ganov525a66b2014-06-14 22:29:00 -070086 // Which document pages to be written.
87 private PageRange[] mRequestedPages;
Svet Ganov525a66b2014-06-14 22:29:00 -070088 // Pages written in the current file.
89 private PageRange[] mWrittenPages;
Svet Ganov525a66b2014-06-14 22:29:00 -070090 // Pages the user selected in the UI.
91 private PageRange[] mSelectedPages;
92
Svetoslav6552bf32014-09-03 21:15:55 -070093 private BitmapDrawable mEmptyState;
94
Svetoslav15cbc8a2014-07-11 09:45:07 -070095 private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
96 private int mSelectedPageCount;
Svet Ganov525a66b2014-06-14 22:29:00 -070097
Svet Ganov525a66b2014-06-14 22:29:00 -070098 private int mPreviewPageMargin;
Svet Ganov38781bd2014-08-12 00:57:07 -070099 private int mPreviewPageMinWidth;
Svet Ganov525a66b2014-06-14 22:29:00 -0700100 private int mPreviewListPadding;
Svet Ganov525a66b2014-06-14 22:29:00 -0700101 private int mFooterHeight;
102
103 private int mColumnCount;
104
105 private MediaSize mMediaSize;
Svet Ganov525a66b2014-06-14 22:29:00 -0700106 private Margins mMinMargins;
107
108 private int mState;
109
110 private int mPageContentWidth;
Svet Ganov525a66b2014-06-14 22:29:00 -0700111 private int mPageContentHeight;
112
Svetoslav5ef522b2014-07-23 20:15:09 -0700113 public interface ContentCallbacks {
114 public void onRequestContentUpdate();
115 public void onMalformedPdfFile();
Svet Ganov525a66b2014-06-14 22:29:00 -0700116 }
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
Svetoslav5ef522b2014-07-23 20:15:09 -0700125 public PageAdapter(Context context, ContentCallbacks callbacks, PreviewArea previewArea) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700126 mContext = context;
Svetoslav5ef522b2014-07-23 20:15:09 -0700127 mCallbacks = callbacks;
Svet Ganov525a66b2014-06-14 22:29:00 -0700128 mLayoutInflater = (LayoutInflater) context.getSystemService(
129 Context.LAYOUT_INFLATER_SERVICE);
Svetoslav5ef522b2014-07-23 20:15:09 -0700130 mPageContentRepository = new PageContentRepository(context, this);
Svet Ganov525a66b2014-06-14 22:29:00 -0700131
Svet Ganov525a66b2014-06-14 22:29:00 -0700132 mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
133 R.dimen.preview_page_margin);
134
Svet Ganov38781bd2014-08-12 00:57:07 -0700135 mPreviewPageMinWidth = mContext.getResources().getDimensionPixelSize(
136 R.dimen.preview_page_min_width);
137
Svet Ganov525a66b2014-06-14 22:29:00 -0700138 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
Svet Ganov38781bd2014-08-12 00:57:07 -0700144 mFooterHeight = mContext.getResources().getDimensionPixelSize(
145 R.dimen.preview_page_footer_height);
Svet Ganov525a66b2014-06-14 22:29:00 -0700146
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
Svetoslav5ef522b2014-07-23 20:15:09 -0700159 @Override
160 public void onMalformedPdfFile() {
161 mCallbacks.onMalformedPdfFile();
162 }
163
Svet Ganov525a66b2014-06-14 22:29:00 -0700164 public void onOrientationChanged() {
165 mColumnCount = mContext.getResources().getInteger(
166 R.integer.preview_page_per_row_count);
Svetoslav139ba7f2014-09-12 10:35:26 -0700167 notifyDataSetChanged();
Svet Ganov525a66b2014-06-14 22:29:00 -0700168 }
169
170 public boolean isOpened() {
171 return mState == STATE_OPENED;
172 }
173
174 public int getFilePageCount() {
175 return mPageContentRepository.getFilePageCount();
176 }
177
Svetoslavf3f963b2014-09-15 21:03:28 -0700178 public void open(ParcelFileDescriptor source, final Runnable callback) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700179 throwIfNotClosed();
180 mState = STATE_OPENED;
181 if (DEBUG) {
182 Log.i(LOG_TAG, "STATE_OPENED");
183 }
Svetoslavf3f963b2014-09-15 21:03:28 -0700184 mPageContentRepository.open(source, new Runnable() {
185 @Override
186 public void run() {
187 notifyDataSetChanged();
188 callback.run();
189 }
190 });
Svet Ganov525a66b2014-06-14 22:29:00 -0700191 }
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;
Svetoslav5ef522b2014-07-23 20:15:09 -0700205 mCallbacks.onRequestContentUpdate();
Svet Ganov525a66b2014-06-14 22:29:00 -0700206 }
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) {
Svetoslav6552bf32014-09-03 21:15:55 -0700257 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700258 }
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);
Svetoslav0d2d9632014-09-17 15:45:16 -0700277 return new MyViewHolder(page);
Svet Ganov525a66b2014-06-14 22:29:00 -0700278 }
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
Svet Ganov525a66b2014-06-14 22:29:00 -0700287 MyViewHolder myHolder = (MyViewHolder) holder;
288
Svetoslave652b022014-09-09 22:11:10 -0700289 PreviewPageFrame page = (PreviewPageFrame) holder.itemView;
Svetoslavc404cac2014-08-27 18:37:16 -0700290 page.setOnClickListener(mPageClickListener);
291
Svet Ganov525a66b2014-06-14 22:29:00 -0700292 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
Svetoslav0d2d9632014-09-17 15:45:16 -0700315 if (provider != null && provider.getPageIndex() != pageIndexInFile) {
316 mPageContentRepository.releasePageContentProvider(provider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700317 }
318
319 provider = mPageContentRepository.acquirePageContentProvider(
320 pageIndexInFile, content);
321 mBoundPagesInAdapter.put(position, null);
322 } else {
323 onSelectedPageNotInFile(pageInDocument);
324 }
Svetoslav6552bf32014-09-03 21:15:55 -0700325 content.init(provider, mEmptyState, mMediaSize, mMinMargins);
Svet Ganov525a66b2014-06-14 22:29:00 -0700326
Svet Ganov525a66b2014-06-14 22:29:00 -0700327 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
Svetoslave652b022014-09-09 22:11:10 -0700328 page.setSelected(true, false);
Svet Ganov525a66b2014-06-14 22:29:00 -0700329 } else {
Svetoslave652b022014-09-09 22:11:10 -0700330 page.setSelected(false, false);
Svet Ganov525a66b2014-06-14 22:29:00 -0700331 }
332
Svetoslave652b022014-09-09 22:11:10 -0700333 page.setContentDescription(mContext.getString(R.string.page_description_template,
334 pageInDocument + 1, mDocumentPageCount));
335
Svet Ganov525a66b2014-06-14 22:29:00 -0700336 TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
337 String text = mContext.getString(R.string.current_page_template,
338 pageInDocument + 1, mDocumentPageCount);
339 pageNumberView.setText(text);
340 }
341
342 @Override
343 public int getItemCount() {
344 return mSelectedPageCount;
345 }
346
347 @Override
348 public long getItemId(int position) {
349 return computePageIndexInDocument(position);
350 }
351
352 @Override
353 public void onViewRecycled(ViewHolder holder) {
354 MyViewHolder myHolder = (MyViewHolder) holder;
355 PageContentView content = (PageContentView) holder.itemView
356 .findViewById(R.id.page_content);
357 recyclePageView(content, myHolder.mPageInAdapter);
358 myHolder.mPageInAdapter = INVALID_PAGE_INDEX;
359 }
360
361 public PageRange[] getRequestedPages() {
362 return mRequestedPages;
363 }
364
365 public PageRange[] getSelectedPages() {
366 PageRange[] selectedPages = computeSelectedPages();
367 if (!Arrays.equals(mSelectedPages, selectedPages)) {
368 mSelectedPages = selectedPages;
369 mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
370 mSelectedPages, mDocumentPageCount);
Svetoslav6552bf32014-09-03 21:15:55 -0700371 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700372 notifyDataSetChanged();
373 }
374 return mSelectedPages;
375 }
376
377 public void onPreviewAreaSizeChanged() {
378 if (mMediaSize != null) {
Svetoslav6552bf32014-09-03 21:15:55 -0700379 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700380 notifyDataSetChanged();
381 }
382 }
383
Svetoslav6552bf32014-09-03 21:15:55 -0700384 private void updatePreviewAreaPageSizeAndEmptyState() {
Svet Ganove771caf2014-09-14 20:29:27 -0700385 if (mMediaSize == null) {
386 return;
387 }
388
Svet Ganov525a66b2014-06-14 22:29:00 -0700389 final int availableWidth = mPreviewArea.getWidth();
390 final int availableHeight = mPreviewArea.getHeight();
391
392 // Page aspect ratio to keep.
393 final float pageAspectRatio = (float) mMediaSize.getWidthMils()
394 / mMediaSize.getHeightMils();
395
396 // Make sure we have no empty columns.
397 final int columnCount = Math.min(mSelectedPageCount, mColumnCount);
398 mPreviewArea.setColumnCount(columnCount);
399
400 // Compute max page width.
401 final int horizontalMargins = 2 * columnCount * mPreviewPageMargin;
402 final int horizontalPaddingAndMargins = horizontalMargins + 2 * mPreviewListPadding;
403 final int pageContentDesiredWidth = (int) ((((float) availableWidth
404 - horizontalPaddingAndMargins) / columnCount) + 0.5f);
405
406 // Compute max page height.
407 final int pageContentDesiredHeight = (int) (((float) pageContentDesiredWidth
408 / pageAspectRatio) + 0.5f);
Svet Ganov38781bd2014-08-12 00:57:07 -0700409
Svetoslav6552bf32014-09-03 21:15:55 -0700410 // If the page does not fit entirely in a vertical direction,
Svet Ganov38781bd2014-08-12 00:57:07 -0700411 // we shirk it but not less than the minimal page width.
412 final int pageContentMinHeight = (int) (mPreviewPageMinWidth / pageAspectRatio + 0.5f);
413 final int pageContentMaxHeight = Math.max(pageContentMinHeight,
414 availableHeight - 2 * (mPreviewListPadding + mPreviewPageMargin) - mFooterHeight);
Svet Ganov525a66b2014-06-14 22:29:00 -0700415
416 mPageContentHeight = Math.min(pageContentDesiredHeight, pageContentMaxHeight);
417 mPageContentWidth = (int) ((mPageContentHeight * pageAspectRatio) + 0.5f);
418
419 final int totalContentWidth = columnCount * mPageContentWidth + horizontalMargins;
420 final int horizontalPadding = (availableWidth - totalContentWidth) / 2;
421
422 final int rowCount = mSelectedPageCount / columnCount
423 + ((mSelectedPageCount % columnCount) > 0 ? 1 : 0);
Svet Ganov38781bd2014-08-12 00:57:07 -0700424 final int totalContentHeight = rowCount * (mPageContentHeight + mFooterHeight + 2
Svet Ganov525a66b2014-06-14 22:29:00 -0700425 * mPreviewPageMargin);
Svet Ganov38781bd2014-08-12 00:57:07 -0700426
427 final int verticalPadding;
Svetoslavdfa3e7c2014-09-09 19:01:56 -0700428 if (mPageContentHeight + mFooterHeight + mPreviewListPadding
429 + 2 * mPreviewPageMargin > availableHeight) {
Svetoslavc404cac2014-08-27 18:37:16 -0700430 verticalPadding = Math.max(0,
431 (availableHeight - mPageContentHeight - mFooterHeight) / 2
432 - mPreviewPageMargin);
Svet Ganov38781bd2014-08-12 00:57:07 -0700433 } else {
434 verticalPadding = Math.max(mPreviewListPadding,
435 (availableHeight - totalContentHeight) / 2);
436 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700437
438 mPreviewArea.setPadding(horizontalPadding, verticalPadding,
439 horizontalPadding, verticalPadding);
Svetoslav6552bf32014-09-03 21:15:55 -0700440
441 // Now update the empty state drawable, as it depends on the page
442 // size and is reused for all views for better performance.
443 LayoutInflater inflater = LayoutInflater.from(mContext);
444 View content = inflater.inflate(R.layout.preview_page_loading, null, false);
445 content.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY),
446 MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY));
447 content.layout(0, 0, content.getMeasuredWidth(), content.getMeasuredHeight());
448
449 Bitmap bitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight,
450 Bitmap.Config.ARGB_8888);
451 Canvas canvas = new Canvas(bitmap);
452 content.draw(canvas);
453
454 // Do not recycle the old bitmap if such as it may be set as an empty
455 // state to any of the page views. Just let the GC take care of it.
456 mEmptyState = new BitmapDrawable(mContext.getResources(), bitmap);
Svet Ganov525a66b2014-06-14 22:29:00 -0700457 }
458
459 private PageRange[] computeSelectedPages() {
460 ArrayList<PageRange> selectedPagesList = new ArrayList<>();
461
462 int startPageIndex = INVALID_PAGE_INDEX;
463 int endPageIndex = INVALID_PAGE_INDEX;
464
465 final int pageCount = mConfirmedPagesInDocument.size();
466 for (int i = 0; i < pageCount; i++) {
467 final int pageIndex = mConfirmedPagesInDocument.keyAt(i);
468 if (startPageIndex == INVALID_PAGE_INDEX) {
469 startPageIndex = endPageIndex = pageIndex;
470 }
471 if (endPageIndex + 1 < pageIndex) {
472 PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
473 selectedPagesList.add(pageRange);
474 startPageIndex = pageIndex;
475 }
476 endPageIndex = pageIndex;
477 }
478
479 if (startPageIndex != INVALID_PAGE_INDEX
480 && endPageIndex != INVALID_PAGE_INDEX) {
481 PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
482 selectedPagesList.add(pageRange);
483 }
484
485 PageRange[] selectedPages = new PageRange[selectedPagesList.size()];
486 selectedPagesList.toArray(selectedPages);
487
488 return selectedPages;
489 }
490
491 public void destroy() {
492 throwIfNotClosed();
493 doDestroy();
494 }
495
496 @Override
497 protected void finalize() throws Throwable {
498 try {
499 if (mState != STATE_DESTROYED) {
500 mCloseGuard.warnIfOpen();
501 doDestroy();
502 }
503 } finally {
504 super.finalize();
505 }
506 }
507
508 private int computePageIndexInDocument(int indexInAdapter) {
509 int skippedAdapterPages = 0;
510 final int selectedPagesCount = mSelectedPages.length;
511 for (int i = 0; i < selectedPagesCount; i++) {
512 PageRange pageRange = PageRangeUtils.asAbsoluteRange(
513 mSelectedPages[i], mDocumentPageCount);
514 skippedAdapterPages += pageRange.getSize();
515 if (skippedAdapterPages > indexInAdapter) {
516 final int overshoot = skippedAdapterPages - indexInAdapter - 1;
517 return pageRange.getEnd() - overshoot;
518 }
519 }
520 return INVALID_PAGE_INDEX;
521 }
522
523 private int computePageIndexInFile(int pageIndexInDocument) {
524 if (!PageRangeUtils.contains(mSelectedPages, pageIndexInDocument)) {
525 return INVALID_PAGE_INDEX;
526 }
527 if (mWrittenPages == null) {
528 return INVALID_PAGE_INDEX;
529 }
530
531 int indexInFile = INVALID_PAGE_INDEX;
532 final int rangeCount = mWrittenPages.length;
533 for (int i = 0; i < rangeCount; i++) {
534 PageRange pageRange = mWrittenPages[i];
535 if (!pageRange.contains(pageIndexInDocument)) {
536 indexInFile += pageRange.getSize();
537 } else {
538 indexInFile += pageIndexInDocument - pageRange.getStart() + 1;
539 return indexInFile;
540 }
541 }
542 return INVALID_PAGE_INDEX;
543 }
544
545 private void setConfirmedPages(PageRange[] pagesInDocument, int documentPageCount) {
546 mConfirmedPagesInDocument.clear();
547 final int rangeCount = pagesInDocument.length;
548 for (int i = 0; i < rangeCount; i++) {
549 PageRange pageRange = PageRangeUtils.asAbsoluteRange(pagesInDocument[i],
550 documentPageCount);
551 for (int j = pageRange.getStart(); j <= pageRange.getEnd(); j++) {
552 mConfirmedPagesInDocument.put(j, null);
553 }
554 }
555 }
556
557 private void onSelectedPageNotInFile(int pageInDocument) {
558 PageRange[] requestedPages = computeRequestedPages(pageInDocument);
559 if (!Arrays.equals(mRequestedPages, requestedPages)) {
560 mRequestedPages = requestedPages;
561 if (DEBUG) {
562 Log.i(LOG_TAG, "Requesting pages: " + Arrays.toString(mRequestedPages));
563 }
Svetoslav5ef522b2014-07-23 20:15:09 -0700564 mCallbacks.onRequestContentUpdate();
Svet Ganov525a66b2014-06-14 22:29:00 -0700565 }
566 }
567
568 private PageRange[] computeRequestedPages(int pageInDocument) {
569 if (mRequestedPages != null &&
570 PageRangeUtils.contains(mRequestedPages, pageInDocument)) {
571 return mRequestedPages;
572 }
573
574 List<PageRange> pageRangesList = new ArrayList<>();
575
576 int remainingPagesToRequest = MAX_PREVIEW_PAGES_BATCH;
577 final int selectedPagesCount = mSelectedPages.length;
578
579 // We always request the pages that are bound, i.e. shown on screen.
580 PageRange[] boundPagesInDocument = computeBoundPagesInDocument();
581
582 final int boundRangeCount = boundPagesInDocument.length;
583 for (int i = 0; i < boundRangeCount; i++) {
584 PageRange boundRange = boundPagesInDocument[i];
585 pageRangesList.add(boundRange);
586 }
587 remainingPagesToRequest -= PageRangeUtils.getNormalizedPageCount(
588 boundPagesInDocument, mDocumentPageCount);
589
590 final boolean requestFromStart = mRequestedPages == null
591 || pageInDocument > mRequestedPages[mRequestedPages.length - 1].getEnd();
592
593 if (!requestFromStart) {
594 if (DEBUG) {
595 Log.i(LOG_TAG, "Requesting from end");
596 }
597
598 // Reminder that ranges are always normalized.
599 for (int i = selectedPagesCount - 1; i >= 0; i--) {
600 if (remainingPagesToRequest <= 0) {
601 break;
602 }
603
604 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
605 mDocumentPageCount);
606 if (pageInDocument < selectedRange.getStart()) {
607 continue;
608 }
609
610 PageRange pagesInRange;
611 int rangeSpan;
612
613 if (selectedRange.contains(pageInDocument)) {
614 rangeSpan = pageInDocument - selectedRange.getStart() + 1;
615 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
616 final int fromPage = Math.max(pageInDocument - rangeSpan - 1, 0);
617 rangeSpan = Math.max(rangeSpan, 0);
618 pagesInRange = new PageRange(fromPage, pageInDocument);
619 } else {
620 rangeSpan = selectedRange.getSize();
621 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
622 rangeSpan = Math.max(rangeSpan, 0);
623 final int fromPage = Math.max(selectedRange.getEnd() - rangeSpan - 1, 0);
624 final int toPage = selectedRange.getEnd();
625 pagesInRange = new PageRange(fromPage, toPage);
626 }
627
628 pageRangesList.add(pagesInRange);
629 remainingPagesToRequest -= rangeSpan;
630 }
631 } else {
632 if (DEBUG) {
633 Log.i(LOG_TAG, "Requesting from start");
634 }
635
636 // Reminder that ranges are always normalized.
637 for (int i = 0; i < selectedPagesCount; i++) {
638 if (remainingPagesToRequest <= 0) {
639 break;
640 }
641
642 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
643 mDocumentPageCount);
644 if (pageInDocument > selectedRange.getEnd()) {
645 continue;
646 }
647
648 PageRange pagesInRange;
649 int rangeSpan;
650
651 if (selectedRange.contains(pageInDocument)) {
652 rangeSpan = selectedRange.getEnd() - pageInDocument + 1;
653 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
654 final int toPage = Math.min(pageInDocument + rangeSpan - 1,
655 mDocumentPageCount - 1);
656 pagesInRange = new PageRange(pageInDocument, toPage);
657 } else {
658 rangeSpan = selectedRange.getSize();
659 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
660 final int fromPage = selectedRange.getStart();
661 final int toPage = Math.min(selectedRange.getStart() + rangeSpan - 1,
662 mDocumentPageCount - 1);
663 pagesInRange = new PageRange(fromPage, toPage);
664 }
665
666 if (DEBUG) {
667 Log.i(LOG_TAG, "computeRequestedPages() Adding range:" + pagesInRange);
668 }
669 pageRangesList.add(pagesInRange);
670 remainingPagesToRequest -= rangeSpan;
671 }
672 }
673
674 PageRange[] pageRanges = new PageRange[pageRangesList.size()];
675 pageRangesList.toArray(pageRanges);
676
677 return PageRangeUtils.normalize(pageRanges);
678 }
679
680 private PageRange[] computeBoundPagesInDocument() {
681 List<PageRange> pagesInDocumentList = new ArrayList<>();
682
683 int fromPage = INVALID_PAGE_INDEX;
684 int toPage = INVALID_PAGE_INDEX;
685
686 final int boundPageCount = mBoundPagesInAdapter.size();
687 for (int i = 0; i < boundPageCount; i++) {
688 // The container is a sparse array, so keys are sorted in ascending order.
689 final int boundPageInAdapter = mBoundPagesInAdapter.keyAt(i);
690 final int boundPageInDocument = computePageIndexInDocument(boundPageInAdapter);
691
692 if (fromPage == INVALID_PAGE_INDEX) {
693 fromPage = boundPageInDocument;
694 }
695
696 if (toPage == INVALID_PAGE_INDEX) {
697 toPage = boundPageInDocument;
698 }
699
700 if (boundPageInDocument > toPage + 1) {
701 PageRange pageRange = new PageRange(fromPage, toPage);
702 pagesInDocumentList.add(pageRange);
703 fromPage = toPage = boundPageInDocument;
704 } else {
705 toPage = boundPageInDocument;
706 }
707 }
708
709 if (fromPage != INVALID_PAGE_INDEX && toPage != INVALID_PAGE_INDEX) {
710 PageRange pageRange = new PageRange(fromPage, toPage);
711 pagesInDocumentList.add(pageRange);
712 }
713
714 PageRange[] pageInDocument = new PageRange[pagesInDocumentList.size()];
715 pagesInDocumentList.toArray(pageInDocument);
716
717 if (DEBUG) {
718 Log.i(LOG_TAG, "Bound pages: " + Arrays.toString(pageInDocument));
719 }
720
721 return pageInDocument;
722 }
723
724 private void recyclePageView(PageContentView page, int pageIndexInAdapter) {
725 PageContentProvider provider = page.getPageContentProvider();
726 if (provider != null) {
Svetoslav0d2d9632014-09-17 15:45:16 -0700727 page.init(null, mEmptyState, mMediaSize, mMinMargins);
Svet Ganov525a66b2014-06-14 22:29:00 -0700728 mPageContentRepository.releasePageContentProvider(provider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700729 }
Svetoslavf3f963b2014-09-15 21:03:28 -0700730 mBoundPagesInAdapter.remove(pageIndexInAdapter);
Svet Ganov525a66b2014-06-14 22:29:00 -0700731 page.setTag(null);
732 }
733
734 public void startPreloadContent(PageRange pageRangeInAdapter) {
Svetoslav1710e0312014-07-11 15:19:22 -0700735 final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
736 final int startPageInFile = computePageIndexInFile(startPageInDocument);
737 final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
738 final int endPageInFile = computePageIndexInFile(endPageInDocument);
739 if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
740 mPageContentRepository.startPreload(startPageInFile, endPageInFile);
741 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700742 }
743
744 public void stopPreloadContent() {
Svetoslav1710e0312014-07-11 15:19:22 -0700745 mPageContentRepository.stopPreload();
Svet Ganov525a66b2014-06-14 22:29:00 -0700746 }
747
748 private void doDestroy() {
749 mPageContentRepository.destroy();
750 mCloseGuard.close();
751 mState = STATE_DESTROYED;
752 if (DEBUG) {
753 Log.i(LOG_TAG, "STATE_DESTROYED");
754 }
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
Svetoslave652b022014-09-09 22:11:10 -0700779 public void onClick(View view) {
780 PreviewPageFrame page = (PreviewPageFrame) view;
Svet Ganov525a66b2014-06-14 22:29:00 -0700781 MyViewHolder holder = (MyViewHolder) page.getTag();
782 final int pageInAdapter = holder.mPageInAdapter;
783 final int pageInDocument = computePageIndexInDocument(pageInAdapter);
Svet Ganov525a66b2014-06-14 22:29:00 -0700784 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
785 mConfirmedPagesInDocument.put(pageInDocument, null);
Svetoslave652b022014-09-09 22:11:10 -0700786 page.setSelected(true, true);
Svet Ganov525a66b2014-06-14 22:29:00 -0700787 } else {
Svetoslavc404cac2014-08-27 18:37:16 -0700788 if (mConfirmedPagesInDocument.size() <= 1) {
789 return;
790 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700791 mConfirmedPagesInDocument.remove(pageInDocument);
Svetoslave652b022014-09-09 22:11:10 -0700792 page.setSelected(false, true);
Svet Ganov525a66b2014-06-14 22:29:00 -0700793 }
794 }
795 }
796}