blob: c1a3f865bed46fe7ee324af63adf345b4b04f6ee [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;
Philip P. Moltmanne7f06a82016-03-16 16:24:38 -070023import android.os.Handler;
24import android.os.Looper;
Svet Ganov525a66b2014-06-14 22:29:00 -070025import android.os.ParcelFileDescriptor;
26import android.print.PageRange;
27import android.print.PrintAttributes.MediaSize;
28import android.print.PrintAttributes.Margins;
29import android.print.PrintDocumentInfo;
30import android.support.v7.widget.RecyclerView.Adapter;
31import android.support.v7.widget.RecyclerView.ViewHolder;
32import android.util.Log;
33import android.util.SparseArray;
Svet Ganov525a66b2014-06-14 22:29:00 -070034import android.view.LayoutInflater;
35import android.view.View;
36import android.view.View.OnClickListener;
37import android.view.ViewGroup;
38import android.view.ViewGroup.LayoutParams;
Svetoslav6552bf32014-09-03 21:15:55 -070039import android.view.View.MeasureSpec;
Svet Ganov525a66b2014-06-14 22:29:00 -070040import android.widget.TextView;
41import com.android.printspooler.R;
Svet Ganovfce84f02014-10-31 16:56:52 -070042import com.android.printspooler.model.OpenDocumentCallback;
Svet Ganov525a66b2014-06-14 22:29:00 -070043import com.android.printspooler.model.PageContentRepository;
44import com.android.printspooler.model.PageContentRepository.PageContentProvider;
45import com.android.printspooler.util.PageRangeUtils;
46import com.android.printspooler.widget.PageContentView;
Svetoslave652b022014-09-09 22:11:10 -070047import com.android.printspooler.widget.PreviewPageFrame;
Svet Ganov525a66b2014-06-14 22:29:00 -070048import dalvik.system.CloseGuard;
49
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.List;
53
54/**
55 * This class represents the adapter for the pages in the print preview list.
56 */
Philip P. Moltmannc43639c2015-12-18 13:58:40 -080057public final class PageAdapter extends Adapter<ViewHolder> {
Svet Ganov525a66b2014-06-14 22:29:00 -070058 private static final String LOG_TAG = "PageAdapter";
59
60 private static final int MAX_PREVIEW_PAGES_BATCH = 50;
61
Svetoslavf3f963b2014-09-15 21:03:28 -070062 private static final boolean DEBUG = false;
Svet Ganov525a66b2014-06-14 22:29:00 -070063
64 private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {
65 PageRange.ALL_PAGES
66 };
67
68 private static final int INVALID_PAGE_INDEX = -1;
69
70 private static final int STATE_CLOSED = 0;
71 private static final int STATE_OPENED = 1;
72 private static final int STATE_DESTROYED = 2;
73
74 private final CloseGuard mCloseGuard = CloseGuard.get();
75
76 private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>();
Svet Ganov525a66b2014-06-14 22:29:00 -070077 private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>();
78
79 private final PageClickListener mPageClickListener = new PageClickListener();
80
Svetoslav15cbc8a2014-07-11 09:45:07 -070081 private final Context mContext;
Svet Ganov525a66b2014-06-14 22:29:00 -070082 private final LayoutInflater mLayoutInflater;
83
Svetoslav5ef522b2014-07-23 20:15:09 -070084 private final ContentCallbacks mCallbacks;
Svet Ganov525a66b2014-06-14 22:29:00 -070085 private final PageContentRepository mPageContentRepository;
Svet Ganov525a66b2014-06-14 22:29:00 -070086 private final PreviewArea mPreviewArea;
87
Svet Ganov525a66b2014-06-14 22:29:00 -070088 // Which document pages to be written.
89 private PageRange[] mRequestedPages;
Svet Ganov525a66b2014-06-14 22:29:00 -070090 // Pages written in the current file.
91 private PageRange[] mWrittenPages;
Svet Ganov525a66b2014-06-14 22:29:00 -070092 // Pages the user selected in the UI.
93 private PageRange[] mSelectedPages;
94
Svetoslav6552bf32014-09-03 21:15:55 -070095 private BitmapDrawable mEmptyState;
Philip P. Moltmann066bf812016-03-09 16:54:52 -080096 private BitmapDrawable mErrorState;
Svetoslav6552bf32014-09-03 21:15:55 -070097
Svetoslav15cbc8a2014-07-11 09:45:07 -070098 private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
99 private int mSelectedPageCount;
Svet Ganov525a66b2014-06-14 22:29:00 -0700100
Svet Ganov525a66b2014-06-14 22:29:00 -0700101 private int mPreviewPageMargin;
Svet Ganov38781bd2014-08-12 00:57:07 -0700102 private int mPreviewPageMinWidth;
Svet Ganov525a66b2014-06-14 22:29:00 -0700103 private int mPreviewListPadding;
Svet Ganov525a66b2014-06-14 22:29:00 -0700104 private int mFooterHeight;
105
106 private int mColumnCount;
107
108 private MediaSize mMediaSize;
Svet Ganov525a66b2014-06-14 22:29:00 -0700109 private Margins mMinMargins;
110
111 private int mState;
112
113 private int mPageContentWidth;
Svet Ganov525a66b2014-06-14 22:29:00 -0700114 private int mPageContentHeight;
115
Svetoslav5ef522b2014-07-23 20:15:09 -0700116 public interface ContentCallbacks {
117 public void onRequestContentUpdate();
118 public void onMalformedPdfFile();
Svet Ganovfce84f02014-10-31 16:56:52 -0700119 public void onSecurePdfFile();
Svet Ganov525a66b2014-06-14 22:29:00 -0700120 }
121
122 public interface PreviewArea {
123 public int getWidth();
124 public int getHeight();
125 public void setColumnCount(int columnCount);
126 public void setPadding(int left, int top, int right, int bottom);
127 }
128
Svetoslav5ef522b2014-07-23 20:15:09 -0700129 public PageAdapter(Context context, ContentCallbacks callbacks, PreviewArea previewArea) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700130 mContext = context;
Svetoslav5ef522b2014-07-23 20:15:09 -0700131 mCallbacks = callbacks;
Svet Ganov525a66b2014-06-14 22:29:00 -0700132 mLayoutInflater = (LayoutInflater) context.getSystemService(
133 Context.LAYOUT_INFLATER_SERVICE);
Svet Ganovfce84f02014-10-31 16:56:52 -0700134 mPageContentRepository = new PageContentRepository(context);
Svet Ganov525a66b2014-06-14 22:29:00 -0700135
Svet Ganov525a66b2014-06-14 22:29:00 -0700136 mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
137 R.dimen.preview_page_margin);
138
Svet Ganov38781bd2014-08-12 00:57:07 -0700139 mPreviewPageMinWidth = mContext.getResources().getDimensionPixelSize(
140 R.dimen.preview_page_min_width);
141
Svet Ganov525a66b2014-06-14 22:29:00 -0700142 mPreviewListPadding = mContext.getResources().getDimensionPixelSize(
143 R.dimen.preview_list_padding);
144
145 mColumnCount = mContext.getResources().getInteger(
146 R.integer.preview_page_per_row_count);
147
Svet Ganov38781bd2014-08-12 00:57:07 -0700148 mFooterHeight = mContext.getResources().getDimensionPixelSize(
149 R.dimen.preview_page_footer_height);
Svet Ganov525a66b2014-06-14 22:29:00 -0700150
151 mPreviewArea = previewArea;
152
153 mCloseGuard.open("destroy");
154
155 setHasStableIds(true);
156
157 mState = STATE_CLOSED;
158 if (DEBUG) {
159 Log.i(LOG_TAG, "STATE_CLOSED");
160 }
161 }
162
163 public void onOrientationChanged() {
164 mColumnCount = mContext.getResources().getInteger(
165 R.integer.preview_page_per_row_count);
Svetoslav139ba7f2014-09-12 10:35:26 -0700166 notifyDataSetChanged();
Svet Ganov525a66b2014-06-14 22:29:00 -0700167 }
168
169 public boolean isOpened() {
170 return mState == STATE_OPENED;
171 }
172
173 public int getFilePageCount() {
174 return mPageContentRepository.getFilePageCount();
175 }
176
Svetoslavf3f963b2014-09-15 21:03:28 -0700177 public void open(ParcelFileDescriptor source, final Runnable callback) {
Svet Ganov525a66b2014-06-14 22:29:00 -0700178 throwIfNotClosed();
179 mState = STATE_OPENED;
180 if (DEBUG) {
181 Log.i(LOG_TAG, "STATE_OPENED");
182 }
Svet Ganovfce84f02014-10-31 16:56:52 -0700183 mPageContentRepository.open(source, new OpenDocumentCallback() {
Svetoslavf3f963b2014-09-15 21:03:28 -0700184 @Override
Svet Ganovfce84f02014-10-31 16:56:52 -0700185 public void onSuccess() {
Svetoslavf3f963b2014-09-15 21:03:28 -0700186 notifyDataSetChanged();
187 callback.run();
188 }
Svet Ganovfce84f02014-10-31 16:56:52 -0700189
190 @Override
191 public void onFailure(int error) {
192 switch (error) {
193 case OpenDocumentCallback.ERROR_MALFORMED_PDF_FILE: {
194 mCallbacks.onMalformedPdfFile();
195 } break;
196
197 case OpenDocumentCallback.ERROR_SECURE_PDF_FILE: {
198 mCallbacks.onSecurePdfFile();
199 } break;
200 }
201 }
Svetoslavf3f963b2014-09-15 21:03:28 -0700202 });
Svet Ganov525a66b2014-06-14 22:29:00 -0700203 }
204
205 public void update(PageRange[] writtenPages, PageRange[] selectedPages,
206 int documentPageCount, MediaSize mediaSize, Margins minMargins) {
207 boolean documentChanged = false;
208 boolean updatePreviewAreaAndPageSize = false;
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -0700209 boolean clearSelectedPages = false;
Svet Ganov525a66b2014-06-14 22:29:00 -0700210
211 // If the app does not tell how many pages are in the document we cannot
212 // optimize and ask for all pages whose count we get from the renderer.
213 if (documentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
214 if (writtenPages == null) {
215 // If we already requested all pages, just wait.
216 if (!Arrays.equals(ALL_PAGES_ARRAY, mRequestedPages)) {
217 mRequestedPages = ALL_PAGES_ARRAY;
Svetoslav5ef522b2014-07-23 20:15:09 -0700218 mCallbacks.onRequestContentUpdate();
Svet Ganov525a66b2014-06-14 22:29:00 -0700219 }
220 return;
221 } else {
222 documentPageCount = mPageContentRepository.getFilePageCount();
223 if (documentPageCount <= 0) {
224 return;
225 }
226 }
227 }
228
Svet Ganov525a66b2014-06-14 22:29:00 -0700229 if (mDocumentPageCount != documentPageCount) {
230 mDocumentPageCount = documentPageCount;
231 documentChanged = true;
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -0700232 clearSelectedPages = true;
Svet Ganov525a66b2014-06-14 22:29:00 -0700233 }
234
235 if (mMediaSize == null || !mMediaSize.equals(mediaSize)) {
236 mMediaSize = mediaSize;
237 updatePreviewAreaAndPageSize = true;
238 documentChanged = true;
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -0700239
240 clearSelectedPages = true;
Svet Ganov525a66b2014-06-14 22:29:00 -0700241 }
242
243 if (mMinMargins == null || !mMinMargins.equals(minMargins)) {
244 mMinMargins = minMargins;
245 updatePreviewAreaAndPageSize = true;
246 documentChanged = true;
Philip P. Moltmann4ef83c462016-03-24 15:27:45 -0700247
248 clearSelectedPages = true;
249 }
250
251 if (clearSelectedPages) {
252 mSelectedPages = PageRange.ALL_PAGES_ARRAY;
253 mSelectedPageCount = documentPageCount;
254 setConfirmedPages(mSelectedPages, documentPageCount);
255 updatePreviewAreaAndPageSize = true;
256 documentChanged = true;
257 } else if (!Arrays.equals(mSelectedPages, selectedPages)) {
258 mSelectedPages = selectedPages;
259 mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
260 mSelectedPages, documentPageCount);
261 setConfirmedPages(mSelectedPages, documentPageCount);
262 updatePreviewAreaAndPageSize = true;
263 documentChanged = true;
Svet Ganov525a66b2014-06-14 22:29:00 -0700264 }
265
266 // If *all pages* is selected we need to convert that to absolute
267 // range as we will be checking if some pages are written or not.
268 if (writtenPages != null) {
269 // If we get all pages, this means all pages that we requested.
270 if (PageRangeUtils.isAllPages(writtenPages)) {
271 writtenPages = mRequestedPages;
272 }
273 if (!Arrays.equals(mWrittenPages, writtenPages)) {
274 // TODO: Do a surgical invalidation of only written pages changed.
275 mWrittenPages = writtenPages;
276 documentChanged = true;
277 }
278 }
279
280 if (updatePreviewAreaAndPageSize) {
Svetoslav6552bf32014-09-03 21:15:55 -0700281 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700282 }
283
284 if (documentChanged) {
285 notifyDataSetChanged();
286 }
287 }
288
289 public void close(Runnable callback) {
290 throwIfNotOpened();
291 mState = STATE_CLOSED;
292 if (DEBUG) {
293 Log.i(LOG_TAG, "STATE_CLOSED");
294 }
295 mPageContentRepository.close(callback);
296 }
297
298 @Override
299 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
300 View page = mLayoutInflater.inflate(R.layout.preview_page, parent, false);
Svetoslav0d2d9632014-09-17 15:45:16 -0700301 return new MyViewHolder(page);
Svet Ganov525a66b2014-06-14 22:29:00 -0700302 }
303
304 @Override
305 public void onBindViewHolder(ViewHolder holder, int position) {
306 if (DEBUG) {
307 Log.i(LOG_TAG, "Binding holder: " + holder + " with id: " + getItemId(position)
308 + " for position: " + position);
309 }
310
Svet Ganov525a66b2014-06-14 22:29:00 -0700311 MyViewHolder myHolder = (MyViewHolder) holder;
312
Svetoslave652b022014-09-09 22:11:10 -0700313 PreviewPageFrame page = (PreviewPageFrame) holder.itemView;
Svetoslavc404cac2014-08-27 18:37:16 -0700314 page.setOnClickListener(mPageClickListener);
315
Svet Ganov525a66b2014-06-14 22:29:00 -0700316 page.setTag(holder);
317
318 myHolder.mPageInAdapter = position;
319
320 final int pageInDocument = computePageIndexInDocument(position);
321 final int pageIndexInFile = computePageIndexInFile(pageInDocument);
322
323 PageContentView content = (PageContentView) page.findViewById(R.id.page_content);
324
325 LayoutParams params = content.getLayoutParams();
326 params.width = mPageContentWidth;
327 params.height = mPageContentHeight;
328
329 PageContentProvider provider = content.getPageContentProvider();
330
331 if (pageIndexInFile != INVALID_PAGE_INDEX) {
332 if (DEBUG) {
333 Log.i(LOG_TAG, "Binding provider:"
334 + " pageIndexInAdapter: " + position
335 + ", pageInDocument: " + pageInDocument
336 + ", pageIndexInFile: " + pageIndexInFile);
337 }
338
Svet Ganov525a66b2014-06-14 22:29:00 -0700339 provider = mPageContentRepository.acquirePageContentProvider(
340 pageIndexInFile, content);
341 mBoundPagesInAdapter.put(position, null);
342 } else {
343 onSelectedPageNotInFile(pageInDocument);
344 }
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800345 content.init(provider, mEmptyState, mErrorState, mMediaSize, mMinMargins);
Svet Ganov525a66b2014-06-14 22:29:00 -0700346
Svet Ganov525a66b2014-06-14 22:29:00 -0700347 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
Svetoslave652b022014-09-09 22:11:10 -0700348 page.setSelected(true, false);
Svet Ganov525a66b2014-06-14 22:29:00 -0700349 } else {
Svetoslave652b022014-09-09 22:11:10 -0700350 page.setSelected(false, false);
Svet Ganov525a66b2014-06-14 22:29:00 -0700351 }
352
Svetoslave652b022014-09-09 22:11:10 -0700353 page.setContentDescription(mContext.getString(R.string.page_description_template,
354 pageInDocument + 1, mDocumentPageCount));
355
Svet Ganov525a66b2014-06-14 22:29:00 -0700356 TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
357 String text = mContext.getString(R.string.current_page_template,
358 pageInDocument + 1, mDocumentPageCount);
359 pageNumberView.setText(text);
360 }
361
362 @Override
363 public int getItemCount() {
364 return mSelectedPageCount;
365 }
366
367 @Override
368 public long getItemId(int position) {
369 return computePageIndexInDocument(position);
370 }
371
372 @Override
373 public void onViewRecycled(ViewHolder holder) {
374 MyViewHolder myHolder = (MyViewHolder) holder;
375 PageContentView content = (PageContentView) holder.itemView
376 .findViewById(R.id.page_content);
377 recyclePageView(content, myHolder.mPageInAdapter);
378 myHolder.mPageInAdapter = INVALID_PAGE_INDEX;
379 }
380
381 public PageRange[] getRequestedPages() {
382 return mRequestedPages;
383 }
384
385 public PageRange[] getSelectedPages() {
386 PageRange[] selectedPages = computeSelectedPages();
387 if (!Arrays.equals(mSelectedPages, selectedPages)) {
388 mSelectedPages = selectedPages;
389 mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
390 mSelectedPages, mDocumentPageCount);
Svetoslav6552bf32014-09-03 21:15:55 -0700391 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700392 notifyDataSetChanged();
393 }
394 return mSelectedPages;
395 }
396
397 public void onPreviewAreaSizeChanged() {
398 if (mMediaSize != null) {
Svetoslav6552bf32014-09-03 21:15:55 -0700399 updatePreviewAreaPageSizeAndEmptyState();
Svet Ganov525a66b2014-06-14 22:29:00 -0700400 notifyDataSetChanged();
401 }
402 }
403
Svetoslav6552bf32014-09-03 21:15:55 -0700404 private void updatePreviewAreaPageSizeAndEmptyState() {
Svet Ganove771caf2014-09-14 20:29:27 -0700405 if (mMediaSize == null) {
406 return;
407 }
408
Svet Ganov525a66b2014-06-14 22:29:00 -0700409 final int availableWidth = mPreviewArea.getWidth();
410 final int availableHeight = mPreviewArea.getHeight();
411
412 // Page aspect ratio to keep.
413 final float pageAspectRatio = (float) mMediaSize.getWidthMils()
414 / mMediaSize.getHeightMils();
415
416 // Make sure we have no empty columns.
417 final int columnCount = Math.min(mSelectedPageCount, mColumnCount);
418 mPreviewArea.setColumnCount(columnCount);
419
420 // Compute max page width.
421 final int horizontalMargins = 2 * columnCount * mPreviewPageMargin;
422 final int horizontalPaddingAndMargins = horizontalMargins + 2 * mPreviewListPadding;
423 final int pageContentDesiredWidth = (int) ((((float) availableWidth
424 - horizontalPaddingAndMargins) / columnCount) + 0.5f);
425
426 // Compute max page height.
Philip P. Moltmannc43639c2015-12-18 13:58:40 -0800427 final int pageContentDesiredHeight = (int) ((pageContentDesiredWidth
Svet Ganov525a66b2014-06-14 22:29:00 -0700428 / pageAspectRatio) + 0.5f);
Svet Ganov38781bd2014-08-12 00:57:07 -0700429
Svetoslav6552bf32014-09-03 21:15:55 -0700430 // If the page does not fit entirely in a vertical direction,
Svet Ganov38781bd2014-08-12 00:57:07 -0700431 // we shirk it but not less than the minimal page width.
432 final int pageContentMinHeight = (int) (mPreviewPageMinWidth / pageAspectRatio + 0.5f);
433 final int pageContentMaxHeight = Math.max(pageContentMinHeight,
434 availableHeight - 2 * (mPreviewListPadding + mPreviewPageMargin) - mFooterHeight);
Svet Ganov525a66b2014-06-14 22:29:00 -0700435
436 mPageContentHeight = Math.min(pageContentDesiredHeight, pageContentMaxHeight);
437 mPageContentWidth = (int) ((mPageContentHeight * pageAspectRatio) + 0.5f);
438
439 final int totalContentWidth = columnCount * mPageContentWidth + horizontalMargins;
440 final int horizontalPadding = (availableWidth - totalContentWidth) / 2;
441
442 final int rowCount = mSelectedPageCount / columnCount
443 + ((mSelectedPageCount % columnCount) > 0 ? 1 : 0);
Svet Ganov38781bd2014-08-12 00:57:07 -0700444 final int totalContentHeight = rowCount * (mPageContentHeight + mFooterHeight + 2
Svet Ganov525a66b2014-06-14 22:29:00 -0700445 * mPreviewPageMargin);
Svet Ganov38781bd2014-08-12 00:57:07 -0700446
447 final int verticalPadding;
Svetoslavdfa3e7c2014-09-09 19:01:56 -0700448 if (mPageContentHeight + mFooterHeight + mPreviewListPadding
449 + 2 * mPreviewPageMargin > availableHeight) {
Svetoslavc404cac2014-08-27 18:37:16 -0700450 verticalPadding = Math.max(0,
451 (availableHeight - mPageContentHeight - mFooterHeight) / 2
452 - mPreviewPageMargin);
Svet Ganov38781bd2014-08-12 00:57:07 -0700453 } else {
454 verticalPadding = Math.max(mPreviewListPadding,
455 (availableHeight - totalContentHeight) / 2);
456 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700457
458 mPreviewArea.setPadding(horizontalPadding, verticalPadding,
459 horizontalPadding, verticalPadding);
Svetoslav6552bf32014-09-03 21:15:55 -0700460
461 // Now update the empty state drawable, as it depends on the page
462 // size and is reused for all views for better performance.
463 LayoutInflater inflater = LayoutInflater.from(mContext);
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800464 View loadingContent = inflater.inflate(R.layout.preview_page_loading, null, false);
465 loadingContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY),
Svetoslav6552bf32014-09-03 21:15:55 -0700466 MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY));
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800467 loadingContent.layout(0, 0, loadingContent.getMeasuredWidth(),
468 loadingContent.getMeasuredHeight());
Svetoslav6552bf32014-09-03 21:15:55 -0700469
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800470 Bitmap loadingBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight,
Svetoslav6552bf32014-09-03 21:15:55 -0700471 Bitmap.Config.ARGB_8888);
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800472 loadingContent.draw(new Canvas(loadingBitmap));
Svetoslav6552bf32014-09-03 21:15:55 -0700473
474 // Do not recycle the old bitmap if such as it may be set as an empty
475 // state to any of the page views. Just let the GC take care of it.
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800476 mEmptyState = new BitmapDrawable(mContext.getResources(), loadingBitmap);
477
478 // Now update the empty state drawable, as it depends on the page
479 // size and is reused for all views for better performance.
480 View errorContent = inflater.inflate(R.layout.preview_page_error, null, false);
481 errorContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY),
482 MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY));
483 errorContent.layout(0, 0, errorContent.getMeasuredWidth(),
484 errorContent.getMeasuredHeight());
485
486 Bitmap errorBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight,
487 Bitmap.Config.ARGB_8888);
488 errorContent.draw(new Canvas(errorBitmap));
489
490 // Do not recycle the old bitmap if such as it may be set as an error
491 // state to any of the page views. Just let the GC take care of it.
492 mErrorState = new BitmapDrawable(mContext.getResources(), errorBitmap);
Svet Ganov525a66b2014-06-14 22:29:00 -0700493 }
494
495 private PageRange[] computeSelectedPages() {
496 ArrayList<PageRange> selectedPagesList = new ArrayList<>();
497
498 int startPageIndex = INVALID_PAGE_INDEX;
499 int endPageIndex = INVALID_PAGE_INDEX;
500
501 final int pageCount = mConfirmedPagesInDocument.size();
502 for (int i = 0; i < pageCount; i++) {
503 final int pageIndex = mConfirmedPagesInDocument.keyAt(i);
504 if (startPageIndex == INVALID_PAGE_INDEX) {
505 startPageIndex = endPageIndex = pageIndex;
506 }
507 if (endPageIndex + 1 < pageIndex) {
508 PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
509 selectedPagesList.add(pageRange);
510 startPageIndex = pageIndex;
511 }
512 endPageIndex = pageIndex;
513 }
514
515 if (startPageIndex != INVALID_PAGE_INDEX
516 && endPageIndex != INVALID_PAGE_INDEX) {
517 PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
518 selectedPagesList.add(pageRange);
519 }
520
521 PageRange[] selectedPages = new PageRange[selectedPagesList.size()];
522 selectedPagesList.toArray(selectedPages);
523
524 return selectedPages;
525 }
526
Svet Ganovc80814e2014-11-24 02:01:37 -0800527 public void destroy(Runnable callback) {
Svet Ganov4237c922014-10-24 12:53:23 -0700528 mCloseGuard.close();
529 mState = STATE_DESTROYED;
530 if (DEBUG) {
531 Log.i(LOG_TAG, "STATE_DESTROYED");
532 }
Svet Ganovc80814e2014-11-24 02:01:37 -0800533 mPageContentRepository.destroy(callback);
Svet Ganov525a66b2014-06-14 22:29:00 -0700534 }
535
536 @Override
537 protected void finalize() throws Throwable {
538 try {
539 if (mState != STATE_DESTROYED) {
540 mCloseGuard.warnIfOpen();
Svet Ganovc80814e2014-11-24 02:01:37 -0800541 destroy(null);
Svet Ganov525a66b2014-06-14 22:29:00 -0700542 }
543 } finally {
544 super.finalize();
545 }
546 }
547
548 private int computePageIndexInDocument(int indexInAdapter) {
549 int skippedAdapterPages = 0;
550 final int selectedPagesCount = mSelectedPages.length;
551 for (int i = 0; i < selectedPagesCount; i++) {
552 PageRange pageRange = PageRangeUtils.asAbsoluteRange(
553 mSelectedPages[i], mDocumentPageCount);
554 skippedAdapterPages += pageRange.getSize();
555 if (skippedAdapterPages > indexInAdapter) {
556 final int overshoot = skippedAdapterPages - indexInAdapter - 1;
557 return pageRange.getEnd() - overshoot;
558 }
559 }
560 return INVALID_PAGE_INDEX;
561 }
562
563 private int computePageIndexInFile(int pageIndexInDocument) {
564 if (!PageRangeUtils.contains(mSelectedPages, pageIndexInDocument)) {
565 return INVALID_PAGE_INDEX;
566 }
567 if (mWrittenPages == null) {
568 return INVALID_PAGE_INDEX;
569 }
570
571 int indexInFile = INVALID_PAGE_INDEX;
572 final int rangeCount = mWrittenPages.length;
573 for (int i = 0; i < rangeCount; i++) {
574 PageRange pageRange = mWrittenPages[i];
575 if (!pageRange.contains(pageIndexInDocument)) {
576 indexInFile += pageRange.getSize();
577 } else {
578 indexInFile += pageIndexInDocument - pageRange.getStart() + 1;
579 return indexInFile;
580 }
581 }
582 return INVALID_PAGE_INDEX;
583 }
584
585 private void setConfirmedPages(PageRange[] pagesInDocument, int documentPageCount) {
586 mConfirmedPagesInDocument.clear();
587 final int rangeCount = pagesInDocument.length;
588 for (int i = 0; i < rangeCount; i++) {
589 PageRange pageRange = PageRangeUtils.asAbsoluteRange(pagesInDocument[i],
590 documentPageCount);
591 for (int j = pageRange.getStart(); j <= pageRange.getEnd(); j++) {
592 mConfirmedPagesInDocument.put(j, null);
593 }
594 }
595 }
596
597 private void onSelectedPageNotInFile(int pageInDocument) {
598 PageRange[] requestedPages = computeRequestedPages(pageInDocument);
599 if (!Arrays.equals(mRequestedPages, requestedPages)) {
600 mRequestedPages = requestedPages;
601 if (DEBUG) {
602 Log.i(LOG_TAG, "Requesting pages: " + Arrays.toString(mRequestedPages));
603 }
Philip P. Moltmanne7f06a82016-03-16 16:24:38 -0700604
605 // This call might come from a recylerview that is currently updating. Hence delay to
606 // after the update
607 (new Handler(Looper.getMainLooper())).post(new Runnable() {
608 @Override public void run() {
609 mCallbacks.onRequestContentUpdate();
610 }
611 });
Svet Ganov525a66b2014-06-14 22:29:00 -0700612 }
613 }
614
615 private PageRange[] computeRequestedPages(int pageInDocument) {
616 if (mRequestedPages != null &&
617 PageRangeUtils.contains(mRequestedPages, pageInDocument)) {
618 return mRequestedPages;
619 }
620
621 List<PageRange> pageRangesList = new ArrayList<>();
622
623 int remainingPagesToRequest = MAX_PREVIEW_PAGES_BATCH;
624 final int selectedPagesCount = mSelectedPages.length;
625
626 // We always request the pages that are bound, i.e. shown on screen.
627 PageRange[] boundPagesInDocument = computeBoundPagesInDocument();
628
629 final int boundRangeCount = boundPagesInDocument.length;
630 for (int i = 0; i < boundRangeCount; i++) {
631 PageRange boundRange = boundPagesInDocument[i];
632 pageRangesList.add(boundRange);
633 }
634 remainingPagesToRequest -= PageRangeUtils.getNormalizedPageCount(
635 boundPagesInDocument, mDocumentPageCount);
636
637 final boolean requestFromStart = mRequestedPages == null
638 || pageInDocument > mRequestedPages[mRequestedPages.length - 1].getEnd();
639
640 if (!requestFromStart) {
641 if (DEBUG) {
642 Log.i(LOG_TAG, "Requesting from end");
643 }
644
645 // Reminder that ranges are always normalized.
646 for (int i = selectedPagesCount - 1; i >= 0; i--) {
647 if (remainingPagesToRequest <= 0) {
648 break;
649 }
650
651 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
652 mDocumentPageCount);
653 if (pageInDocument < selectedRange.getStart()) {
654 continue;
655 }
656
657 PageRange pagesInRange;
658 int rangeSpan;
659
660 if (selectedRange.contains(pageInDocument)) {
661 rangeSpan = pageInDocument - selectedRange.getStart() + 1;
662 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
663 final int fromPage = Math.max(pageInDocument - rangeSpan - 1, 0);
664 rangeSpan = Math.max(rangeSpan, 0);
665 pagesInRange = new PageRange(fromPage, pageInDocument);
666 } else {
667 rangeSpan = selectedRange.getSize();
668 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
669 rangeSpan = Math.max(rangeSpan, 0);
670 final int fromPage = Math.max(selectedRange.getEnd() - rangeSpan - 1, 0);
671 final int toPage = selectedRange.getEnd();
672 pagesInRange = new PageRange(fromPage, toPage);
673 }
674
675 pageRangesList.add(pagesInRange);
676 remainingPagesToRequest -= rangeSpan;
677 }
678 } else {
679 if (DEBUG) {
680 Log.i(LOG_TAG, "Requesting from start");
681 }
682
683 // Reminder that ranges are always normalized.
684 for (int i = 0; i < selectedPagesCount; i++) {
685 if (remainingPagesToRequest <= 0) {
686 break;
687 }
688
689 PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
690 mDocumentPageCount);
691 if (pageInDocument > selectedRange.getEnd()) {
692 continue;
693 }
694
695 PageRange pagesInRange;
696 int rangeSpan;
697
698 if (selectedRange.contains(pageInDocument)) {
699 rangeSpan = selectedRange.getEnd() - pageInDocument + 1;
700 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
701 final int toPage = Math.min(pageInDocument + rangeSpan - 1,
702 mDocumentPageCount - 1);
703 pagesInRange = new PageRange(pageInDocument, toPage);
704 } else {
705 rangeSpan = selectedRange.getSize();
706 rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
707 final int fromPage = selectedRange.getStart();
708 final int toPage = Math.min(selectedRange.getStart() + rangeSpan - 1,
709 mDocumentPageCount - 1);
710 pagesInRange = new PageRange(fromPage, toPage);
711 }
712
713 if (DEBUG) {
714 Log.i(LOG_TAG, "computeRequestedPages() Adding range:" + pagesInRange);
715 }
716 pageRangesList.add(pagesInRange);
717 remainingPagesToRequest -= rangeSpan;
718 }
719 }
720
721 PageRange[] pageRanges = new PageRange[pageRangesList.size()];
722 pageRangesList.toArray(pageRanges);
723
724 return PageRangeUtils.normalize(pageRanges);
725 }
726
727 private PageRange[] computeBoundPagesInDocument() {
728 List<PageRange> pagesInDocumentList = new ArrayList<>();
729
730 int fromPage = INVALID_PAGE_INDEX;
731 int toPage = INVALID_PAGE_INDEX;
732
733 final int boundPageCount = mBoundPagesInAdapter.size();
734 for (int i = 0; i < boundPageCount; i++) {
735 // The container is a sparse array, so keys are sorted in ascending order.
736 final int boundPageInAdapter = mBoundPagesInAdapter.keyAt(i);
737 final int boundPageInDocument = computePageIndexInDocument(boundPageInAdapter);
738
739 if (fromPage == INVALID_PAGE_INDEX) {
740 fromPage = boundPageInDocument;
741 }
742
743 if (toPage == INVALID_PAGE_INDEX) {
744 toPage = boundPageInDocument;
745 }
746
747 if (boundPageInDocument > toPage + 1) {
748 PageRange pageRange = new PageRange(fromPage, toPage);
749 pagesInDocumentList.add(pageRange);
750 fromPage = toPage = boundPageInDocument;
751 } else {
752 toPage = boundPageInDocument;
753 }
754 }
755
756 if (fromPage != INVALID_PAGE_INDEX && toPage != INVALID_PAGE_INDEX) {
757 PageRange pageRange = new PageRange(fromPage, toPage);
758 pagesInDocumentList.add(pageRange);
759 }
760
761 PageRange[] pageInDocument = new PageRange[pagesInDocumentList.size()];
762 pagesInDocumentList.toArray(pageInDocument);
763
764 if (DEBUG) {
765 Log.i(LOG_TAG, "Bound pages: " + Arrays.toString(pageInDocument));
766 }
767
768 return pageInDocument;
769 }
770
771 private void recyclePageView(PageContentView page, int pageIndexInAdapter) {
772 PageContentProvider provider = page.getPageContentProvider();
773 if (provider != null) {
Philip P. Moltmann066bf812016-03-09 16:54:52 -0800774 page.init(null, mEmptyState, mErrorState, mMediaSize, mMinMargins);
Svet Ganov525a66b2014-06-14 22:29:00 -0700775 mPageContentRepository.releasePageContentProvider(provider);
Svet Ganov525a66b2014-06-14 22:29:00 -0700776 }
Svetoslavf3f963b2014-09-15 21:03:28 -0700777 mBoundPagesInAdapter.remove(pageIndexInAdapter);
Svet Ganov525a66b2014-06-14 22:29:00 -0700778 page.setTag(null);
779 }
780
781 public void startPreloadContent(PageRange pageRangeInAdapter) {
Svetoslav1710e0312014-07-11 15:19:22 -0700782 final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
783 final int startPageInFile = computePageIndexInFile(startPageInDocument);
784 final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
785 final int endPageInFile = computePageIndexInFile(endPageInDocument);
786 if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
787 mPageContentRepository.startPreload(startPageInFile, endPageInFile);
788 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700789 }
790
791 public void stopPreloadContent() {
Svetoslav1710e0312014-07-11 15:19:22 -0700792 mPageContentRepository.stopPreload();
Svet Ganov525a66b2014-06-14 22:29:00 -0700793 }
794
Svet Ganov525a66b2014-06-14 22:29:00 -0700795 private void throwIfNotOpened() {
796 if (mState != STATE_OPENED) {
797 throw new IllegalStateException("Not opened");
798 }
799 }
800
801 private void throwIfNotClosed() {
802 if (mState != STATE_CLOSED) {
803 throw new IllegalStateException("Not closed");
804 }
805 }
806
807 private final class MyViewHolder extends ViewHolder {
808 int mPageInAdapter;
809
810 private MyViewHolder(View itemView) {
811 super(itemView);
812 }
813 }
814
815 private final class PageClickListener implements OnClickListener {
816 @Override
Svetoslave652b022014-09-09 22:11:10 -0700817 public void onClick(View view) {
818 PreviewPageFrame page = (PreviewPageFrame) view;
Svet Ganov525a66b2014-06-14 22:29:00 -0700819 MyViewHolder holder = (MyViewHolder) page.getTag();
820 final int pageInAdapter = holder.mPageInAdapter;
821 final int pageInDocument = computePageIndexInDocument(pageInAdapter);
Svet Ganov525a66b2014-06-14 22:29:00 -0700822 if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
823 mConfirmedPagesInDocument.put(pageInDocument, null);
Svetoslave652b022014-09-09 22:11:10 -0700824 page.setSelected(true, true);
Svet Ganov525a66b2014-06-14 22:29:00 -0700825 } else {
Svetoslavc404cac2014-08-27 18:37:16 -0700826 if (mConfirmedPagesInDocument.size() <= 1) {
827 return;
828 }
Svet Ganov525a66b2014-06-14 22:29:00 -0700829 mConfirmedPagesInDocument.remove(pageInDocument);
Svetoslave652b022014-09-09 22:11:10 -0700830 page.setSelected(false, true);
Svet Ganov525a66b2014-06-14 22:29:00 -0700831 }
832 }
833 }
834}