rewrite EncryptActivity data flow

This commit is contained in:
Vincent Breitmoser 2015-05-27 21:15:36 +02:00
parent e03e1e5cfc
commit 8f3e0a9110
10 changed files with 355 additions and 486 deletions

View File

@ -77,8 +77,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment
private long[] mPubMasterKeyIds;
private long mSignMasterKeyId = Constants.key.none;
public static final String[] USER_IDS_PROJECTION = new String[]{
UserPackets._ID,
UserPackets.MASTER_KEY_ID,
@ -149,19 +147,13 @@ public class CertifyKeyFragment extends CryptoOperationFragment
vActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
mSignMasterKeyId = masterKeyId;
}
});
View vCertifyButton = view.findViewById(R.id.certify_key_certify_button);
vCertifyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mSignMasterKeyId == Constants.key.none) {
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
if (selectedKeyId == Constants.key.none) {
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show();
} else {
@ -307,8 +299,10 @@ public class CertifyKeyFragment extends CryptoOperationFragment
Bundle data = new Bundle();
{
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
// fill values for this action
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
CertifyActionsParcel parcel = new CertifyActionsParcel(selectedKeyId);
parcel.mCertifyActions.addAll(certifyActions);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.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;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
public class EncryptActivity extends BaseActivity {
// preselect ids, for internal use
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// preselect keys given by intent
long signingKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
Fragment modeFragment = EncryptModeAsymmetricFragment.newInstance(signingKeyId, encryptionKeyIds);
transaction.replace(R.id.encrypt_mode_container, modeFragment);
transaction.commit();
}
}
public void toggleModeFragment() {
boolean symmetric = getModeFragment() instanceof EncryptModeAsymmetricFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.encrypt_mode_container,
symmetric
? EncryptModeSymmetricFragment.newInstance()
: EncryptModeAsymmetricFragment.newInstance(0, null)
);
// doesn't matter if the user doesn't look at the activity
transaction.commitAllowingStateLoss();
}
public EncryptModeFragment getModeFragment() {
return (EncryptModeFragment)
getSupportFragmentManager().findFragmentById(R.id.encrypt_mode_container);
}
}

View File

@ -18,39 +18,25 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
public class EncryptFilesActivity extends BaseActivity implements
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
EncryptFilesFragment.IMode {
public class EncryptFilesActivity extends EncryptActivity {
/* Intents */
// Intents
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA;
// enables ASCII Armor for file encryption when uri is given
public static final String EXTRA_ASCII_ARMOR = OpenKeychainIntents.ENCRYPT_EXTRA_ASCII_ARMOR;
// preselect ids, for internal use
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
Fragment mModeFragment;
EncryptFilesFragment mEncryptFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -62,19 +48,7 @@ public class EncryptFilesActivity extends BaseActivity implements
}
}, false);
// Handle intent actions
handleActions(getIntent(), savedInstanceState);
}
@Override
protected void initLayout() {
setContentView(R.layout.encrypt_files_activity);
}
/**
* Handles all actions with this intent
*/
private void handleActions(Intent intent, Bundle savedInstanceState) {
Intent intent = getIntent();
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
@ -88,10 +62,6 @@ public class EncryptFilesActivity extends BaseActivity implements
uris.add(intent.getData());
}
/*
* Android's Action
*/
// When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
// Files via content provider, override uri and action
@ -103,56 +73,21 @@ public class EncryptFilesActivity extends BaseActivity implements
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files");
EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
transaction.replace(R.id.encrypt_file_container, encryptFragment);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
@Override
public void onModeChanged(boolean symmetric) {
// switch fragments
getSupportFragmentManager().beginTransaction()
.replace(R.id.encrypt_mode_container,
symmetric
? EncryptModeSymmetricFragment.newInstance()
: EncryptModeAsymmetricFragment.newInstance(0, null)
)
.commitAllowingStateLoss();
getSupportFragmentManager().executePendingTransactions();
}
@Override
public void onSignatureKeyIdChanged(long signatureKeyId) {
mEncryptFragment.setSigningKeyId(signatureKeyId);
}
@Override
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
}
@Override
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
}
@Override
public void onPassphraseChanged(Passphrase passphrase) {
mEncryptFragment.setPassphrase(passphrase);
protected void initLayout() {
setContentView(R.layout.encrypt_files_activity);
}
}

View File

