diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java index 2ae3ec6268a78..7a6aad6a96f5d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java @@ -16,6 +16,7 @@ package com.android.printspooler.model; +import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; @@ -57,6 +58,8 @@ public final class RemotePrintDocument { private static final boolean DEBUG = false; + private static final long FORCE_CANCEL_TIMEOUT = 1000; // ms + private static final int STATE_INITIAL = 0; private static final int STATE_STARTED = 1; private static final int STATE_UPDATING = 2; @@ -212,7 +215,7 @@ public final class RemotePrintDocument { // cancellation and start over. if (mCurrentCommand != null && (mCurrentCommand.isRunning() || mCurrentCommand.isPending())) { - mCurrentCommand.cancel(); + mCurrentCommand.cancel(false); } // Schedule a layout command. @@ -233,7 +236,7 @@ public final class RemotePrintDocument { // Cancel the current write as a new one is to be scheduled. if (mCurrentCommand instanceof WriteCommand && (mCurrentCommand.isPending() || mCurrentCommand.isRunning())) { - mCurrentCommand.cancel(); + mCurrentCommand.cancel(false); } // Schedule a write command. @@ -277,9 +280,9 @@ public final class RemotePrintDocument { } } - public void cancel() { + public void cancel(boolean force) { if (DEBUG) { - Log.i(LOG_TAG, "[CALLED] cancel()"); + Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")"); } mNextCommand = null; @@ -290,7 +293,7 @@ public final class RemotePrintDocument { mState = STATE_CANCELING; - mCurrentCommand.cancel(); + mCurrentCommand.cancel(force); } public void destroy() { @@ -441,8 +444,9 @@ public final class RemotePrintDocument { if (mCurrentCommand != null) { if (mCurrentCommand.isPending()) { mCurrentCommand.run(); + + mState = STATE_UPDATING; } - mState = STATE_UPDATING; } else { mState = STATE_UPDATED; } @@ -535,14 +539,17 @@ public final class RemotePrintDocument { protected final CommandDoneCallback mDoneCallback; + private final Handler mHandler; + protected ICancellationSignal mCancellation; private CharSequence mError; private int mState = STATE_PENDING; - public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, + public AsyncCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, CommandDoneCallback doneCallback) { + mHandler = new AsyncCommandHandler(looper); mAdapter = adapter; mDocument = document; mDoneCallback = doneCallback; @@ -556,7 +563,29 @@ public final class RemotePrintDocument { return mState == STATE_CANCELED; } - public final void cancel() { + /** + * If a force cancel is pending, remove it. This is usually called when a command returns + * and thereby does not need to be canceled anymore. + */ + protected void removeForceCancel() { + if (DEBUG) { + if (mHandler.hasMessages(AsyncCommandHandler.MSG_FORCE_CANCEL)) { + Log.i(LOG_TAG, "[FORCE CANCEL] Removed"); + } + } + + mHandler.removeMessages(AsyncCommandHandler.MSG_FORCE_CANCEL); + } + + /** + * Cancel the current command. + * + * @param force If set, does not wait for the {@link PrintDocumentAdapter} to cancel. This + * should only be used if this is the last command send to the as otherwise the + * {@link PrintDocumentAdapter adapter} might get commands while it is still + * running the old one. + */ + public final void cancel(boolean force) { if (isRunning()) { canceling(); if (mCancellation != null) { @@ -566,14 +595,25 @@ public final class RemotePrintDocument { Log.w(LOG_TAG, "Error while canceling", re); } } - } else if (isCanceling()) { - // Nothing to do - } else { - canceled(); - - // Done. - mDoneCallback.onDone(); } + + if (isCanceling()) { + if (force) { + if (DEBUG) { + Log.i(LOG_TAG, "[FORCE CANCEL] queued"); + } + mHandler.sendMessageDelayed( + mHandler.obtainMessage(AsyncCommandHandler.MSG_FORCE_CANCEL), + FORCE_CANCEL_TIMEOUT); + } + + return; + } + + canceled(); + + // Done. + mDoneCallback.onDone(); } protected final void canceling() { @@ -617,7 +657,7 @@ public final class RemotePrintDocument { } protected final void failed(CharSequence error) { - if (mState != STATE_RUNNING) { + if (mState != STATE_RUNNING && mState != STATE_CANCELING) { throw new IllegalStateException("Not running."); } mState = STATE_FAILED; @@ -632,6 +672,37 @@ public final class RemotePrintDocument { public CharSequence getError() { return mError; } + + /** + * Handler for the async command. + */ + private class AsyncCommandHandler extends Handler { + /** Message indicated the desire for to force cancel a command */ + final static int MSG_FORCE_CANCEL = 0; + + AsyncCommandHandler(@NonNull Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_FORCE_CANCEL: + if (isCanceling()) { + if (DEBUG) { + Log.i(LOG_TAG, "[FORCE CANCEL] executed"); + } + failed("Command did not respond to cancellation in " + + FORCE_CANCEL_TIMEOUT + " ms"); + + mDoneCallback.onDone(); + } + break; + default: + // not reached; + } + } + } } private static final class LayoutCommand extends AsyncCommand { @@ -646,7 +717,7 @@ public final class RemotePrintDocument { public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, PrintAttributes oldAttributes, PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) { - super(adapter, document, callback); + super(looper, adapter, document, callback); mHandler = new LayoutHandler(looper); mRemoteResultCallback = new LayoutResultCallback(mHandler); mOldAttributes.copyFrom(oldAttributes); @@ -795,6 +866,21 @@ public final class RemotePrintDocument { @Override public void handleMessage(Message message) { + // The command might have been force canceled, see + // AsyncCommand.AsyncCommandHandler#handleMessage + if (isFailed()) { + if (DEBUG) { + Log.i(LOG_TAG, "[CALLBACK] on canceled layout command"); + } + + return; + } else { + if (message.what != MSG_ON_LAYOUT_STARTED) { + // No need to force cancel anymore if layout finished + removeForceCancel(); + } + } + switch (message.what) { case MSG_ON_LAYOUT_STARTED: { ICancellationSignal cancellation = (ICancellationSignal) message.obj; @@ -882,7 +968,7 @@ public final class RemotePrintDocument { public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, int pageCount, PageRange[] pages, MutexFileProvider fileProvider, CommandDoneCallback callback) { - super(adapter, document, callback); + super(looper, adapter, document, callback); mContext = context; mHandler = new WriteHandler(looper); mRemoteResultCallback = new WriteResultCallback(mHandler); @@ -1052,6 +1138,21 @@ public final class RemotePrintDocument { @Override public void handleMessage(Message message) { + // The command might have been force canceled, see + // AsyncCommand.AsyncCommandHandler#handleMessage + if (isFailed()) { + if (DEBUG) { + Log.i(LOG_TAG, "[CALLBACK] on canceled write command"); + } + + return; + } else { + if (message.what != MSG_ON_WRITE_STARTED) { + // No need to force cancel anymore if write finished + removeForceCancel(); + } + } + switch (message.what) { case MSG_ON_WRITE_STARTED: { ICancellationSignal cancellation = (ICancellationSignal) message.obj; diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 08cd0b6434d49..64f5cc62201ec 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -320,8 +320,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat if (isFinishing() || (isFinalState(mState) && !mPrintedDocument.isUpdating())) { return; } - mPrintedDocument.cancel(); setState(STATE_PRINT_CANCELED); + mPrintedDocument.cancel(true); doFinish(); } }, PrintActivity.this); @@ -1013,7 +1013,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void requestCreatePdfFileOrFinish() { - mPrintedDocument.cancel(); + mPrintedDocument.cancel(false); if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) { startCreateDocumentActivity(); @@ -1130,7 +1130,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private void cancelPrint() { setState(STATE_PRINT_CANCELED); updateOptionsUi(); - mPrintedDocument.cancel(); + mPrintedDocument.cancel(true); doFinish(); } @@ -1889,7 +1889,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat public void onPrinterUnavailable(PrinterInfo printer) { if (mCurrentPrinter.getId().equals(printer.getId())) { setState(STATE_PRINTER_UNAVAILABLE); - mPrintedDocument.cancel(); + mPrintedDocument.cancel(false); ensureErrorUiShown(getString(R.string.print_error_printer_unavailable), PrintErrorFragment.ACTION_NONE); updateOptionsUi();