From 7da783228499ea9d5b0a98201e8ba5b17e30bb10 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 31 Aug 2014 17:32:13 +0200 Subject: Add cancelable mechanism and support in key import Closes #323 --- .../keychain/helper/ExportHelper.java | 9 +- .../keychain/pgp/PgpImportExport.java | 23 +++++- .../keychain/service/KeychainIntentService.java | 21 ++++- .../service/KeychainIntentServiceHandler.java | 8 +- .../keychain/service/OperationResultParcel.java | 10 ++- .../keychain/service/OperationResults.java | 35 ++++---- .../keychain/ui/ImportKeysActivity.java | 5 +- .../keychain/ui/dialog/ProgressDialogFragment.java | 96 ++++++++++------------ OpenKeychain/src/main/res/values/strings.xml | 2 + 9 files changed, 119 insertions(+), 90 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index bcd57b290..f065dbc6d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -121,14 +121,7 @@ public class ExportHelper { // Message is received after exporting is done in KeychainIntentService KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity, mActivity.getString(R.string.progress_exporting), - ProgressDialog.STYLE_HORIZONTAL, - true, - new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - mActivity.stopService(intent); - } - }) { + ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index 0bc3ac0ab..669a7d37e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -46,6 +46,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; public class PgpImportExport { @@ -125,11 +126,20 @@ public class PgpImportExport { } /** Imports keys from given data. If keyIds is given only those are imported */ + public ImportKeyResult importKeyRings(List entries, + AtomicBoolean cancelled) { + return importKeyRings(entries.iterator(), entries.size(), cancelled); + } + public ImportKeyResult importKeyRings(List entries) { - return importKeyRings(entries.iterator(), entries.size()); + return importKeyRings(entries.iterator(), entries.size(), null); } public ImportKeyResult importKeyRings(Iterator entries, int num) { + return importKeyRings(entries, num, null); + } + public ImportKeyResult importKeyRings(Iterator entries, int num, + AtomicBoolean cancelled) { updateProgress(R.string.progress_importing, 0, 100); // If there aren't even any keys, do nothing here. @@ -143,6 +153,12 @@ public class PgpImportExport { int position = 0; double progSteps = 100.0 / num; for (ParcelableKeyRing entry : new IterableIterator(entries)) { + // Has this action been cancelled? If so, don't proceed any further + if (cancelled != null && cancelled.get()) { + Log.d(Constants.TAG, "CANCELLED!"); + break; + } + try { UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes()); @@ -208,9 +224,12 @@ public class PgpImportExport { } } if (log.containsWarnings()) { - resultType |= ImportKeyResult.RESULT_WITH_WARNINGS; + resultType |= ImportKeyResult.RESULT_WARNINGS; } } + if (cancelled != null && cancelled.get()) { + resultType |= ImportKeyResult.RESULT_CANCELLED; + } return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 021e6bc07..bc570385a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -72,6 +72,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * This Service contains all important long lasting operations for APG. It receives Intents with @@ -110,6 +111,8 @@ public class KeychainIntentService extends IntentService public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE"; + public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; + /* keys for data bundle */ // encrypt, decrypt, import export @@ -196,6 +199,8 @@ public class KeychainIntentService extends IntentService Messenger mMessenger; private boolean mIsCanceled; + // this attribute can possibly merged with the one above? not sure... + private AtomicBoolean mActionCanceled = new AtomicBoolean(false); public KeychainIntentService() { super("KeychainIntentService"); @@ -214,6 +219,10 @@ public class KeychainIntentService extends IntentService */ @Override protected void onHandleIntent(Intent intent) { + + // We have not been cancelled! (yet) + mActionCanceled.set(false); + Bundle extras = intent.getExtras(); if (extras == null) { Log.e(Constants.TAG, "Extras bundle is null!"); @@ -500,7 +509,7 @@ public class KeychainIntentService extends IntentService ProviderHelper providerHelper = new ProviderHelper(this); PgpImportExport pgpImportExport = new PgpImportExport(this, providerHelper, this); - ImportKeyResult result = pgpImportExport.importKeyRings(entries); + ImportKeyResult result = pgpImportExport.importKeyRings(entries, mActionCanceled); if (result.mSecret > 0) { providerHelper.consolidateDatabaseStep1(this); @@ -612,7 +621,6 @@ public class KeychainIntentService extends IntentService ArrayList keyRings = new ArrayList(entries.size()); for (ImportKeysListEntry entry : entries) { - Keyserver server; if (entry.getOrigin() == null) { server = new HkpKeyserver(keyServer); @@ -874,6 +882,15 @@ public class KeychainIntentService extends IntentService } } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_CANCEL.equals(intent.getAction())) { + mActionCanceled.set(true); + return START_STICKY; + } + return super.onStartCommand(intent, flags, startId); + } + private String getOriginalFilename(Bundle data) throws PgpGeneralException, FileNotFoundException { int target = data.getInt(TARGET); switch (target) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 0cdbe708e..1772dc6ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -60,18 +60,16 @@ public class KeychainIntentServiceHandler extends Handler { public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage, int progressDialogStyle) { - this(activity, progressDialogMessage, progressDialogStyle, false, null); + this(activity, progressDialogMessage, progressDialogStyle, false); } public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage, - int progressDialogStyle, boolean cancelable, - OnCancelListener onCancelListener) { + int progressDialogStyle, boolean cancelable) { this.mActivity = activity; this.mProgressDialogFragment = ProgressDialogFragment.newInstance( progressDialogMessage, progressDialogStyle, - cancelable, - onCancelListener); + cancelable); } public void showProgressDialog(FragmentActivity activity) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index fe699224b..151c1761e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -55,13 +55,17 @@ public class OperationResultParcel implements Parcelable { public static final String EXTRA_RESULT = "operation_result"; - /** Holds the overall result, the number specifying varying degrees of success. The first bit - * is 0 on overall success, 1 on overall failure. All other bits may be used for more specific - * conditions. */ + /** Holds the overall result, the number specifying varying degrees of success: + * - The first bit is 0 on overall success, 1 on overall failure + * - The second bit indicates if the action was cancelled - may still be an error or success! + * - The third bit should be set if the operation succeeded with warnings + * All other bits may be used for more specific conditions. */ final int mResult; public static final int RESULT_OK = 0; public static final int RESULT_ERROR = 1; + public static final int RESULT_CANCELLED = 2; + public static final int RESULT_WARNINGS = 4; /// A list of log entries tied to the operation result. final OperationLog mLog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java index f3d0b9e9b..822c069cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -26,6 +26,7 @@ import android.view.View; import com.github.johnpersano.supertoasts.SuperCardToast; import com.github.johnpersano.supertoasts.SuperToast; +import com.github.johnpersano.supertoasts.SuperToast.Duration; import com.github.johnpersano.supertoasts.util.OnClickWrapper; import com.github.johnpersano.supertoasts.util.Style; @@ -44,16 +45,14 @@ public abstract class OperationResults { public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; // At least one new key - public static final int RESULT_OK_NEWKEYS = 2; + public static final int RESULT_OK_NEWKEYS = 8; // At least one updated key - public static final int RESULT_OK_UPDATED = 4; + public static final int RESULT_OK_UPDATED = 16; // At least one key failed (might still be an overall success) - public static final int RESULT_WITH_ERRORS = 8; - // There are warnings in the log - public static final int RESULT_WITH_WARNINGS = 16; + public static final int RESULT_WITH_ERRORS = 32; // No keys to import... - public static final int RESULT_FAIL_NOTHING = 32 + 1; + public static final int RESULT_FAIL_NOTHING = 64 + 1; public boolean isOkBoth() { return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED)) @@ -119,15 +118,20 @@ public abstract class OperationResults { if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) { String withWarnings; + duration = Duration.EXTRA_LONG; + color = Style.GREEN; + withWarnings = ""; + // Any warnings? - if ((resultType & ImportKeyResult.RESULT_WITH_WARNINGS) > 0) { + if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) { duration = 0; color = Style.ORANGE; - withWarnings = activity.getResources().getString(R.string.import_with_warnings); - } else { - duration = SuperToast.Duration.LONG; - color = Style.GREEN; - withWarnings = ""; + withWarnings += activity.getString(R.string.import_with_warnings); + } + if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) { + duration = 0; + color = Style.ORANGE; + withWarnings += activity.getString(R.string.import_with_cancelled); } // New and updated keys @@ -152,13 +156,14 @@ public abstract class OperationResults { duration = 0; color = Style.RED; if (isFailNothing()) { - str = activity.getString(R.string.import_error_nothing); + str = activity.getString((resultType & ImportKeyResult.RESULT_CANCELLED) > 0 + ? R.string.import_error_nothing_cancelled + : R.string.import_error_nothing); } else { str = activity.getString(R.string.import_error); } } - // TODO: externalize into Notify class? boolean button = getLog() != null && !getLog().isEmpty(); SuperCardToast toast = new SuperCardToast(activity, button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, @@ -243,7 +248,7 @@ public abstract class OperationResults { } // Some old key was updated - public static final int UPDATED = 2; + public static final int UPDATED = 4; // Public key was saved public static final int SAVED_PUBLIC = 8; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 96fa11363..7ad7d1007 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.net.Uri; import android.nfc.NdefMessage; @@ -447,7 +449,8 @@ public class ImportKeysActivity extends ActionBarActivity { KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( this, getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL) { + ProgressDialog.STYLE_HORIZONTAL, + true) { public void handleMessage(Message message) { // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index 0324dd3b9..49a8ac49f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -21,32 +21,26 @@ import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnKeyListener; +import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.ContextThemeWrapper; import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.KeychainIntentService; public class ProgressDialogFragment extends DialogFragment { private static final String ARG_MESSAGE = "message"; private static final String ARG_STYLE = "style"; private static final String ARG_CANCELABLE = "cancelable"; - private OnCancelListener mOnCancelListener; - - /** - * Creates new instance of this fragment - * - * @param message - * @param style - * @param cancelable - * @return - */ - public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable, - OnCancelListener onCancelListener) { + /** Creates new instance of this fragment */ + public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable) { ProgressDialogFragment frag = new ProgressDialogFragment(); Bundle args = new Bundle(); args.putString(ARG_MESSAGE, message); @@ -54,28 +48,16 @@ public class ProgressDialogFragment extends DialogFragment { args.putBoolean(ARG_CANCELABLE, cancelable); frag.setArguments(args); - frag.mOnCancelListener = onCancelListener; return frag; } - /** - * Updates progress of dialog - * - * @param messageId - * @param progress - * @param max - */ + /** Updates progress of dialog */ public void setProgress(int messageId, int progress, int max) { setProgress(getString(messageId), progress, max); } - /** - * Updates progress of dialog - * - * @param progress - * @param max - */ + /** Updates progress of dialog */ public void setProgress(int progress, int max) { ProgressDialog dialog = (ProgressDialog) getDialog(); @@ -83,13 +65,7 @@ public class ProgressDialogFragment extends DialogFragment { dialog.setMax(max); } - /** - * Updates progress of dialog - * - * @param message - * @param progress - * @param max - */ + /** Updates progress of dialog */ public void setProgress(String message, int progress, int max) { ProgressDialog dialog = (ProgressDialog) getDialog(); @@ -98,15 +74,6 @@ public class ProgressDialogFragment extends DialogFragment { dialog.setMax(max); } - @Override - public void onCancel(DialogInterface dialog) { - super.onCancel(dialog); - - if (this.mOnCancelListener != null) { - this.mOnCancelListener.onCancel(dialog); - } - } - /** * Creates dialog */ @@ -121,41 +88,62 @@ public class ProgressDialogFragment extends DialogFragment { ProgressDialog dialog = new ProgressDialog(context); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + // We never use the builtin cancel method dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); String message = getArguments().getString(ARG_MESSAGE); int style = getArguments().getInt(ARG_STYLE); - boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE); + final boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE); dialog.setMessage(message); dialog.setProgressStyle(style); + // If this is supposed to be cancelable, add our (custom) cancel mechanic if (cancelable) { + // Just show the button, take care of the onClickListener afterwards (in onStart) dialog.setButton(DialogInterface.BUTTON_NEGATIVE, - activity.getString(R.string.progress_cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); + activity.getString(R.string.progress_cancel), (DialogInterface.OnClickListener) null); } - // Disable the back button + // Disable the back button regardless OnKeyListener keyListener = new OnKeyListener() { - @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { + if (cancelable) { + ((ProgressDialog) dialog).getButton( + DialogInterface.BUTTON_NEGATIVE).performClick(); + } + // return true, indicating we handled this return true; } return false; } - }; dialog.setOnKeyListener(keyListener); return dialog; } + + @Override + public void onStart() { + super.onStart(); + + // Override the default behavior so the dialog is NOT dismissed on click + Button negative = ((ProgressDialog) getDialog()).getButton(DialogInterface.BUTTON_NEGATIVE); + negative.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // send a cancel message. note that this message will be handled by + // KeychainIntentService.onStartCommand, which runs in this thread, + // not the service one, and will not queue up a command. + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_CANCEL); + getActivity().startService(intent); + } + }); + + } + } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 8eb452df6..270feb099 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -381,8 +381,10 @@ View Log Nothing to import. + Import cancelled. Error importing keys! , with warnings + , until cancelled Decrypt File with OpenKeychain -- cgit v1.2.3