@ -72,19 +72,12 @@ import java.util.Set;
public class EncryptFilesFragment extends CryptoOperationFragment {
public interface IMode {
public void onModeChanged(boolean symmetric);
}
public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";
public static final String ARG_URIS = "uris";
private static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
private IMode mModeInterface;
private boolean mSymmetricMode = false;
private boolean mUseArmor = false;
private boolean mUseCompression = true;
private boolean mDeleteAfterEncrypt = false;
@ -92,11 +85,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
private boolean mEncryptFilenames = true;
private boolean mHiddenRecipients = false;
private long mEncryptionKeyIds[] = null;
private String mEncryptionUserIds[] = null;
private long mSigningKeyId = Constants.key.none;
private Passphrase mPassphrase = new Passphrase();
private ArrayList<Uri> mOutputUris = new ArrayList<>();
private RecyclerView mSelectedFiles;
@ -118,29 +106,11 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
return frag;
}
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
mEncryptionKeyIds = encryptionKeyIds;
}
public void setEncryptionUserIds(String[] encryptionUserIds) {
mEncryptionUserIds = encryptionUserIds;
}
public void setSigningKeyId(long signingKeyId) {
mSigningKeyId = signingKeyId;
}
public void setPassphrase(Passphrase passphrase) {
mPassphrase = passphrase;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mModeInterface = (IMode) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity + " must be IMode");
if ( ! (activity instanceof EncryptActivity) ) {
throw new AssertionError(activity + " must inherit from EncryptionActivity");
}
}
@ -293,8 +263,8 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
break;
}
case R.id.check_use_symmetric: {
mSymmetricMode = item.isChecked();
mModeInterface.onModeChanged(mSymmetricMode);
EncryptActivity encryptActivity = (EncryptActivity) getActivity();
encryptActivity.toggleModeFragment();
break;
}
case R.id.check_use_armor: {
@ -325,53 +295,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
return true;
}
protected boolean inputIsValid() {
// file checks
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
.show(this);
return false;
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
// This should be impossible...
return false;
} else if (mFilesModels.size() != mOutputUris.size()) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
// This as well
return false;
}
if (mSymmetricMode) {
// symmetric encryption checks
if (mPassphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show(this);
return false;
}
if (mPassphrase.isEmpty()) {
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show(this);
return false;
}
} else {
// asymmetric encryption checks
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
&& mEncryptionKeyIds.length > 0);
// Files must be encrypted, only text can be signed-only right now
if (!gotEncryptionKeys) {
Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
.show(this);
return false;
}
}
return true;
}
public void onEncryptSuccess(final SignEncryptResult result) {
if (mDeleteAfterEncrypt) {
DeleteFileDialogFragment deleteFileDialog =
@ -403,6 +326,21 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
protected SignEncryptParcel createEncryptBundle() {
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
.show(this);
return null;
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
// This should be impossible...
return null;
} else if (mFilesModels.size() != mOutputUris.size()) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
// This as well
return null;
}
// fill values for this action
SignEncryptParcel data = new SignEncryptParcel();
@ -419,17 +357,39 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
if (mSymmetricMode) {
Log.d(Constants.TAG, "Symmetric encryption enabled!");
Passphrase passphrase = mPassphrase;
EncryptActivity encryptActivity = (EncryptActivity) getActivity();
EncryptModeFragment modeFragment = encryptActivity.getModeFragment();
if (modeFragment.isAsymmetric()) {
long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds();
long signingKeyId = modeFragment.getAsymmetricSigningKeyId();
boolean gotEncryptionKeys = (encryptionKeyIds != null
&& encryptionKeyIds.length > 0);
if (!gotEncryptionKeys && signingKeyId == 0) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show(this);
return null;
}
data.setEncryptionMasterKeyIds(encryptionKeyIds);
data.setSignatureMasterKeyId(signingKeyId);
} else {
Passphrase passphrase = modeFragment.getSymmetricPassphrase();
if (passphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show(this);
return null;
}
if (passphrase.isEmpty()) {
passphrase = null;
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show(this);
return null;
}
data.setSymmetricPassphrase(passphrase);
} else {
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
data.setSignatureMasterKeyId(mSigningKeyId);
}
return data;
}
@ -461,16 +421,27 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
if (!mSymmetricMode && mEncryptionUserIds != null) {
Set<String> users = new HashSet<>();
for (String user : mEncryptionUserIds) {
KeyRing.UserId userId = KeyRing.splitUserId(user);
if (userId.email != null) {
users.add(userId.email);
}
}
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
EncryptActivity modeInterface = (EncryptActivity) getActivity();
EncryptModeFragment modeFragment = modeInterface.getModeFragment();
if (!modeFragment.isAsymmetric()) {
return sendIntent;
}
String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds();
if (encryptionUserIds == null) {
return sendIntent;
}
Set<String> users = new HashSet<>();
for (String user : encryptionUserIds) {
KeyRing.UserId userId = KeyRing.splitUserId(user);
if (userId.email != null) {
users.add(userId.email);
}
}
// pass trough email addresses as extra for email applications
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
return sendIntent;
}
@ -482,19 +453,17 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
if (!inputIsValid()) {
final SignEncryptParcel input = createEncryptBundle();
// this is null if invalid, just return in that case
if (input == null) {
// Notify was created by inputIsValid.
Log.d(Constants.TAG, "Input not valid!");
return;
}
Log.d(Constants.TAG, "Input valid!");
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
final SignEncryptParcel input = createEncryptBundle();
Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
@ -727,7 +696,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
for (Uri inputUri : inputUris) {
ViewModel newModel = new ViewModel(mActivity, inputUri);
if (mDataset.contains(newModel)) {
Log.e(Constants.TAG, "Skipped duplicate " + inputUri.toString());
Log.e(Constants.TAG, "Skipped duplicate " + inputUri);
} else {
mDataset.add(newModel);
}

View File

@ -17,18 +17,13 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.tokenautocomplete.TokenCompleteTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
@ -39,38 +34,19 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class EncryptModeAsymmetricFragment extends Fragment {
public interface IAsymmetric {
public void onSignatureKeyIdChanged(long signatureKeyId);
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds);
public void onEncryptionUserIdsChanged(String[] encryptionUserIds);
}
public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
ProviderHelper mProviderHelper;
// view
private KeySpinner mSign;
private KeySpinner mSignKeySpinner;
private EncryptKeyCompletionView mEncryptKeyView;
// model
private IAsymmetric mEncryptInterface;
// @Override
// public void updateUi() {
// if (mSign != null) {
// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
// }
// }
public static final String ARG_SINGATURE_KEY_ID = "signature_key_id";
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
@ -89,16 +65,6 @@ public class EncryptModeAsymmetricFragment extends Fragment {
return frag;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mEncryptInterface = (IAsymmetric) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity + " must implement IAsymmetric");
}
}
/**
* Inflate the layout for this fragment
*/
@ -106,13 +72,7 @@ public class EncryptModeAsymmetricFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
mSign = (KeySpinner) view.findViewById(R.id.sign);
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
}
});
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
mEncryptKeyView.setThreshold(1); // Start working from first character
@ -128,22 +88,6 @@ public class EncryptModeAsymmetricFragment extends Fragment {
long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
preselectKeys(signatureKeyId, encryptionKeyIds);
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
@Override
public void onTokenAdded(Object token) {
if (token instanceof KeyItem) {
updateEncryptionKeys();
}
}
@Override
public void onTokenRemoved(Object token) {
if (token instanceof KeyItem) {
updateEncryptionKeys();
}
}
});
}
/**
@ -155,8 +99,7 @@ public class EncryptModeAsymmetricFragment extends Fragment {
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
if (keyring.hasAnySecret()) {
mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
mSign.setSelectedKeyId(signatureKeyId);
mSignKeySpinner.setSelectedKeyId(signatureKeyId);
}
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
@ -175,27 +118,55 @@ public class EncryptModeAsymmetricFragment extends Fragment {
}
// This is to work-around a rendering bug in TokenCompleteTextView
mEncryptKeyView.requestFocus();
updateEncryptionKeys();
}
}
private void updateEncryptionKeys() {
List<Object> objects = mEncryptKeyView.getObjects();
@Override
public boolean isAsymmetric() {
return true;
}
@Override
public long getAsymmetricSigningKeyId() {
return mSignKeySpinner.getSelectedItemId();
}
@Override
public long[] getAsymmetricEncryptionKeyIds() {
List<Long> keyIds = new ArrayList<>();
List<String> userIds = new ArrayList<>();
for (Object object : objects) {
for (Object object : mEncryptKeyView.getObjects()) {
if (object instanceof KeyItem) {
keyIds.add(((KeyItem) object).mKeyId);
userIds.add(((KeyItem) object).mUserIdFull);
}
}
long[] keyIdsArr = new long[keyIds.size()];
Iterator<Long> iterator = keyIds.iterator();
for (int i = 0; i < keyIds.size(); i++) {
keyIdsArr[i] = iterator.next();
}
mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()]));
return keyIdsArr;
}
@Override
public String[] getAsymmetricEncryptionUserIds() {
List<String> userIds = new ArrayList<>();
for (Object object : mEncryptKeyView.getObjects()) {
if (object instanceof KeyItem) {
userIds.add(((KeyItem) object).mUserIdFull);
}
}
return userIds.toArray(new String[userIds.size()]);
}
@Override
public Passphrase getSymmetricPassphrase() {
throw new UnsupportedOperationException("should never happen, this is a programming error!");
}
}

View File

@ -0,0 +1,19 @@
package org.sufficientlysecure.keychain.ui;
import android.support.v4.app.Fragment;
import org.sufficientlysecure.keychain.util.Passphrase;
public abstract class EncryptModeFragment extends Fragment {
public abstract boolean isAsymmetric();
public abstract long getAsymmetricSigningKeyId();
public abstract long[] getAsymmetricEncryptionKeyIds();
public abstract String[] getAsymmetricEncryptionUserIds();
public abstract Passphrase getSymmetricPassphrase();
}

View File

@ -17,11 +17,7 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -30,14 +26,7 @@ import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Passphrase;
public class EncryptModeSymmetricFragment extends Fragment {
public interface ISymmetric {
public void onPassphraseChanged(Passphrase passphrase);
}
private ISymmetric mEncryptInterface;
public class EncryptModeSymmetricFragment extends EncryptModeFragment {
private EditText mPassphrase;
private EditText mPassphraseAgain;
@ -54,53 +43,54 @@ public class EncryptModeSymmetricFragment extends Fragment {
return frag;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mEncryptInterface = (ISymmetric) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement ISymmetric");
}
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false);
mPassphrase = (EditText) view.findViewById(R.id.passphrase);
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
// update passphrase in EncryptActivity
Passphrase p1 = new Passphrase(mPassphrase.getText());
Passphrase p2 = new Passphrase(mPassphraseAgain.getText());
boolean passesEquals = (p1.equals(p2));
p1.removeFromMemory();
p2.removeFromMemory();
if (passesEquals) {
mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
} else {
mEncryptInterface.onPassphraseChanged(null);
}
}
};
mPassphrase.addTextChangedListener(textWatcher);
mPassphraseAgain.addTextChangedListener(textWatcher);
return view;
}
@Override
public boolean isAsymmetric() {
return false;
}
@Override
public long getAsymmetricSigningKeyId() {
throw new UnsupportedOperationException("should never happen, this is a programming error!");
}
@Override
public long[] getAsymmetricEncryptionKeyIds() {
throw new UnsupportedOperationException("should never happen, this is a programming error!");
}
@Override
public String[] getAsymmetricEncryptionUserIds() {
throw new UnsupportedOperationException("should never happen, this is a programming error!");
}
@Override
public Passphrase getSymmetricPassphrase() {
Passphrase p1 = null, p2 = null;
try {
p1 = new Passphrase(mPassphrase.getText());
p2 = new Passphrase(mPassphraseAgain.getText());
if (!p1.equals(p2)) {
return null;
}
return new Passphrase(mPassphrase.getText());
} finally {
if (p1 != null) {
p1.removeFromMemory();
}
if (p2 != null) {
p2.removeFromMemory();
}
}
}
}

View File

@ -20,20 +20,14 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
public class EncryptTextActivity extends BaseActivity implements
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
EncryptTextFragment.IMode {
public class EncryptTextActivity extends EncryptActivity {
/* Intents */
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT;
@ -41,13 +35,6 @@ public class EncryptTextActivity extends BaseActivity implements
/* EXTRA keys for input */
public static final String EXTRA_TEXT = OpenKeychainIntents.ENCRYPT_EXTRA_TEXT;
// preselect ids, for internal use
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
Fragment mModeFragment;
EncryptTextFragment mEncryptFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -89,23 +76,14 @@ public class EncryptTextActivity extends BaseActivity implements
textData = "";
}
// preselect keys given by intent
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
mEncryptFragment = EncryptTextFragment.newInstance(textData);
transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text");
EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData);
transaction.replace(R.id.encrypt_text_container, encryptFragment);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
@Override
@ -113,44 +91,4 @@ public class EncryptTextActivity extends BaseActivity implements
setContentView(R.layout.encrypt_text_activity);
}
@Override
public void onModeChanged(boolean symmetric) {
// switch fragments
getSupportFragmentManager().beginTransaction()
.replace(R.id.encrypt_mode_container,
symmetric
? EncryptModeSymmetricFragment.newInstance()
: EncryptModeAsymmetricFragment.newInstance(0, null)
)
.commitAllowingStateLoss();
getSupportFragmentManager().executePendingTransactions();
}
@Override
public void onSignatureKeyIdChanged(long signatureKeyId) {
if (mEncryptFragment != null) {
mEncryptFragment.setSigningKeyId(signatureKeyId);
}
}
@Override
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
if (mEncryptFragment != null) {
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
}
}
@Override
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
if (mEncryptFragment != null) {
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
}
}
@Override
public void onPassphraseChanged(Passphrase passphrase) {
if (mEncryptFragment != null) {
mEncryptFragment.setSymmetricPassphrase(passphrase);
}
}
}

