PrintPreviewController.java revision 15cbc8a03250eafdf947cd8ad4e77f34444d5ba4
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 final PrintActivity mActivity; 49 50 private final MutexFileProvider mFileProvider; 51 private final MyHandler mHandler; 52 53 private final PageAdapter mPageAdapter; 54 private final StaggeredGridLayoutManager mLayoutManger; 55 56 private final PrintOptionsLayout mPrintOptionsLayout; 57 private final RecyclerView mRecyclerView; 58 private final PrintContentView mContentView; 59 private final EmbeddedContentContainer mEmbeddedContentContainer; 60 61 private final PreloadController mPreloadController; 62 63 private int mDocumentPageCount; 64 65 public PrintPreviewController(PrintActivity activity, MutexFileProvider fileProvider) { 66 mActivity = activity; 67 mHandler = new MyHandler(activity.getMainLooper()); 68 mFileProvider = fileProvider; 69 70 mPrintOptionsLayout = (PrintOptionsLayout) activity.findViewById(R.id.options_container); 71 mPageAdapter = new PageAdapter(activity, activity, this); 72 73 final int columnCount = mActivity.getResources().getInteger( 74 R.integer.preview_page_per_row_count); 75 76 mLayoutManger = new StaggeredGridLayoutManager(columnCount, OrientationHelper.VERTICAL); 77 78 mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content); 79 mRecyclerView.setLayoutManager(mLayoutManger); 80 mRecyclerView.setAdapter(mPageAdapter); 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 && 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 Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE); 188 mHandler.enqueueOperation(operation); 189 } 190 }); 191 } 192 193 public void destroy() { 194 mPageAdapter.destroy(); 195 } 196 197 @Override 198 public int getWidth() { 199 return mEmbeddedContentContainer.getWidth(); 200 } 201 202 @Override 203 public int getHeight() { 204 return mEmbeddedContentContainer.getHeight(); 205 } 206 207 @Override 208 public void setColumnCount(int columnCount) { 209 mLayoutManger.setSpanCount(columnCount); 210 } 211 212 @Override 213 public void setPadding(int left, int top , int right, int bottom) { 214 mRecyclerView.setPadding(left, top, right, bottom); 215 } 216 217 private final class MyHandler extends Handler { 218 public static final int MSG_OPEN = 1; 219 public static final int MSG_CLOSE = 2; 220 public static final int MSG_DESTROY = 3; 221 public static final int MSG_UPDATE = 4; 222 public static final int MSG_START_PRELOAD = 5; 223 224 private boolean mAsyncOperationInProgress; 225 226 private final Runnable mOnAsyncOperationDoneCallback = new Runnable() { 227 @Override 228 public void run() { 229 mAsyncOperationInProgress = false; 230 handleNextOperation(); 231 } 232 }; 233 234 private final List<Message> mPendingOperations = new ArrayList<>(); 235 236 public MyHandler(Looper looper) { 237 super(looper, null, false); 238 } 239 240 public void enqueueOperation(Message message) { 241 mPendingOperations.add(message); 242 handleNextOperation(); 243 } 244 245 public void handleNextOperation() { 246 while (!mPendingOperations.isEmpty() && !mAsyncOperationInProgress) { 247 Message operation = mPendingOperations.remove(0); 248 handleMessage(operation); 249 } 250 } 251 252 @Override 253 public void handleMessage(Message message) { 254 switch (message.what) { 255 case MSG_OPEN: { 256 try { 257 File file = mFileProvider.acquireFile(PrintPreviewController.this); 258 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, 259 ParcelFileDescriptor.MODE_READ_ONLY); 260 261 mAsyncOperationInProgress = true; 262 mPageAdapter.open(pfd, new Runnable() { 263 @Override 264 public void run() { 265 if (mDocumentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { 266 mDocumentPageCount = mPageAdapter.getFilePageCount(); 267 mActivity.updateOptionsUi(); 268 } 269 mOnAsyncOperationDoneCallback.run(); 270 } 271 }); 272 } catch (FileNotFoundException fnfe) { 273 /* ignore - file guaranteed to be there */ 274 } 275 } break; 276 277 case MSG_CLOSE: { 278 mAsyncOperationInProgress = true; 279 mPageAdapter.close(new Runnable() { 280 @Override 281 public void run() { 282 mFileProvider.releaseFile(); 283 mOnAsyncOperationDoneCallback.run(); 284 } 285 }); 286 } break; 287 288 case MSG_DESTROY: { 289 mPageAdapter.destroy(); 290 handleNextOperation(); 291 } break; 292 293 case MSG_UPDATE: { 294 SomeArgs args = (SomeArgs) message.obj; 295 PageRange[] writtenPages = (PageRange[]) args.arg1; 296 PageRange[] selectedPages = (PageRange[]) args.arg2; 297 MediaSize mediaSize = (MediaSize) args.arg3; 298 Margins margins = (Margins) args.arg4; 299 final int pageCount = args.argi1; 300 args.recycle(); 301 302 mPageAdapter.update(writtenPages, selectedPages, pageCount, 303 mediaSize, margins); 304 305 } break; 306 307 case MSG_START_PRELOAD: { 308 mPreloadController.startPreloadContent(); 309 } break; 310 } 311 } 312 } 313 314 private final class PreloadController implements RecyclerView.OnScrollListener { 315 private final RecyclerView mRecyclerView; 316 317 private int mOldScrollState; 318 319 public PreloadController(RecyclerView recyclerView) { 320 mRecyclerView = recyclerView; 321 mOldScrollState = mRecyclerView.getScrollState(); 322 } 323 324 @Override 325 public void onScrollStateChanged(int state) { 326 switch (mOldScrollState) { 327 case RecyclerView.SCROLL_STATE_SETTLING: { 328 if (state == RecyclerView.SCROLL_STATE_IDLE 329 || state == RecyclerView.SCROLL_STATE_DRAGGING){ 330 startPreloadContent(); 331 } 332 } break; 333 334 case RecyclerView.SCROLL_STATE_IDLE: 335 case RecyclerView.SCROLL_STATE_DRAGGING: { 336 if (state == RecyclerView.SCROLL_STATE_SETTLING) { 337 stopPreloadContent(); 338 } 339 } break; 340 } 341 mOldScrollState = state; 342 } 343 344 @Override 345 public void onScrolled(int dx, int dy) { 346 /* do nothing */ 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