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