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