PrintPreviewController.java revision 525a66b2bb5abf844aff2109bdc9ed819566bece
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.printspooler.ui;
18
19import android.os.Handler;
20import android.os.Looper;
21import android.os.Message;
22import android.os.ParcelFileDescriptor;
23import android.print.PageRange;
24import android.print.PrintAttributes.MediaSize;
25import android.print.PrintAttributes.Margins;
26import android.print.PrintDocumentInfo;
27import android.support.v7.widget.OrientationHelper;
28import android.support.v7.widget.RecyclerView;
29import android.support.v7.widget.RecyclerView.ViewHolder;
30import android.support.v7.widget.RecyclerView.LayoutManager;
31import android.support.v7.widget.StaggeredGridLayoutManager;
32import android.view.View;
33import com.android.internal.os.SomeArgs;
34import com.android.printspooler.R;
35import com.android.printspooler.model.MutexFileProvider;
36import com.android.printspooler.widget.PrintContentView;
37import com.android.printspooler.widget.EmbeddedContentContainer;
38import com.android.printspooler.widget.PrintOptionsLayout;
39
40import java.io.File;
41import java.io.FileNotFoundException;
42import java.util.ArrayList;
43import java.util.List;
44
45class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallback,
46        PageAdapter.PreviewArea, EmbeddedContentContainer.OnSizeChangeListener {
47
48    private PrintActivity mActivity;
49
50    private final MutexFileProvider mFileProvider;
51
52    private final MyHandler mHandler;
53
54    private final PageAdapter mPageAdapter;
55
56    private final StaggeredGridLayoutManager mLayoutManger;
57
58    private PrintOptionsLayout mPrintOptionsLayout;
59
60    private final RecyclerView mRecyclerView;
61
62    private final PrintContentView mContentView;
63
64    private final EmbeddedContentContainer mEmbeddedContentContainer;
65
66    private final PreloadController mPreloadController;
67
68    private int mDocumentPageCount;
69
70    public PrintPreviewController(PrintActivity activity, MutexFileProvider fileProvider) {
71        mActivity = activity;
72        mHandler = new MyHandler(activity.getMainLooper());
73        mFileProvider = fileProvider;
74
75        mPrintOptionsLayout = (PrintOptionsLayout) activity.findViewById(R.id.options_container);
76        mPageAdapter = new PageAdapter(activity, activity, this);
77
78        final int columnCount = mActivity.getResources().getInteger(
79                R.integer.preview_page_per_row_count);
80
81        mLayoutManger = new StaggeredGridLayoutManager(columnCount, OrientationHelper.VERTICAL);
82        mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
83        mRecyclerView.setLayoutManager(mLayoutManger);
84        mRecyclerView.setAdapter(mPageAdapter);
85        mPreloadController = new PreloadController(mRecyclerView);
86        mRecyclerView.setOnScrollListener(mPreloadController);
87
88        mContentView = (PrintContentView) activity.findViewById(R.id.options_content);
89        mEmbeddedContentContainer = (EmbeddedContentContainer) activity.findViewById(
90                R.id.embedded_content_container);
91        mEmbeddedContentContainer.setOnSizeChangeListener(this);
92    }
93
94    @Override
95    public void onSizeChanged(int width, int height) {
96        mPageAdapter.onPreviewAreaSizeChanged();
97    }
98
99    public boolean isOptionsOpened() {
100        return mContentView.isOptionsOpened();
101    }
102
103    public void closeOptions() {
104        mContentView.closeOptions();
105    }
106
107    public void setUiShown(boolean shown) {
108        if (shown) {
109            mRecyclerView.setVisibility(View.VISIBLE);
110        } else {
111            mRecyclerView.setVisibility(View.GONE);
112        }
113    }
114
115    public void onOrientationChanged() {
116        // Adjust the print option column count.
117        final int optionColumnCount = mActivity.getResources().getInteger(
118                R.integer.print_option_column_count);
119        mPrintOptionsLayout.setColumnCount(optionColumnCount);
120        mPageAdapter.onOrientationChanged();
121    }
122
123    public int getFilePageCount() {
124        return mPageAdapter.getFilePageCount();
125    }
126
127    public PageRange[] getSelectedPages() {
128        return mPageAdapter.getSelectedPages();
129    }
130
131    public PageRange[] getRequestedPages() {
132        return mPageAdapter.getRequestedPages();
133    }
134
135    public void onContentUpdated(boolean documentChanged, int documentPageCount,
136            PageRange[] writtenPages, PageRange[] selectedPages, MediaSize mediaSize,
137            Margins minMargins) {
138                boolean contentChanged = false;
139
140        if (documentChanged) {
141            contentChanged = true;
142        }
143
144        if (documentPageCount != mDocumentPageCount) {
145            mDocumentPageCount = documentPageCount;
146            contentChanged = true;
147        }
148
149        if (contentChanged) {
150            // If not closed, close as we start over.
151            if (mPageAdapter.isOpened()) {
152                Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
153                mHandler.enqueueOperation(operation);
154            }
155        }
156
157        // The content changed. In this case we have to invalidate
158        // all rendered pages and reopen the file...
159        if (contentChanged && writtenPages != null) {
160            Message operation = mHandler.obtainMessage(MyHandler.MSG_OPEN);
161            mHandler.enqueueOperation(operation);
162        }
163
164        // Update the attributes before after closed to avoid flicker.
165        SomeArgs args = SomeArgs.obtain();
166        args.arg1 = writtenPages;
167        args.arg2 = selectedPages;
168        args.arg3 = mediaSize;
169        args.arg4 = minMargins;
170        args.argi1 = documentPageCount;
171
172        Message operation = mHandler.obtainMessage(MyHandler.MSG_UPDATE, args);
173        mHandler.enqueueOperation(operation);
174
175        // If document changed and has pages we want to start preloading.
176        if (contentChanged && writtenPages != null) {
177            operation = mHandler.obtainMessage(MyHandler.MSG_START_PRELOAD);
178            mHandler.enqueueOperation(operation);
179        }
180    }
181
182    @Override
183    public void onReleaseRequested(final File file) {
184        // This is called from the async task's single threaded executor
185        // thread, i.e. not on the main thread - so post a message.
186        mHandler.post(new Runnable() {
187            @Override
188            public void run() {
189                // At this point the other end will write to the file, hence
190                // we have to close it and reopen after the write completes.
191                Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
192                mHandler.enqueueOperation(operation);
193            }
194        });
195    }
196
197    public void destroy() {
198        mPageAdapter.destroy();
199    }
200
201    @Override
202    public int getWidth() {
203        return mEmbeddedContentContainer.getWidth();
204    }
205
206    @Override
207    public int getHeight() {
208        return mEmbeddedContentContainer.getHeight();
209    }
210
211    @Override
212    public void setColumnCount(int columnCount) {
213        mLayoutManger.setSpanCount(columnCount);
214    }
215
216    @Override
217    public void setPadding(int left, int top , int right, int bottom) {
218        mRecyclerView.setPadding(left, top, right, bottom);
219    }
220
221    private final class MyHandler extends Handler {
222        public static final int MSG_OPEN = 1;
223        public static final int MSG_CLOSE = 2;
224        public static final int MSG_DESTROY = 3;
225        public static final int MSG_UPDATE = 4;
226        public static final int MSG_START_PRELOAD = 5;
227
228        private boolean mAsyncOperationInProgress;
229
230        private final Runnable mOnAsyncOperationDoneCallback = new Runnable() {
231            @Override
232            public void run() {
233                mAsyncOperationInProgress = false;
234                handleNextOperation();
235            }
236        };
237
238        private final List<Message> mPendingOperations = new ArrayList<>();
239
240        public MyHandler(Looper looper) {
241            super(looper, null, false);
242        }
243
244        public void enqueueOperation(Message message) {
245            mPendingOperations.add(message);
246            handleNextOperation();
247        }
248
249        public void handleNextOperation() {
250            while (!mPendingOperations.isEmpty() && !mAsyncOperationInProgress) {
251                Message operation = mPendingOperations.remove(0);
252                handleMessage(operation);
253            }
254        }
255
256        @Override
257        public void handleMessage(Message message) {
258            switch (message.what) {
259                case MSG_OPEN: {
260                    try {
261                        File file = mFileProvider.acquireFile(PrintPreviewController.this);
262                        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
263                                ParcelFileDescriptor.MODE_READ_ONLY);
264
265                        mAsyncOperationInProgress = true;
266                        mPageAdapter.open(pfd, new Runnable() {
267                            @Override
268                            public void run() {
269                                if (mDocumentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
270                                    mDocumentPageCount = mPageAdapter.getFilePageCount();
271                                    mActivity.updateOptionsUi();
272                                }
273                                mOnAsyncOperationDoneCallback.run();
274                            }
275                        });
276                    } catch (FileNotFoundException fnfe) {
277                        /* ignore - file guaranteed to be there */
278                    }
279                } break;
280
281                case MSG_CLOSE: {
282                    mAsyncOperationInProgress = true;
283                    mPageAdapter.close(new Runnable() {
284                        @Override
285                        public void run() {
286                            mFileProvider.releaseFile();
287                            mOnAsyncOperationDoneCallback.run();
288                        }
289                    });
290                } break;
291
292                case MSG_DESTROY: {
293                    mPageAdapter.destroy();
294                    handleNextOperation();
295                } break;
296
297                case MSG_UPDATE: {
298                    SomeArgs args = (SomeArgs) message.obj;
299                    PageRange[] writtenPages = (PageRange[]) args.arg1;
300                    PageRange[] selectedPages = (PageRange[]) args.arg2;
301                    MediaSize mediaSize = (MediaSize) args.arg3;
302                    Margins margins = (Margins) args.arg4;
303                    final int pageCount = args.argi1;
304                    args.recycle();
305
306                    mPageAdapter.update(writtenPages, selectedPages, pageCount,
307                            mediaSize, margins);
308
309                } break;
310
311                case MSG_START_PRELOAD: {
312                    mPreloadController.startPreloadContent();
313                } break;
314            }
315        }
316    }
317
318    private final class PreloadController implements RecyclerView.OnScrollListener {
319        private final RecyclerView mRecyclerView;
320
321        private int mOldScrollState;
322
323        public PreloadController(RecyclerView recyclerView) {
324            mRecyclerView = recyclerView;
325            mOldScrollState = mRecyclerView.getScrollState();
326        }
327
328        @Override
329        public void onScrollStateChanged(int state) {
330            switch (mOldScrollState) {
331                case RecyclerView.SCROLL_STATE_SETTLING: {
332                    if (state == RecyclerView.SCROLL_STATE_IDLE
333                            || state == RecyclerView.SCROLL_STATE_DRAGGING){
334                        startPreloadContent();
335                    }
336                } break;
337
338                case RecyclerView.SCROLL_STATE_IDLE:
339                case RecyclerView.SCROLL_STATE_DRAGGING: {
340                    if (state == RecyclerView.SCROLL_STATE_SETTLING) {
341                        stopPreloadContent();
342                    }
343                } break;
344            }
345            mOldScrollState = state;
346        }
347
348        @Override
349        public void onScrolled(int dx, int dy) {
350            /* do nothing */
351        }
352
353        public void startPreloadContent() {
354            PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
355
356            if (pageAdapter.isOpened()) {
357                PageRange shownPages = computeShownPages();
358                if (shownPages != null) {
359                    pageAdapter.startPreloadContent(shownPages);
360                }
361            }
362        }
363
364        public void stopPreloadContent() {
365            PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
366
367            if (pageAdapter.isOpened()) {
368                pageAdapter.stopPreloadContent();
369            }
370        }
371
372        private PageRange computeShownPages() {
373            final int childCount = mRecyclerView.getChildCount();
374            if (childCount > 0) {
375                LayoutManager layoutManager = mRecyclerView.getLayoutManager();
376
377                View firstChild = layoutManager.getChildAt(0);
378                ViewHolder firstHolder = mRecyclerView.getChildViewHolder(firstChild);
379
380                View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
381                ViewHolder lastHolder = mRecyclerView.getChildViewHolder(lastChild);
382
383                return new PageRange(firstHolder.getPosition(), lastHolder.getPosition());
384            }
385            return null;
386        }
387    }
388}
389