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