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         mHandler.cancelQueuedOperations();
    197         mRecyclerView.setAdapter(null);
    198         mPageAdapter.destroy(callback);
    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_UPDATE = 4;
    225         public static final int MSG_START_PRELOAD = 5;
    226 
    227         private boolean mAsyncOperationInProgress;
    228 
    229         private final Runnable mOnAsyncOperationDoneCallback = new Runnable() {
    230             @Override
    231             public void run() {
    232                 mAsyncOperationInProgress = false;
    233                 handleNextOperation();
    234             }
    235         };
    236 
    237         private final List<Message> mPendingOperations = new ArrayList<>();
    238 
    239         public MyHandler(Looper looper) {
    240             super(looper, null, false);
    241         }
    242 
    243         public void cancelQueuedOperations() {
    244             mPendingOperations.clear();
    245         }
    246 
    247         public void enqueueOperation(Message message) {
    248             mPendingOperations.add(message);
    249             handleNextOperation();
    250         }
    251 
    252         public void handleNextOperation() {
    253             while (!mPendingOperations.isEmpty() && !mAsyncOperationInProgress) {
    254                 Message operation = mPendingOperations.remove(0);
    255                 handleMessage(operation);
    256             }
    257         }
    258 
    259         @Override
    260         public void handleMessage(Message message) {
    261             switch (message.what) {
    262                 case MSG_OPEN: {
    263                     try {
    264                         File file = mFileProvider.acquireFile(PrintPreviewController.this);
    265                         ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
    266                                 ParcelFileDescriptor.MODE_READ_ONLY);
    267 
    268                         mAsyncOperationInProgress = true;
    269                         mPageAdapter.open(pfd, new Runnable() {
    270                             @Override
    271                             public void run() {
    272                                 if (mDocumentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
    273                                     mDocumentPageCount = mPageAdapter.getFilePageCount();
    274                                     mActivity.updateOptionsUi();
    275                                 }
    276                                 mOnAsyncOperationDoneCallback.run();
    277                             }
    278                         });
    279                     } catch (FileNotFoundException fnfe) {
    280                         /* ignore - file guaranteed to be there */
    281                     }
    282                 } break;
    283 
    284                 case MSG_CLOSE: {
    285                     mAsyncOperationInProgress = true;
    286                     mPageAdapter.close(new Runnable() {
    287                         @Override
    288                         public void run() {
    289                             mFileProvider.releaseFile();
    290                             mOnAsyncOperationDoneCallback.run();
    291                         }
    292                     });
    293                 } break;
    294 
    295                 case MSG_UPDATE: {
    296                     SomeArgs args = (SomeArgs) message.obj;
    297                     PageRange[] writtenPages = (PageRange[]) args.arg1;
    298                     PageRange[] selectedPages = (PageRange[]) args.arg2;
    299                     MediaSize mediaSize = (MediaSize) args.arg3;
    300                     Margins margins = (Margins) args.arg4;
    301                     final int pageCount = args.argi1;
    302                     args.recycle();
    303 
    304                     mPageAdapter.update(writtenPages, selectedPages, pageCount,
    305                             mediaSize, margins);
    306 
    307                 } break;
    308 
    309                 case MSG_START_PRELOAD: {
    310                     mPreloadController.startPreloadContent();
    311                 } break;
    312             }
    313         }
    314     }
    315 
    316     private final class PreloadController extends RecyclerView.OnScrollListener {
    317         private final RecyclerView mRecyclerView;
    318 
    319         private int mOldScrollState;
    320 
    321         public PreloadController(RecyclerView recyclerView) {
    322             mRecyclerView = recyclerView;
    323             mOldScrollState = mRecyclerView.getScrollState();
    324         }
    325 
    326         @Override
    327         public void onScrollStateChanged(RecyclerView recyclerView, int state) {
    328             switch (mOldScrollState) {
    329                 case RecyclerView.SCROLL_STATE_SETTLING: {
    330                     if (state == RecyclerView.SCROLL_STATE_IDLE
    331                             || state == RecyclerView.SCROLL_STATE_DRAGGING){
    332                         startPreloadContent();
    333                     }
    334                 } break;
    335 
    336                 case RecyclerView.SCROLL_STATE_IDLE:
    337                 case RecyclerView.SCROLL_STATE_DRAGGING: {
    338                     if (state == RecyclerView.SCROLL_STATE_SETTLING) {
    339                         stopPreloadContent();
    340                     }
    341                 } break;
    342             }
    343             mOldScrollState = state;
    344         }
    345 
    346         public void startPreloadContent() {
    347             PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
    348             if (pageAdapter != null && pageAdapter.isOpened()) {
    349                 PageRange shownPages = computeShownPages();
    350                 if (shownPages != null) {
    351                     pageAdapter.startPreloadContent(shownPages);
    352                 }
    353             }
    354         }
    355 
    356         public void stopPreloadContent() {
    357             PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
    358             if (pageAdapter != null && pageAdapter.isOpened()) {
    359                 pageAdapter.stopPreloadContent();
    360             }
    361         }
    362 
    363         private PageRange computeShownPages() {
    364             final int childCount = mRecyclerView.getChildCount();
    365             if (childCount > 0) {
    366                 LayoutManager layoutManager = mRecyclerView.getLayoutManager();
    367 
    368                 View firstChild = layoutManager.getChildAt(0);
    369                 ViewHolder firstHolder = mRecyclerView.getChildViewHolder(firstChild);
    370 
    371                 View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
    372                 ViewHolder lastHolder = mRecyclerView.getChildViewHolder(lastChild);
    373 
    374                 return new PageRange(firstHolder.getPosition(), lastHolder.getPosition());
    375             }
    376             return null;
    377         }
    378     }
    379 }
    380