View File

@ -47,7 +47,6 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ShareHelper;
@ -56,44 +55,14 @@ import java.util.Set;
public class EncryptTextFragment extends CryptoOperationFragment {
public interface IMode {
public void onModeChanged(boolean symmetric);
}
public static final String ARG_TEXT = "text";
private IMode mModeInterface;
private boolean mSymmetricMode = false;
private boolean mShareAfterEncrypt = false;
private boolean mUseCompression = true;
private boolean mHiddenRecipients = false;
private long mEncryptionKeyIds[] = null;
private String mEncryptionUserIds[] = null;
// TODO Constants.key.none? What's wrong with a null value?
private long mSigningKeyId = Constants.key.none;
private Passphrase mSymmetricPassphrase = new Passphrase();
private String mMessage = "";
private TextView mText;
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
mEncryptionKeyIds = encryptionKeyIds;
}
public void setEncryptionUserIds(String[] encryptionUserIds) {
mEncryptionUserIds = encryptionUserIds;
}
public void setSigningKeyId(long signingKeyId) {
mSigningKeyId = signingKeyId;
}
public void setSymmetricPassphrase(Passphrase passphrase) {
mSymmetricPassphrase = passphrase;
}
/**
* Creates new instance of this fragment
*/
@ -110,10 +79,8 @@ public class EncryptTextFragment extends CryptoOperationFragment {
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mModeInterface = (IMode) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement IMode");
if ( ! (activity instanceof EncryptActivity) ) {
throw new AssertionError(activity + " must inherit from EncryptionActivity");
}
}
@ -124,8 +91,8 @@ public class EncryptTextFragment extends CryptoOperationFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_text_fragment, container, false);
mText = (TextView) view.findViewById(R.id.encrypt_text_text);
mText.addTextChangedListener(new TextWatcher() {
TextView textView = (TextView) view.findViewById(R.id.encrypt_text_text);
textView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@ -144,7 +111,7 @@ public class EncryptTextFragment extends CryptoOperationFragment {
// set initial text
if (mMessage != null) {
mText.setText(mMessage);
textView.setText(mMessage);
}
return view;
@ -171,8 +138,8 @@ public class EncryptTextFragment extends CryptoOperationFragment {
}
switch (item.getItemId()) {
case R.id.check_use_symmetric: {
mSymmetricMode = item.isChecked();
mModeInterface.onModeChanged(mSymmetricMode);
EncryptActivity modeInterface = (EncryptActivity) getActivity();
modeInterface.toggleModeFragment();
break;
}
case R.id.check_enable_compression: {
@ -185,11 +152,11 @@ public class EncryptTextFragment extends CryptoOperationFragment {
// break;
// }
case R.id.encrypt_copy: {
startEncrypt(false);
cryptoOperation(false);
break;
}
case R.id.encrypt_share: {
startEncrypt(true);
cryptoOperation(true);
break;
}
default: {
@ -199,7 +166,6 @@ public class EncryptTextFragment extends CryptoOperationFragment {
return true;
}
protected void onEncryptSuccess(SignEncryptResult result) {
if (mShareAfterEncrypt) {
// Share encrypted message/file
@ -215,6 +181,13 @@ public class EncryptTextFragment extends CryptoOperationFragment {
}
protected SignEncryptParcel createEncryptBundle() {
if (mMessage == null || mMessage.isEmpty()) {
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
.show(this);
return null;
}
// fill values for this action
SignEncryptParcel data = new SignEncryptParcel();
@ -227,22 +200,45 @@ public class EncryptTextFragment extends CryptoOperationFragment {
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
}
data.setHiddenRecipients(mHiddenRecipients);
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
data.setSymmetricEncryptionAlgorithm(
PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
data.setSignatureHashAlgorithm(
PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
// Always use armor for messages
data.setEnableAsciiArmorOutput(true);
if (mSymmetricMode) {
Log.d(Constants.TAG, "Symmetric encryption enabled!");
Passphrase passphrase = mSymmetricPassphrase;
EncryptActivity modeInterface = (EncryptActivity) getActivity();
EncryptModeFragment modeFragment = modeInterface.getModeFragment();
if (modeFragment.isAsymmetric()) {
long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds();
long signingKeyId = modeFragment.getAsymmetricSigningKeyId();
boolean gotEncryptionKeys = (encryptionKeyIds != null
&& encryptionKeyIds.length > 0);
if (!gotEncryptionKeys && signingKeyId == 0L) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show(this);
return null;
}
data.setEncryptionMasterKeyIds(encryptionKeyIds);
data.setSignatureMasterKeyId(signingKeyId);
} else {
Passphrase passphrase = modeFragment.getSymmetricPassphrase();
if (passphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show(this);
return null;
}
if (passphrase.isEmpty()) {
passphrase = null;
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show(this);
return null;
}
data.setSymmetricPassphrase(passphrase);
} else {
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
data.setSignatureMasterKeyId(mSigningKeyId);
}
return data;
}
@ -273,65 +269,41 @@ public class EncryptTextFragment extends CryptoOperationFragment {
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
if (!mSymmetricMode && mEncryptionUserIds != null) {
Set<String> users = new HashSet<>();
for (String user : mEncryptionUserIds) {
KeyRing.UserId userId = KeyRing.splitUserId(user);
if (userId.email != null) {
users.add(userId.email);
}
}
// pass trough email addresses as extra for email applications
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
EncryptActivity modeInterface = (EncryptActivity) getActivity();
EncryptModeFragment modeFragment = modeInterface.getModeFragment();
if (!modeFragment.isAsymmetric()) {
return sendIntent;
}
String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds();
if (encryptionUserIds == null) {
return sendIntent;
}
Set<String> users = new HashSet<>();
for (String user : encryptionUserIds) {
KeyRing.UserId userId = KeyRing.splitUserId(user);
if (userId.email != null) {
users.add(userId.email);
}
}
// pass trough email addresses as extra for email applications
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
return sendIntent;
}
protected boolean inputIsValid() {
if (mMessage == null || mMessage.isEmpty()) {
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
.show(this);
return false;
}
if (mSymmetricMode) {
// symmetric encryption checks
if (mSymmetricPassphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show(this);
return false;
}
if (mSymmetricPassphrase.isEmpty()) {
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show(this);
return false;
}
} else {
// asymmetric encryption checks
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
&& mEncryptionKeyIds.length > 0);
if (!gotEncryptionKeys && mSigningKeyId == 0) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show(this);
return false;
}
}
return true;
}
public void startEncrypt(boolean share) {
public void cryptoOperation(boolean share) {
mShareAfterEncrypt = share;
cryptoOperation();
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
if (!inputIsValid()) {
final SignEncryptParcel input = createEncryptBundle();
// this is null if invalid, just return in that case
if (input == null) {
// Notify was created by inputIsValid.
return;
}
@ -340,7 +312,6 @@ public class EncryptTextFragment extends CryptoOperationFragment {
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
final SignEncryptParcel input = createEncryptBundle();
final Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);

View File

@ -20,12 +20,14 @@ package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.AppCompatSpinner;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.View;
@ -42,17 +44,18 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090
*/
public abstract class KeySpinner extends AppCompatSpinner implements LoaderManager.LoaderCallbacks<Cursor> {
public abstract class KeySpinner extends AppCompatSpinner implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_SUPER_STATE = "super_state";
public static final String ARG_SELECTED_KEY_ID = "select_key_id";
public interface OnKeyChangedListener {
public void onKeyChanged(long masterKeyId);
void onKeyChanged(long masterKeyId);
}
protected long mSelectedKeyId = Constants.key.none;
@ -82,15 +85,17 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
super.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mSelectedKeyId = id;
if (mListener != null) {
mListener.onKeyChanged(id);
mListener.onKeyChanged(mSelectedKeyId);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
mSelectedKeyId = Constants.key.none;
if (mListener != null) {
mListener.onKeyChanged(Constants.key.none);
mListener.onKeyChanged(mSelectedKeyId);
}
}
});
@ -138,7 +143,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
}
public void setSelectedKeyId(long selectedKeyId) {
this.mSelectedKeyId = selectedKeyId;
mSelectedKeyId = selectedKeyId;
}
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {