/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.printspooler.ui;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
import android.print.PrintDocumentInfo;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.view.View;
import com.android.internal.os.SomeArgs;
import com.android.printspooler.R;
import com.android.printspooler.model.MutexFileProvider;
import com.android.printspooler.widget.PrintContentView;
import com.android.printspooler.widget.EmbeddedContentContainer;
import com.android.printspooler.widget.PrintOptionsLayout;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallback,
PageAdapter.PreviewArea, EmbeddedContentContainer.OnSizeChangeListener {
private final PrintActivity mActivity;
private final MutexFileProvider mFileProvider;
private final MyHandler mHandler;
private final PageAdapter mPageAdapter;
private final GridLayoutManager mLayoutManger;
private final PrintOptionsLayout mPrintOptionsLayout;
private final RecyclerView mRecyclerView;
private final PrintContentView mContentView;
private final EmbeddedContentContainer mEmbeddedContentContainer;
private final PreloadController mPreloadController;
private int mDocumentPageCount;
public PrintPreviewController(PrintActivity activity, MutexFileProvider fileProvider) {
mActivity = activity;
mHandler = new MyHandler(activity.getMainLooper());
mFileProvider = fileProvider;
mPrintOptionsLayout = (PrintOptionsLayout) activity.findViewById(R.id.options_container);
mPageAdapter = new PageAdapter(activity, activity, this);
final int columnCount = mActivity.getResources().getInteger(
R.integer.preview_page_per_row_count);
mLayoutManger = new GridLayoutManager(mActivity, columnCount);
mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
mRecyclerView.setLayoutManager(mLayoutManger);
mRecyclerView.setAdapter(mPageAdapter);
mRecyclerView.setItemViewCacheSize(0);
mPreloadController = new PreloadController(mRecyclerView);
mRecyclerView.setOnScrollListener(mPreloadController);
mContentView = (PrintContentView) activity.findViewById(R.id.options_content);
mEmbeddedContentContainer = (EmbeddedContentContainer) activity.findViewById(
R.id.embedded_content_container);
mEmbeddedContentContainer.setOnSizeChangeListener(this);
}
@Override
public void onSizeChanged(int width, int height) {
mPageAdapter.onPreviewAreaSizeChanged();
}
public boolean isOptionsOpened() {
return mContentView.isOptionsOpened();
}
public void closeOptions() {
mContentView.closeOptions();
}
public void setUiShown(boolean shown) {
if (shown) {
mRecyclerView.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.GONE);
}
}
public void onOrientationChanged() {
// Adjust the print option column count.
final int optionColumnCount = mActivity.getResources().getInteger(
R.integer.print_option_column_count);
mPrintOptionsLayout.setColumnCount(optionColumnCount);
mPageAdapter.onOrientationChanged();
}
public int getFilePageCount() {
return mPageAdapter.getFilePageCount();
}
public PageRange[] getSelectedPages() {
return mPageAdapter.getSelectedPages();
}
public PageRange[] getRequestedPages() {
return mPageAdapter.getRequestedPages();
}
public void onContentUpdated(boolean documentChanged, int documentPageCount,
PageRange[] writtenPages, PageRange[] selectedPages, MediaSize mediaSize,
Margins minMargins) {
boolean contentChanged = false;
if (documentChanged) {
contentChanged = true;
}
if (documentPageCount != mDocumentPageCount) {
mDocumentPageCount = documentPageCount;
contentChanged = true;
}
if (contentChanged) {
// If not closed, close as we start over.
if (mPageAdapter.isOpened()) {
Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
mHandler.enqueueOperation(operation);
}
}
// The content changed. In this case we have to invalidate
// all rendered pages and reopen the file...
if ((contentChanged || !mPageAdapter.isOpened()) && writtenPages != null) {
Message operation = mHandler.obtainMessage(MyHandler.MSG_OPEN);
mHandler.enqueueOperation(operation);
}
// Update the attributes before after closed to avoid flicker.
SomeArgs args = SomeArgs.obtain();
args.arg1 = writtenPages;
args.arg2 = selectedPages;
args.arg3 = mediaSize;
args.arg4 = minMargins;
args.argi1 = documentPageCount;
Message operation = mHandler.obtainMessage(MyHandler.MSG_UPDATE, args);
mHandler.enqueueOperation(operation);
// If document changed and has pages we want to start preloading.
if (contentChanged && writtenPages != null) {
operation = mHandler.obtainMessage(MyHandler.MSG_START_PRELOAD);
mHandler.enqueueOperation(operation);
}
}
@Override
public void onReleaseRequested(final File file) {
// This is called from the async task's single threaded executor
// thread, i.e. not on the main thread - so post a message.
mHandler.post(new Runnable() {
@Override
public void run() {
// At this point the other end will write to the file, hence
// we have to close it and reopen after the write completes.
if (mPageAdapter.isOpened()) {
Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
mHandler.enqueueOperation(operation);
}
}
});
}
public void destroy(Runnable callback) {
mHandler.cancelQueuedOperations();
mRecyclerView.setAdapter(null);
mPageAdapter.destroy(callback);
}
@Override
public int getWidth() {
return mEmbeddedContentContainer.getWidth();
}
@Override
public int getHeight() {
return mEmbeddedContentContainer.getHeight();
}
@Override
public void setColumnCount(int columnCount) {
mLayoutManger.setSpanCount(columnCount);
}
@Override
public void setPadding(int left, int top , int right, int bottom) {
mRecyclerView.setPadding(left, top, right, bottom);
}
private final class MyHandler extends Handler {
public static final int MSG_OPEN = 1;
public static final int MSG_CLOSE = 2;
public static final int MSG_UPDATE = 4;
public static final int MSG_START_PRELOAD = 5;
private boolean mAsyncOperationInProgress;
private final Runnable mOnAsyncOperationDoneCallback = new Runnable() {
@Override
public void run() {
mAsyncOperationInProgress = false;
handleNextOperation();
}
};
private final List<Message> mPendingOperations = new ArrayList<>();
public MyHandler(Looper looper) {
super(looper, null, false);
}
public void cancelQueuedOperations() {
mPendingOperations.clear();
}
public void enqueueOperation(Message message) {
mPendingOperations.add(message);
handleNextOperation();
}
public void handleNextOperation() {
while (!mPendingOperations.isEmpty() && !mAsyncOperationInProgress) {
Message operation = mPendingOperations.remove(0);
handleMessage(operation);
}
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_OPEN: {
try {
File file = mFileProvider.acquireFile(PrintPreviewController.this);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_ONLY);
mAsyncOperationInProgress = true;
mPageAdapter.open(pfd, new Runnable() {
@Override
public void run() {
if (mDocumentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
mDocumentPageCount = mPageAdapter.getFilePageCount();
mActivity.updateOptionsUi();
}
mOnAsyncOperationDoneCallback.run();
}
});
} catch (FileNotFoundException fnfe) {
/* ignore - file guaranteed to be there */
}
} break;
case MSG_CLOSE: {
mAsyncOperationInProgress = true;
mPageAdapter.close(new Runnable() {
@Override
public void run() {
mFileProvider.releaseFile();
mOnAsyncOperationDoneCallback.run();
}
});
} break;
case MSG_UPDATE: {
SomeArgs args = (SomeArgs) message.obj;
PageRange[] writtenPages = (PageRange[]) args.arg1;
PageRange[] selectedPages = (PageRange[]) args.arg2;
MediaSize mediaSize = (MediaSize) args.arg3;
Margins margins = (Margins) args.arg4;
final int pageCount = args.argi1;
args.recycle();
mPageAdapter.update(writtenPages, selectedPages, pageCount,
mediaSize, margins);
} break;
case MSG_START_PRELOAD: {
mPreloadController.startPreloadContent();
} break;
}
}
}
private final class PreloadController extends RecyclerView.OnScrollListener {
private final RecyclerView mRecyclerView;
private int mOldScrollState;
public PreloadController(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mOldScrollState = mRecyclerView.getScrollState();
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int state) {
switch (mOldScrollState) {
case RecyclerView.SCROLL_STATE_SETTLING: {
if (state == RecyclerView.SCROLL_STATE_IDLE
|| state == RecyclerView.SCROLL_STATE_DRAGGING){
startPreloadContent();
}
} break;
case RecyclerView.SCROLL_STATE_IDLE:
case RecyclerView.SCROLL_STATE_DRAGGING: {
if (state == RecyclerView.SCROLL_STATE_SETTLING) {
stopPreloadContent();
}
} break;
}
mOldScrollState = state;
}
public void startPreloadContent() {
PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
if (pageAdapter != null && pageAdapter.isOpened()) {
PageRange shownPages = computeShownPages();
if (shownPages != null) {
pageAdapter.startPreloadContent(shownPages);
}
}
}
public void stopPreloadContent() {
PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
if (pageAdapter != null && pageAdapter.isOpened()) {
pageAdapter.stopPreloadContent();
}
}
private PageRange computeShownPages() {
final int childCount = mRecyclerView.getChildCount();
if (childCount > 0) {
LayoutManager layoutManager = mRecyclerView.getLayoutManager();
View firstChild = layoutManager.getChildAt(0);
ViewHolder firstHolder = mRecyclerView.getChildViewHolder(firstChild);
View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
ViewHolder lastHolder = mRecyclerView.getChildViewHolder(lastChild);
return new PageRange(firstHolder.getPosition(), lastHolder.getPosition());
}
return null;
}
}
}