394 lines
15 KiB
Java
394 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
|
* Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.ui.base;
|
|
|
|
import java.util.Date;
|
|
|
|
import android.app.Activity;
|
|
import android.app.ProgressDialog;
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.Messenger;
|
|
import android.os.Parcelable;
|
|
import android.os.SystemClock;
|
|
import android.support.v4.app.Fragment;
|
|
import android.support.v4.app.FragmentActivity;
|
|
import android.support.v4.app.FragmentManager;
|
|
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
|
import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity;
|
|
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
|
|
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
|
import org.sufficientlysecure.keychain.ui.RetryUploadDialogActivity;
|
|
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
import org.sufficientlysecure.keychain.util.Log;
|
|
|
|
/**
|
|
* Designed to be integrated into activities or fragments used for CryptoOperations.
|
|
* Encapsulates the execution of a crypto operation and handling of input pending cases.s
|
|
*
|
|
* @param <T> The type of input parcel sent to the operation
|
|
* @param <S> The type of result returned by the operation
|
|
*/
|
|
public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> {
|
|
|
|
private long operationStartTime;
|
|
|
|
public interface Callback<T extends Parcelable, S extends OperationResult> {
|
|
T createOperationInput();
|
|
|
|
void onCryptoOperationSuccess(S result);
|
|
|
|
void onCryptoOperationCancelled();
|
|
|
|
void onCryptoOperationError(S result);
|
|
|
|
boolean onCryptoSetProgress(String msg, int progress, int max);
|
|
}
|
|
|
|
public static abstract class AbstractCallback<T extends Parcelable, S extends OperationResult>
|
|
implements Callback<T,S> {
|
|
@Override
|
|
public void onCryptoOperationCancelled() {
|
|
throw new UnsupportedOperationException("Unexpectedly cancelled operation!!");
|
|
}
|
|
|
|
@Override
|
|
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// request codes from CryptoOperationHelper are created essentially
|
|
// a static property, used to identify requestCodes meant for this
|
|
// particular helper. a request code looks as follows:
|
|
// (id << 9) + (1<<8) + REQUEST_CODE_X
|
|
// that is, starting from LSB, there are 8 bits request code, 1
|
|
// fixed bit set, then 7 bit helper-id code. the first two
|
|
// summands are stored in the mHelperId for easy operation.
|
|
private final int mHelperId;
|
|
// bitmask for helperId is everything except the least 8 bits
|
|
public static final int HELPER_ID_BITMASK = ~0xff;
|
|
|
|
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
|
public static final int REQUEST_CODE_NFC = 2;
|
|
public static final int REQUEST_CODE_ENABLE_ORBOT = 3;
|
|
public static final int REQUEST_CODE_RETRY_UPLOAD = 4;
|
|
|
|
private Integer mProgressMessageResource;
|
|
private boolean mCancellable = false;
|
|
private Long minimumOperationDelay;
|
|
|
|
private FragmentActivity mActivity;
|
|
private Fragment mFragment;
|
|
private Callback<T, S> mCallback;
|
|
|
|
private boolean mUseFragment; // short hand for mActivity == null
|
|
|
|
/**
|
|
* If OperationHelper is being integrated into an activity
|
|
*/
|
|
public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
|
|
Integer progressMessageString) {
|
|
mHelperId = (id << 9) + (1<<8);
|
|
mActivity = activity;
|
|
mUseFragment = false;
|
|
mCallback = callback;
|
|
mProgressMessageResource = progressMessageString;
|
|
}
|
|
|
|
/**
|
|
* if OperationHelper is being integrated into a fragment
|
|
*/
|
|
public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
|
|
mHelperId = (id << 9) + (1<<8);
|
|
mFragment = fragment;
|
|
mUseFragment = true;
|
|
mProgressMessageResource = progressMessageString;
|
|
mCallback = callback;
|
|
}
|
|
|
|
public void setProgressMessageResource(int id) {
|
|
mProgressMessageResource = id;
|
|
}
|
|
|
|
public void setOperationMinimumDelay(Long delay) {
|
|
this.minimumOperationDelay = delay;
|
|
}
|
|
|
|
public void setProgressCancellable(boolean cancellable) {
|
|
mCancellable = cancellable;
|
|
}
|
|
|
|
private void initiateInputActivity(RequiredInputParcel requiredInput,
|
|
CryptoInputParcel cryptoInputParcel) {
|
|
|
|
Activity activity = mUseFragment ? mFragment.getActivity() : mActivity;
|
|
|
|
switch (requiredInput.mType) {
|
|
// always use CryptoOperationHelper.startActivityForResult!
|
|
case SECURITY_TOKEN_MOVE_KEY_TO_CARD:
|
|
case SECURITY_TOKEN_DECRYPT:
|
|
case SECURITY_TOKEN_SIGN: {
|
|
Intent intent = new Intent(activity, SecurityTokenOperationActivity.class);
|
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
|
startActivityForResult(intent, REQUEST_CODE_NFC);
|
|
return;
|
|
}
|
|
|
|
case PASSPHRASE:
|
|
case PASSPHRASE_SYMMETRIC:
|
|
case BACKUP_CODE: {
|
|
Intent intent = new Intent(activity, PassphraseDialogActivity.class);
|
|
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
|
intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
|
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
|
return;
|
|
}
|
|
|
|
case ENABLE_ORBOT: {
|
|
Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class);
|
|
intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
|
startActivityForResult(intent, REQUEST_CODE_ENABLE_ORBOT);
|
|
return;
|
|
}
|
|
|
|
case UPLOAD_FAIL_RETRY: {
|
|
Intent intent = new Intent(activity, RetryUploadDialogActivity.class);
|
|
intent.putExtra(RetryUploadDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
|
startActivityForResult(intent, REQUEST_CODE_RETRY_UPLOAD);
|
|
return;
|
|
}
|
|
|
|
default: {
|
|
throw new RuntimeException("Unhandled pending result!");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void startActivityForResult(Intent intent, int requestCode) {
|
|
if (mUseFragment) {
|
|
mFragment.startActivityForResult(intent, mHelperId + requestCode);
|
|
} else {
|
|
mActivity.startActivityForResult(intent, mHelperId + requestCode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts the result of an activity started by this helper. Returns true if requestCode is
|
|
* recognized, false otherwise.
|
|
* @return true if requestCode was recognized, false otherwise
|
|
*/
|
|
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
|
Log.d(Constants.TAG, "received activity result in OperationHelper");
|
|
|
|
if ((requestCode & HELPER_ID_BITMASK) != mHelperId) {
|
|
// this wasn't meant for us to handle
|
|
return false;
|
|
}
|
|
Log.d(Constants.TAG, "handling activity result in OperationHelper");
|
|
// filter out mHelperId from requestCode
|
|
requestCode ^= mHelperId;
|
|
|
|
if (resultCode == Activity.RESULT_CANCELED) {
|
|
mCallback.onCryptoOperationCancelled();
|
|
return true;
|
|
}
|
|
|
|
switch (requestCode) {
|
|
case REQUEST_CODE_PASSPHRASE: {
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
CryptoInputParcel cryptoInput =
|
|
data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
|
cryptoOperation(cryptoInput);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case REQUEST_CODE_NFC: {
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
CryptoInputParcel cryptoInput =
|
|
data.getParcelableExtra(SecurityTokenOperationActivity.RESULT_CRYPTO_INPUT);
|
|
cryptoOperation(cryptoInput);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case REQUEST_CODE_ENABLE_ORBOT: {
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
CryptoInputParcel cryptoInput =
|
|
data.getParcelableExtra(
|
|
OrbotRequiredDialogActivity.RESULT_CRYPTO_INPUT);
|
|
cryptoOperation(cryptoInput);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case REQUEST_CODE_RETRY_UPLOAD: {
|
|
if (resultCode == Activity.RESULT_OK) {
|
|
CryptoInputParcel cryptoInput =
|
|
data.getParcelableExtra(
|
|
RetryUploadDialogActivity.RESULT_CRYPTO_INPUT);
|
|
cryptoOperation(cryptoInput);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected void dismissProgress() {
|
|
FragmentManager fragmentManager =
|
|
mUseFragment ? mFragment.getFragmentManager() :
|
|
mActivity.getSupportFragmentManager();
|
|
|
|
if (fragmentManager == null) { // the fragment holding us has died
|
|
// fragmentManager was null when used with DialogFragments. (they close on click?)
|
|
return;
|
|
}
|
|
|
|
ProgressDialogFragment progressDialogFragment =
|
|
(ProgressDialogFragment) fragmentManager.findFragmentByTag(
|
|
ServiceProgressHandler.TAG_PROGRESS_DIALOG);
|
|
|
|
if (progressDialogFragment == null) {
|
|
return;
|
|
}
|
|
|
|
progressDialogFragment.dismissAllowingStateLoss();
|
|
|
|
}
|
|
|
|
public void cryptoOperation(final CryptoInputParcel cryptoInput) {
|
|
|
|
FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity;
|
|
|
|
T operationInput = mCallback.createOperationInput();
|
|
if (operationInput == null) {
|
|
return;
|
|
}
|
|
|
|
// Send all information needed to service to edit key in other thread
|
|
Intent intent = new Intent(activity, KeychainService.class);
|
|
|
|
intent.putExtra(KeychainService.EXTRA_OPERATION_INPUT, operationInput);
|
|
intent.putExtra(KeychainService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
|
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) {
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
// handle messages by standard KeychainIntentServiceHandler first
|
|
super.handleMessage(message);
|
|
|
|
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
|
|
// get returned data bundle
|
|
Bundle returnData = message.getData();
|
|
if (returnData == null) {
|
|
return;
|
|
}
|
|
|
|
final OperationResult result =
|
|
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
|
|
|
onHandleResult(result);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onSetProgress(String msg, int progress, int max) {
|
|
// allow handling of progress in fragment, or delegate upwards
|
|
if (!mCallback.onCryptoSetProgress(msg, progress, max)) {
|
|
super.onSetProgress(msg, progress, max);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create a new Messenger for the communication back
|
|
Messenger messenger = new Messenger(saveHandler);
|
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
|
|
|
if (mProgressMessageResource != null) {
|
|
saveHandler.showProgressDialog(
|
|
activity.getString(mProgressMessageResource),
|
|
ProgressDialog.STYLE_HORIZONTAL, mCancellable);
|
|
}
|
|
|
|
activity.startService(intent);
|
|
}
|
|
|
|
public void cryptoOperation() {
|
|
operationStartTime = SystemClock.elapsedRealtime();
|
|
cryptoOperation(CryptoInputParcel.createCryptoInputParcel(new Date()));
|
|
}
|
|
|
|
private void onHandleResult(final OperationResult result) {
|
|
Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success());
|
|
|
|
if (result instanceof InputPendingResult) {
|
|
InputPendingResult pendingResult = (InputPendingResult) result;
|
|
if (pendingResult.isPending()) {
|
|
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
|
|
initiateInputActivity(requiredInput, pendingResult.mCryptoInputParcel);
|
|
return;
|
|
}
|
|
}
|
|
|
|
dismissProgress();
|
|
|
|
long elapsedTime = SystemClock.elapsedRealtime() - operationStartTime;
|
|
if (minimumOperationDelay == null || elapsedTime > minimumOperationDelay) {
|
|
returnResultToCallback(result);
|
|
return;
|
|
}
|
|
|
|
new Handler().postDelayed(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
returnResultToCallback(result);
|
|
}
|
|
}, minimumOperationDelay - elapsedTime);
|
|
}
|
|
|
|
private void returnResultToCallback(OperationResult result) {
|
|
try {
|
|
if (result.success()) {
|
|
// noinspection unchecked, because type erasure :(
|
|
mCallback.onCryptoOperationSuccess((S) result);
|
|
} else {
|
|
// noinspection unchecked, because type erasure :(
|
|
mCallback.onCryptoOperationError((S) result);
|
|
}
|
|
} catch (ClassCastException e) {
|
|
throw new AssertionError("bad return class ("
|
|
+ result.getClass().getSimpleName() + "), this is a programming error!");
|
|
}
|
|
}
|
|
} |