Merge branch 'master' into improve-file-more

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
	OpenKeychain/src/main/res/layout/encrypt_content.xml
This commit is contained in:
mar-v-in
2014-07-06 02:24:34 +02:00
74 changed files with 11697 additions and 1929 deletions

View File

@@ -38,6 +38,7 @@ import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
@@ -50,6 +51,9 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Primes;
import java.io.IOException;
@@ -63,6 +67,7 @@ import java.security.SignatureException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
/**
@@ -99,18 +104,13 @@ public class PgpKeyOperation {
}
/** Creates new secret key. */
private PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws PgpGeneralMsgIdException {
private PGPKeyPair createKey(int algorithmChoice, int keySize) throws PgpGeneralMsgIdException {
try {
if (keySize < 512) {
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
}
if (passphrase == null) {
passphrase = "";
}
int algorithm;
KeyPairGenerator keyGen;
@@ -123,9 +123,6 @@ public class PgpKeyOperation {
}
case Constants.choice.algorithm.elgamal: {
if (isMasterKey) {
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
}
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize);
BigInteger g = new BigInteger("2");
@@ -151,19 +148,8 @@ public class PgpKeyOperation {
}
// build new key pair
PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
@@ -175,6 +161,55 @@ public class PgpKeyOperation {
}
}
public UncachedKeyRing createSecretKeyRing(SaveKeyringParcel saveParcel, OperationLog log,
int indent) {
try {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
indent += 1;
updateProgress(R.string.progress_building_key, 0, 100);
if (saveParcel.addSubKeys == null || saveParcel.addSubKeys.isEmpty()) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);
return null;
}
SubkeyAdd add = saveParcel.addSubKeys.remove(0);
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
}
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA1);
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, true, keyEncryptor);
PGPSecretKeyRing sKR = new PGPSecretKeyRing(
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
return internal(sKR, masterSecretKey, saveParcel, "", log, indent);
} catch (PGPException e) {
Log.e(Constants.TAG, "pgp error encoding key", e);
return null;
} catch (IOException e) {
Log.e(Constants.TAG, "io error encoding key", e);
return null;
} catch (PgpGeneralMsgIdException e) {
Log.e(Constants.TAG, "pgp msg id error", e);
return null;
}
}
/** This method introduces a list of modifications specified by a SaveKeyringParcel to a
* WrappedSecretKeyRing.
*
@@ -204,28 +239,49 @@ public class PgpKeyOperation {
indent += 1;
updateProgress(R.string.progress_building_key, 0, 100);
// Make sure this is called with a proper SaveKeyringParcel
if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
return null;
}
// We work on bouncycastle object level here
PGPSecretKeyRing sKR = wsKR.getRing();
PGPPublicKey masterPublicKey = sKR.getPublicKey();
PGPSecretKey masterSecretKey = sKR.getSecretKey();
// Make sure the fingerprint matches
if (saveParcel.mFingerprint == null
|| !Arrays.equals(saveParcel.mFingerprint,
masterSecretKey.getPublicKey().getFingerprint())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_FINGERPRINT, indent);
return null;
}
return internal(sKR, masterSecretKey, saveParcel, passphrase, log, indent);
}
private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
SaveKeyringParcel saveParcel, String passphrase,
OperationLog log, int indent) {
updateProgress(R.string.progress_certifying_master_key, 20, 100);
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
// 1. Unlock private key
log.add(LogLevel.DEBUG, LogType.MSG_MF_UNLOCK, indent);
PGPPrivateKey masterPrivateKey; {
PGPPrivateKey masterPrivateKey;
{
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent+1);
log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
return null;
}
}
if (!Arrays.equals(saveParcel.mFingerprint, sKR.getPublicKey().getFingerprint())) {
return null;
}
updateProgress(R.string.progress_certifying_master_key, 20, 100);
// work on master secret key
try {
@@ -235,14 +291,42 @@ public class PgpKeyOperation {
// 2a. Add certificates for new user ids
for (String userId : saveParcel.addUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
// this operation supersedes all previous binding and revocation certificates,
// so remove those to retain assertions from canonicalization for later operations
@SuppressWarnings("unchecked")
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
if (it != null) {
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
// if it's not a self cert, never mind
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
continue;
}
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
|| cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION
|| cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION
|| cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, cert);
}
}
}
// if it's supposed to be primary, we can do that here as well
boolean isPrimary = saveParcel.changePrimaryUserId != null
&& userId.equals(saveParcel.changePrimaryUserId);
// generate and add new certificate
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, false);
masterPublicKey, userId, isPrimary);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
// 2b. Add revocations for revoked user ids
for (String userId : saveParcel.revokeUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
// a duplicate revocatin will be removed during canonicalization, so no need to
// take care of that here.
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
@@ -251,7 +335,84 @@ public class PgpKeyOperation {
// 3. If primary user id changed, generate new certificates for both old and new
if (saveParcel.changePrimaryUserId != null) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
// todo
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
// noinspection unchecked
for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
boolean isRevoked = false;
PGPSignature currentCert = null;
// noinspection unchecked
for (PGPSignature cert : new IterableIterator<PGPSignature>(
masterPublicKey.getSignaturesForID(userId))) {
// if it's not a self cert, never mind
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
continue;
}
// we know from canonicalization that if there is any revocation here, it
// is valid and not superseded by a newer certification.
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
isRevoked = true;
continue;
}
// we know from canonicalization that there is only one binding
// certification here, so we can just work with the first one.
if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION ||
cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
currentCert = cert;
}
}
if (currentCert == null) {
// no certificate found?! error error error
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
return null;
}
// we definitely should not update certifications of revoked keys, so just leave it.
if (isRevoked) {
// revoked user ids cannot be primary!
if (userId.equals(saveParcel.changePrimaryUserId)) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
return null;
}
continue;
}
// if this is~ the/a primary user id
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
// if it's the one we want, just leave it as is
if (userId.equals(saveParcel.changePrimaryUserId)) {
continue;
}
// otherwise, generate new non-primary certification
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, false);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
continue;
}
// if we are here, this is not currently a primary user id
// if it should be
if (userId.equals(saveParcel.changePrimaryUserId)) {
// add shiny new primary user id certificate
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, true);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
}
// user id is not primary and is not supposed to be - nothing to do here.
}
}
// Update the secret key ring
@@ -261,8 +422,7 @@ public class PgpKeyOperation {
sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
}
// 4a. For each subkey change, generate new subkey binding certificate
// 4a. For each subkey change, generate new subkey binding certificate
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
@@ -280,7 +440,8 @@ public class PgpKeyOperation {
return null;
}
// generate and add new signature
// generate and add new signature. we can be sloppy here and just leave the old one,
// it will be removed during canonicalization
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
@@ -316,16 +477,36 @@ public class PgpKeyOperation {
}
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
PGPSecretKey sKey = createKey(add.mAlgorithm, add.mKeysize, passphrase, false);
// generate a new secret key (privkey only for now)
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
// add subkey binding signature (making this a sub rather than master key)
PGPPublicKey pKey = keyPair.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature(
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
add.mFlags, add.mExpiry);
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
PGPSecretKey sKey; {
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA1);
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
sha1Calc, false, keyEncryptor);
}
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
PGPPublicKey pKey = sKey.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, add.mFlags, add.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, cert);
sKey = PGPSecretKey.replacePublicKey(sKey, pKey);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
} catch (PgpGeneralMsgIdException e) {
return null;
}
@@ -420,6 +601,18 @@ public class PgpKeyOperation {
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
throws IOException, PGPException, SignatureException {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray());
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
return generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, subPrivateKey,
pKey, flags, expiry);
}
private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry)
throws IOException, PGPException, SignatureException {
// date for signing
Date todayDate = new Date();
@@ -427,12 +620,6 @@ public class PgpKeyOperation {
// If this key can sign, we need a primary key binding signature
if ((flags & KeyFlags.SIGN_DATA) != 0) {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray());
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
// cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate);

View File

@@ -8,16 +8,13 @@ import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.security.NoSuchProviderException;
import java.util.Iterator;
public class WrappedSecretKeyRing extends WrappedKeyRing {
@@ -91,29 +88,6 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
}
}
public UncachedKeyRing changeSecretKeyPassphrase(String oldPassphrase,
String newPassphrase)
throws IOException, PGPException, NoSuchProviderException {
if (oldPassphrase == null) {
oldPassphrase = "";
}
if (newPassphrase == null) {
newPassphrase = "";
}
PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
mRing,
new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()),
new JcePBESecretKeyEncryptorBuilder(mRing.getSecretKey()
.getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray()));
return new UncachedKeyRing(newKeyRing);
}
public IterableIterator<WrappedSecretKey> secretKeyIterator() {
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
return new IterableIterator<WrappedSecretKey>(new Iterator<WrappedSecretKey>() {

View File

@@ -405,7 +405,11 @@ public class ProviderHelper {
// classify and order user ids. primary are moved to the front, revoked to the back,
// otherwise the order in the keyfile is preserved.
log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size());
if (trustedKeys.size() == 0) {
log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING_ZERO);
} else {
log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size());
}
mIndent += 1;
List<UserIdItem> uids = new ArrayList<UserIdItem>();
for (String userId : new IterableIterator<String>(

View File

@@ -35,7 +35,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.ui.EditKeyActivity;
import org.sufficientlysecure.keychain.ui.EditKeyActivityOld;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
import org.sufficientlysecure.keychain.util.AlgorithmNames;
@@ -163,11 +163,11 @@ public class AccountSettingsFragment extends Fragment implements
}
private void createKey() {
Intent intent = new Intent(getActivity(), EditKeyActivity.class);
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
// set default user id to account name
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName());
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, mAccSettings.getAccountName());
startActivityForResult(intent, REQUEST_CODE_CREATE_KEY);
}

View File

@@ -215,6 +215,10 @@ public class KeychainIntentService extends IntentService
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
Bundle data = extras.getBundle(EXTRA_DATA);
if (data == null) {
Log.e(Constants.TAG, "data extra is null!");
return;
}
OtherHelper.logDebugBundle(data, "EXTRA_DATA");
@@ -331,33 +335,41 @@ public class KeychainIntentService extends IntentService
try {
/* Input */
SaveKeyringParcel saveParcel = data.getParcelable(SAVE_KEYRING_PARCEL);
long masterKeyId = saveParcel.mMasterKeyId;
if (saveParcel == null) {
Log.e(Constants.TAG, "bug: missing save_keyring_parcel in data!");
return;
}
/* Operation */
ProviderHelper providerHelper = new ProviderHelper(this);
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 50, 100));
try {
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
WrappedSecretKeyRing secRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
OperationLog log = new OperationLog();
UncachedKeyRing ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
passphrase, log, 0);
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
UncachedKeyRing ring;
if (saveParcel.mMasterKeyId != null) {
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
WrappedSecretKeyRing secRing =
providerHelper.getWrappedSecretKeyRing(saveParcel.mMasterKeyId);
ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
passphrase, log, 0);
} else {
ring = keyOperations.createSecretKeyRing(saveParcel, log, 0);
}
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
// cache new passphrase
if (saveParcel.newPassphrase != null) {
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
saveParcel.newPassphrase);
}
} catch (ProviderHelper.NotFoundException e) {
// UncachedKeyRing ring = keyOperations.(saveParcel); //new Keyring
// save the pair
setProgress(R.string.progress_saving_key_ring, 95, 100);
// providerHelper.saveSecretKeyRing(ring);
sendErrorToHandler(e);
}
setProgress(R.string.progress_done, 100, 100);
if (saveParcel.newPassphrase != null) {
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, saveParcel.newPassphrase);
}
/* Output */
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
} catch (Exception e) {
@@ -458,7 +470,7 @@ public class KeychainIntentService extends IntentService
outStream);
if (mIsCanceled && outputFile != null) {
boolean isDeleted = new File(outputFile).delete();
new File(outputFile).delete();
}
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
@@ -614,6 +626,7 @@ public class KeychainIntentService extends IntentService
return;
}
Message msg = Message.obtain();
assert msg != null;
msg.arg1 = arg1;
if (arg2 != null) {
msg.arg2 = arg2;

View File

@@ -27,8 +27,10 @@ import android.support.v4.app.FragmentManager;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
public class KeychainIntentServiceHandler extends Handler {
@@ -126,6 +128,7 @@ public class KeychainIntentServiceHandler extends Handler {
break;
default:
Log.e(Constants.TAG, "unknown handler message!");
break;
}
}

View File

@@ -165,6 +165,7 @@ public class OperationResultParcel implements Parcelable {
MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error),
MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good),
MSG_IP_UID_CERTS_UNKNOWN (R.plurals.msg_ip_uid_certs_unknown),
MSG_IP_UID_CLASSIFYING_ZERO (R.string.msg_ip_uid_classifying_zero),
MSG_IP_UID_CLASSIFYING (R.plurals.msg_ip_uid_classifying),
MSG_IP_UID_REORDER(R.string.msg_ip_uid_reorder),
MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing),
@@ -233,9 +234,16 @@ public class OperationResultParcel implements Parcelable {
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
// secret key create
MSG_CR_ERROR_NO_MASTER (R.string.msg_mr),
// secret key modify
MSG_MF (R.string.msg_mr),
MSG_MF_ERROR_ENCODE (R.string.msg_mf_error_encode),
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),

View File

@@ -49,7 +49,6 @@ import java.util.Date;
* convenience.
*/
public class PassphraseCacheService extends Service {
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
public static final String ACTION_PASSPHRASE_CACHE_ADD = Constants.INTENT_PREFIX
+ "PASSPHRASE_CACHE_ADD";
@@ -83,7 +82,7 @@ public class PassphraseCacheService extends Service {
* @param passphrase
*/
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
Log.d(TAG, "cacheNewPassphrase() for " + keyId);
Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + keyId);
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
@@ -103,7 +102,7 @@ public class PassphraseCacheService extends Service {
* @return passphrase or null (if no passphrase is cached for this keyId)
*/
public static String getCachedPassphrase(Context context, long keyId) {
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphrase() get masterKeyId for " + keyId);
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
@@ -159,7 +158,7 @@ public class PassphraseCacheService extends Service {
private String getCachedPassphraseImpl(long keyId) {
// passphrase for symmetric encryption?
if (keyId == Constants.key.symmetric) {
Log.d(TAG, "getCachedPassphraseImpl() for symmetric encryption");
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption");
String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric);
if (cachedPassphrase == null) {
return null;
@@ -170,7 +169,7 @@ public class PassphraseCacheService extends Service {
// try to get master key id which is used as an identifier for cached passphrases
try {
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + keyId);
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + keyId);
WrappedSecretKeyRing key = new ProviderHelper(this).getWrappedSecretKeyRing(
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId));
// no passphrase needed? just add empty string and return it, then
@@ -184,18 +183,18 @@ public class PassphraseCacheService extends Service {
// get cached passphrase
String cachedPassphrase = mPassphraseCache.get(keyId);
if (cachedPassphrase == null) {
Log.d(TAG, "Passphrase not (yet) cached, returning null");
Log.d(Constants.TAG, "PassphraseCacheService Passphrase not (yet) cached, returning null");
// not really an error, just means the passphrase is not cached but not empty either
return null;
}
// set it again to reset the cache life cycle
Log.d(TAG, "Cache passphrase again when getting it!");
Log.d(Constants.TAG, "PassphraseCacheService Cache passphrase again when getting it!");
addCachedPassphrase(this, keyId, cachedPassphrase);
return cachedPassphrase;
} catch (ProviderHelper.NotFoundException e) {
Log.e(TAG, "Passphrase for unknown key was requested!");
Log.e(Constants.TAG, "PassphraseCacheService Passphrase for unknown key was requested!");
return null;
}
}
@@ -212,7 +211,7 @@ public class PassphraseCacheService extends Service {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG, "Received broadcast...");
Log.d(Constants.TAG, "PassphraseCacheService Received broadcast...");
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
@@ -248,7 +247,7 @@ public class PassphraseCacheService extends Service {
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand()");
Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");
// register broadcastreceiver
registerReceiver();
@@ -259,8 +258,8 @@ public class PassphraseCacheService extends Service {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
Log.d(TAG,
"Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
Log.d(Constants.TAG,
"PassphraseCacheService Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
+ keyId + ", ttl: " + ttl);
// add keyId and passphrase to memory
@@ -285,10 +284,10 @@ public class PassphraseCacheService extends Service {
try {
messenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "Sending message failed", e);
Log.e(Constants.TAG, "PassphraseCacheService Sending message failed", e);
}
} else {
Log.e(Constants.TAG, "Intent or Intent Action not supported!");
Log.e(Constants.TAG, "PassphraseCacheService Intent or Intent Action not supported!");
}
}
@@ -305,11 +304,11 @@ public class PassphraseCacheService extends Service {
// remove passphrase corresponding to keyId from memory
mPassphraseCache.remove(keyId);
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");
// stop whole service if no cached passphrases remaining
if (mPassphraseCache.size() == 0) {
Log.d(TAG, "No passphrases remaining in memory, stopping service!");
Log.d(Constants.TAG, "PassphraseCacheServic No passphrases remaining in memory, stopping service!");
stopSelf();
}
}

View File

@@ -22,10 +22,10 @@ import java.util.ArrayList;
*/
public class SaveKeyringParcel implements Parcelable {
// the master key id to be edited
public final long mMasterKeyId;
// the key fingerprint, for safety
public final byte[] mFingerprint;
// the master key id to be edited. if this is null, a new one will be created
public Long mMasterKeyId;
// the key fingerprint, for safety. MUST be null for a new key.
public byte[] mFingerprint;
public String newPassphrase;
@@ -38,9 +38,7 @@ public class SaveKeyringParcel implements Parcelable {
public ArrayList<String> revokeUserIds;
public ArrayList<Long> revokeSubKeys;
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
mMasterKeyId = masterKeyId;
mFingerprint = fingerprint;
public SaveKeyringParcel() {
addUserIds = new ArrayList<String>();
addSubKeys = new ArrayList<SubkeyAdd>();
changeSubKeys = new ArrayList<SubkeyChange>();
@@ -48,13 +46,19 @@ public class SaveKeyringParcel implements Parcelable {
revokeSubKeys = new ArrayList<Long>();
}
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
this();
mMasterKeyId = masterKeyId;
mFingerprint = fingerprint;
}
// performance gain for using Parcelable here would probably be negligible,
// use Serializable instead.
public static class SubkeyAdd implements Serializable {
public final int mAlgorithm;
public final int mKeysize;
public final int mFlags;
public final Long mExpiry;
public int mAlgorithm;
public int mKeysize;
public int mFlags;
public Long mExpiry;
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
mAlgorithm = algorithm;
mKeysize = keysize;
@@ -64,9 +68,9 @@ public class SaveKeyringParcel implements Parcelable {
}
public static class SubkeyChange implements Serializable {
public final long mKeyId;
public final Integer mFlags;
public final Long mExpiry;
public long mKeyId;
public Integer mFlags;
public Long mExpiry;
public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId;
mFlags = flags;
@@ -75,9 +79,11 @@ public class SaveKeyringParcel implements Parcelable {
}
public SaveKeyringParcel(Parcel source) {
mMasterKeyId = source.readLong();
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
mFingerprint = source.createByteArray();
newPassphrase = source.readString();
addUserIds = source.createStringArrayList();
addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
@@ -90,9 +96,14 @@ public class SaveKeyringParcel implements Parcelable {
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeLong(mMasterKeyId);
destination.writeInt(mMasterKeyId == null ? 0 : 1);
if(mMasterKeyId != null) {
destination.writeLong(mMasterKeyId);
}
destination.writeByteArray(mFingerprint);
destination.writeString(newPassphrase);
destination.writeStringList(addUserIds);
destination.writeSerializable(addSubKeys);

View File

@@ -155,7 +155,7 @@ public class DecryptActivity extends DrawerActivity {
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri);
mSwitchToTab = PAGER_TAB_FILE;
} else {
} else if (ACTION_DECRYPT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");
}

View File

@@ -1,6 +1,5 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
@@ -18,732 +17,52 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
import org.sufficientlysecure.keychain.ui.widget.SectionView;
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;
public class EditKeyActivity extends ActionBarActivity {
public class EditKeyActivity extends ActionBarActivity implements EditorListener {
// Actions for internal use only:
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
// possible extra keys
public static final String EXTRA_USER_IDS = "user_ids";
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
// EDIT
private Uri mDataUri;
private SectionView mUserIdsView;
private SectionView mKeysView;
private String mCurrentPassphrase = null;
private String mNewPassphrase = null;
private String mSavedNewPassphrase = null;
private boolean mIsPassphraseSet;
private boolean mNeedsSaving;
private boolean mIsBrandNewKeyring = false;
private Button mChangePassphrase;
private CheckBox mNoPassphrase;
Vector<String> mUserIds;
Vector<UncachedSecretKey> mKeys;
Vector<Integer> mKeysUsages;
boolean mMasterCanSign = true;
ExportHelper mExportHelper;
public boolean needsSaving() {
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
mNeedsSaving |= hasPassphraseChanged();
mNeedsSaving |= mIsBrandNewKeyring;
return mNeedsSaving;
}
public void somethingChanged() {
ActivityCompat.invalidateOptionsMenu(this);
}
public void onDeleted(Editor e, boolean wasNewItem) {
somethingChanged();
}
public void onEdited() {
somethingChanged();
}
private EditKeyFragment mEditKeyFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this);
setContentView(R.layout.edit_key_activity_new);
// Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Save
saveClicked();
}
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Cancel
cancelClicked();
}
}
);
mUserIds = new Vector<String>();
mKeys = new Vector<UncachedSecretKey>();
mKeysUsages = new Vector<Integer>();
// Catch Intents opened from other apps
Intent intent = getIntent();
String action = intent.getAction();
if (ACTION_CREATE_KEY.equals(action)) {
handleActionCreateKey(intent);
} else if (ACTION_EDIT_KEY.equals(action)) {
handleActionEditKey(intent);
}
}
/**
* Handle intent action to create new key
*
* @param intent
*/
private void handleActionCreateKey(Intent intent) {
Bundle extras = intent.getExtras();
mCurrentPassphrase = "";
mIsBrandNewKeyring = true;
if (extras != null) {
// if userId is given, prefill the fields
if (extras.containsKey(EXTRA_USER_IDS)) {
Log.d(Constants.TAG, "UserIds are given!");
mUserIds.add(extras.getString(EXTRA_USER_IDS));
}
// if no passphrase is given
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
if (noPassphrase) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassphrase.setVisibility(View.GONE);
}
}
// generate key
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
/*
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
if (generateDefaultKeys) {
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
mCurrentPassphrase);
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after generating is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_HORIZONTAL, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// Stop key generation on cancel
stopService(serviceIntent);
EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
EditKeyActivity.this.finish();
}
}) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service
Bundle data = message.getData();
ArrayList<UncachedSecretKey> newKeys =
PgpConversionHelper.BytesToPGPSecretKeyList(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
KeychainIntentService.RESULT_KEY_USAGES);
if (newKeys.size() == keyUsageFlags.size()) {
for (int i = 0; i < newKeys.size(); ++i) {
mKeys.add(newKeys.get(i));
mKeysUsages.add(keyUsageFlags.get(i));
}
}
buildLayout(true);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(this);
// start service with intent
startService(serviceIntent);
}
*/
}
} else {
buildLayout(false);
}
}
/**
* Handle intent action to edit existing key
*
* @param intent
*/
private void handleActionEditKey(Intent intent) {
mDataUri = intent.getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
Uri dataUri = getIntent().getData();
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
finish();
} else {
Log.d(Constants.TAG, "uri: " + mDataUri);
try {
Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri);
mMasterCanSign = keyRing.getSubKey().canCertify();
for (WrappedSecretKey key : keyRing.secretKeyIterator()) {
// Turn into uncached instance
mKeys.add(key.getUncached());
mKeysUsages.add(key.getKeyUsage()); // get usage when view is created
}
boolean isSet = false;
for (String userId : keyRing.getSubKey().getUserIds()) {
Log.d(Constants.TAG, "Added userId " + userId);
if (!isSet) {
isSet = true;
String[] parts = KeyRing.splitUserId(userId);
if (parts[0] != null) {
setTitle(parts[0]);
}
}
mUserIds.add(userId);
}
buildLayout(false);
mCurrentPassphrase = "";
mIsPassphraseSet = keyRing.hasPassphrase();
if (!mIsPassphraseSet) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassphrase.setVisibility(View.GONE);
}
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e);
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show();
finish();
}
return;
}
loadFragment(savedInstanceState, dataUri);
}
/**
* Shows the dialog to set a new passphrase
*/
private void showSetPassphraseDialog() {
// Message is received after passphrase is cached
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
// set new returned passphrase!
mNewPassphrase = data
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
updatePassphraseButtonText();
somethingChanged();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
// set title based on isPassphraseSet()
int title;
if (isPassphraseSet()) {
title = R.string.title_change_passphrase;
} else {
title = R.string.title_set_passphrase;
private void loadFragment(Bundle savedInstanceState, Uri dataUri) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
messenger, title);
// Create an instance of the fragment
mEditKeyFragment = EditKeyFragment.newInstance(dataUri);
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
}
/**
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
* id and key.
*
* @param newKeys
*/
private void buildLayout(boolean newKeys) {
setContentView(R.layout.edit_key_activity);
// find views
mChangePassphrase = (Button) findViewById(R.id.edit_key_btn_change_passphrase);
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
if (mIsPassphraseSet) {
mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
}
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mUserIdsView.setType(SectionView.TYPE_USER_ID);
mUserIdsView.setCanBeEdited(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(SectionView.TYPE_KEY);
mKeysView.setCanBeEdited(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this);
container.addView(mKeysView);
updatePassphraseButtonText();
mChangePassphrase.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showSetPassphraseDialog();
}
});
// disable passphrase when no passphrase checkbox is checked!
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
// remove passphrase
mSavedNewPassphrase = mNewPassphrase;
mNewPassphrase = "";
mChangePassphrase.setVisibility(View.GONE);
} else {
mNewPassphrase = mSavedNewPassphrase;
mChangePassphrase.setVisibility(View.VISIBLE);
}
somethingChanged();
}
});
}
private long getMasterKeyId() {
if (mKeysView.getEditors().getChildCount() == 0) {
return 0;
}
return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyId();
}
public boolean isPassphraseSet() {
if (mNoPassphrase.isChecked()) {
return true;
} else if ((mIsPassphraseSet)
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
return true;
} else {
return false;
}
}
public boolean hasPassphraseChanged() {
if (mNoPassphrase != null) {
if (mNoPassphrase.isChecked()) {
return mIsPassphraseSet;
} else {
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
}
} else {
return false;
}
}
private void saveClicked() {
final long masterKeyId = getMasterKeyId();
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
try {
if (!isPassphraseSet()) {
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
}
String passphrase;
if (mIsPassphraseSet) {
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
} else {
passphrase = "";
}
if (passphrase == null) {
PassphraseDialogFragment.show(this, masterKeyId,
new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivity.this, masterKeyId);
checkEmptyIDsWanted();
}
}
});
} else {
mCurrentPassphrase = passphrase;
checkEmptyIDsWanted();
}
} catch (PgpGeneralException e) {
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
} else {
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
}
}
private void checkEmptyIDsWanted() {
try {
ArrayList<String> userIDs = getUserIds(mUserIdsView);
List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
int curID = 0;
for (String userID : userIDs) {
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivity.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(EditKeyActivity.this.getString(R.string.ask_empty_id_ok));
alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
finallySaveClicked();
}
}
);
alert.setNegativeButton(this.getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
}
);
alert.setCancelable(false);
alert.show();
return;
}
curID++;
}
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.STYLE_ALERT).show();
}
finallySaveClicked();
}
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
final boolean[] primitives = new boolean[booleanList.size()];
int index = 0;
for (Boolean object : booleanList) {
primitives[index++] = object;
}
return primitives;
}
private void finallySaveClicked() {
/*
try {
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
OldSaveKeyringParcel saveParams = new OldSaveKeyringParcel();
saveParams.userIds = getUserIds(mUserIdsView);
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
saveParams.deletedKeys = mKeysView.getDeletedKeys();
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
saveParams.keysUsages = getKeysUsages(mKeysView);
saveParams.newPassphrase = mNewPassphrase;
saveParams.oldPassphrase = mCurrentPassphrase;
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
saveParams.keys = getKeys(mKeysView);
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
// fill values for this action
Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after saving is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Intent data = new Intent();
// return uri pointing to new created key
Uri uri = KeyRings.buildGenericKeyRingUri(getMasterKeyId());
data.setData(uri);
setResult(RESULT_OK, data);
finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(this);
// start service with intent
startService(intent);
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
*/
}
private void cancelClicked() {
if (needsSaving()) { //ask if we want to save
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivity.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key));
alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
saveClicked();
}
});
alert.setNegativeButton(this.getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
setResult(RESULT_CANCELED);
finish();
}
});
alert.setCancelable(false);
alert.show();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* Returns user ids from the SectionView
*
* @param userIdsView
* @return
*/
private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
ArrayList<String> userIds = new ArrayList<String>();
ViewGroup userIdEditors = userIdsView.getEditors();
boolean gotMainUserId = false;
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId;
userId = editor.getValue();
if (editor.isMainUserId()) {
userIds.add(0, userId);
gotMainUserId = true;
} else {
userIds.add(userId);
}
}
if (userIds.size() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
}
if (!gotMainUserId) {
throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
}
return userIds;
}
/**
* Returns keys from the SectionView
*
* @param keysView
* @return
*/
private ArrayList<UncachedSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keys.add(editor.getValue());
}
return keys;
}
/**
* Returns usage selections of keys from the SectionView
*
* @param keysView
* @return
*/
private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
ArrayList<Integer> keysUsages = new ArrayList<Integer>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keysUsages.add(editor.getUsage());
}
return keysUsages;
}
private ArrayList<Calendar> getKeysExpiryDates(SectionView keysView) throws PgpGeneralException {
ArrayList<Calendar> keysExpiryDates = new ArrayList<Calendar>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keysExpiryDates.add(editor.getExpiryDate());
}
return keysExpiryDates;
}
private void updatePassphraseButtonText() {
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
: getString(R.string.btn_set_passphrase));
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.edit_key_fragment_container, mEditKeyFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class EditKeyActivityNew extends ActionBarActivity {
private EditKeyFragment mEditKeyFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_key_activity_new);
Uri dataUri = getIntent().getData();
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
finish();
return;
}
loadFragment(savedInstanceState, dataUri);
}
private void loadFragment(Bundle savedInstanceState, Uri dataUri) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mEditKeyFragment = EditKeyFragment.newInstance(dataUri);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.edit_key_fragment_container, mEditKeyFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -0,0 +1,744 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* 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.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
import org.sufficientlysecure.keychain.ui.widget.SectionView;
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;
public class EditKeyActivityOld extends ActionBarActivity implements EditorListener {
// Actions for internal use only:
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
// possible extra keys
public static final String EXTRA_USER_IDS = "user_ids";
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
// EDIT
private Uri mDataUri;
private SectionView mUserIdsView;
private SectionView mKeysView;
private String mCurrentPassphrase = null;
private String mNewPassphrase = null;
private String mSavedNewPassphrase = null;
private boolean mIsPassphraseSet;
private boolean mNeedsSaving;
private boolean mIsBrandNewKeyring = false;
private Button mChangePassphrase;
private CheckBox mNoPassphrase;
Vector<String> mUserIds;
Vector<UncachedSecretKey> mKeys;
Vector<Integer> mKeysUsages;
boolean mMasterCanSign = true;
ExportHelper mExportHelper;
public boolean needsSaving() {
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
mNeedsSaving |= hasPassphraseChanged();
mNeedsSaving |= mIsBrandNewKeyring;
return mNeedsSaving;
}
public void somethingChanged() {
ActivityCompat.invalidateOptionsMenu(this);
}
public void onDeleted(Editor e, boolean wasNewItem) {
somethingChanged();
}
public void onEdited() {
somethingChanged();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this);
// Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Save
saveClicked();
}
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Cancel
cancelClicked();
}
}
);
mUserIds = new Vector<String>();
mKeys = new Vector<UncachedSecretKey>();
mKeysUsages = new Vector<Integer>();
// Catch Intents opened from other apps
Intent intent = getIntent();
String action = intent.getAction();
if (ACTION_CREATE_KEY.equals(action)) {
handleActionCreateKey(intent);
} else if (ACTION_EDIT_KEY.equals(action)) {
handleActionEditKey(intent);
}
}
/**
* Handle intent action to create new key
*
* @param intent
*/
private void handleActionCreateKey(Intent intent) {
Bundle extras = intent.getExtras();
mCurrentPassphrase = "";
mIsBrandNewKeyring = true;
if (extras != null) {
// if userId is given, prefill the fields
if (extras.containsKey(EXTRA_USER_IDS)) {
Log.d(Constants.TAG, "UserIds are given!");
mUserIds.add(extras.getString(EXTRA_USER_IDS));
}
// if no passphrase is given
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
if (noPassphrase) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassphrase.setVisibility(View.GONE);
}
}
// generate key
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
/*
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
if (generateDefaultKeys) {
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
mCurrentPassphrase);
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after generating is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_HORIZONTAL, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// Stop key generation on cancel
stopService(serviceIntent);
EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
EditKeyActivity.this.finish();
}
}) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service
Bundle data = message.getDataAsStringList();
ArrayList<UncachedSecretKey> newKeys =
PgpConversionHelper.BytesToPGPSecretKeyList(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
KeychainIntentService.RESULT_KEY_USAGES);
if (newKeys.size() == keyUsageFlags.size()) {
for (int i = 0; i < newKeys.size(); ++i) {
mKeys.add(newKeys.get(i));
mKeysUsages.add(keyUsageFlags.get(i));
}
}
buildLayout(true);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(this);
// start service with intent
startService(serviceIntent);
}
*/
}
} else {
buildLayout(false);
}
}
/**
* Handle intent action to edit existing key
*
* @param intent
*/
private void handleActionEditKey(Intent intent) {
mDataUri = intent.getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
finish();
} else {
Log.d(Constants.TAG, "uri: " + mDataUri);
try {
Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri);
mMasterCanSign = keyRing.getSubKey().canCertify();
for (WrappedSecretKey key : keyRing.secretKeyIterator()) {
// Turn into uncached instance
mKeys.add(key.getUncached());
mKeysUsages.add(key.getKeyUsage()); // get usage when view is created
}
boolean isSet = false;
for (String userId : keyRing.getSubKey().getUserIds()) {
Log.d(Constants.TAG, "Added userId " + userId);
if (!isSet) {
isSet = true;
String[] parts = KeyRing.splitUserId(userId);
if (parts[0] != null) {
setTitle(parts[0]);
}
}
mUserIds.add(userId);
}
buildLayout(false);
mCurrentPassphrase = "";
mIsPassphraseSet = keyRing.hasPassphrase();
if (!mIsPassphraseSet) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassphrase.setVisibility(View.GONE);
}
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e);
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show();
finish();
}
}
}
/**
* Shows the dialog to set a new passphrase
*/
private void showSetPassphraseDialog() {
// Message is received after passphrase is cached
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
// set new returned passphrase!
mNewPassphrase = data
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
updatePassphraseButtonText();
somethingChanged();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
// set title based on isPassphraseSet()
int title;
if (isPassphraseSet()) {
title = R.string.title_change_passphrase;
} else {
title = R.string.title_set_passphrase;
}
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
messenger, null, title);
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
}
/**
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
* id and key.
*
* @param newKeys
*/
private void buildLayout(boolean newKeys) {
setContentView(R.layout.edit_key_activity);
// find views
mChangePassphrase = (Button) findViewById(R.id.edit_key_btn_change_passphrase);
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
if (mIsPassphraseSet) {
mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
}
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mUserIdsView.setType(SectionView.TYPE_USER_ID);
mUserIdsView.setCanBeEdited(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(SectionView.TYPE_KEY);
mKeysView.setCanBeEdited(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this);
container.addView(mKeysView);
updatePassphraseButtonText();
mChangePassphrase.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showSetPassphraseDialog();
}
});
// disable passphrase when no passphrase checkbox is checked!
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
// remove passphrase
mSavedNewPassphrase = mNewPassphrase;
mNewPassphrase = "";
mChangePassphrase.setVisibility(View.GONE);
} else {
mNewPassphrase = mSavedNewPassphrase;
mChangePassphrase.setVisibility(View.VISIBLE);
}
somethingChanged();
}
});
}
private long getMasterKeyId() {
if (mKeysView.getEditors().getChildCount() == 0) {
return 0;
}
return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyId();
}
public boolean isPassphraseSet() {
if (mNoPassphrase.isChecked()) {
return true;
} else if ((mIsPassphraseSet)
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
return true;
} else {
return false;
}
}
public boolean hasPassphraseChanged() {
if (mNoPassphrase != null) {
if (mNoPassphrase.isChecked()) {
return mIsPassphraseSet;
} else {
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
}
} else {
return false;
}
}
private void saveClicked() {
final long masterKeyId = getMasterKeyId();
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
try {
if (!isPassphraseSet()) {
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
}
String passphrase;
if (mIsPassphraseSet) {
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
} else {
passphrase = "";
}
if (passphrase == null) {
PassphraseDialogFragment.show(this, masterKeyId,
new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivityOld.this, masterKeyId);
checkEmptyIDsWanted();
}
}
});
} else {
mCurrentPassphrase = passphrase;
checkEmptyIDsWanted();
}
} catch (PgpGeneralException e) {
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
} else {
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
}
}
private void checkEmptyIDsWanted() {
try {
ArrayList<String> userIDs = getUserIds(mUserIdsView);
List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
int curID = 0;
for (String userID : userIDs) {
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivityOld.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_empty_id_ok));
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
finallySaveClicked();
}
}
);
alert.setNegativeButton(this.getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
}
);
alert.setCancelable(false);
alert.show();
return;
}
curID++;
}
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.STYLE_ALERT).show();
}
finallySaveClicked();
}
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
final boolean[] primitives = new boolean[booleanList.size()];
int index = 0;
for (Boolean object : booleanList) {
primitives[index++] = object;
}
return primitives;
}
private void finallySaveClicked() {
/*
try {
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
OldSaveKeyringParcel saveParams = new OldSaveKeyringParcel();
saveParams.userIds = getUserIds(mUserIdsView);
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
saveParams.deletedKeys = mKeysView.getDeletedKeys();
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
saveParams.keysUsages = getKeysUsages(mKeysView);
saveParams.newPassphrase = mNewPassphrase;
saveParams.oldPassphrase = mCurrentPassphrase;
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
saveParams.keys = getKeys(mKeysView);
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
// fill values for this action
Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after saving is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Intent data = new Intent();
// return uri pointing to new created key
Uri uri = KeyRings.buildGenericKeyRingUri(getMasterKeyId());
data.setData(uri);
setResult(RESULT_OK, data);
finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(this);
// start service with intent
startService(intent);
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
*/
}
private void cancelClicked() {
if (needsSaving()) { //ask if we want to save
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivityOld.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_save_changed_key));
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
saveClicked();
}
});
alert.setNegativeButton(this.getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
setResult(RESULT_CANCELED);
finish();
}
});
alert.setCancelable(false);
alert.show();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* Returns user ids from the SectionView
*
* @param userIdsView
* @return
*/
private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
ArrayList<String> userIds = new ArrayList<String>();
ViewGroup userIdEditors = userIdsView.getEditors();
boolean gotMainUserId = false;
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId;
userId = editor.getValue();
if (editor.isMainUserId()) {
userIds.add(0, userId);
gotMainUserId = true;
} else {
userIds.add(userId);
}
}
if (userIds.size() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
}
if (!gotMainUserId) {
throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
}
return userIds;
}
/**
* Returns keys from the SectionView
*
* @param keysView
* @return
*/
private ArrayList<UncachedSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keys.add(editor.getValue());
}
return keys;
}
/**
* Returns usage selections of keys from the SectionView
*
* @param keysView
* @return
*/
private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
ArrayList<Integer> keysUsages = new ArrayList<Integer>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keysUsages.add(editor.getUsage());
}
return keysUsages;
}
private ArrayList<Calendar> getKeysExpiryDates(SectionView keysView) throws PgpGeneralException {
ArrayList<Calendar> keysExpiryDates = new ArrayList<Calendar>();
ViewGroup keyEditors = keysView.getEditors();
if (keyEditors.getChildCount() == 0) {
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
}
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
keysExpiryDates.add(editor.getExpiryDate());
}
return keysExpiryDates;
}
private void updatePassphraseButtonText() {
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
: getString(R.string.btn_set_passphrase));
}
}

View File

@@ -37,6 +37,7 @@ import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
@@ -50,38 +51,48 @@ import org.sufficientlysecure.keychain.service.OperationResults;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsArrayAdapter;
import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
public class EditKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private ListView mUserIdsList;
private ListView mKeysList;
private ListView mSubkeysList;
private ListView mUserIdsAddedList;
private ListView mKeysAddedList;
private ListView mSubkeysAddedList;
private View mChangePassphrase;
private View mAddUserId;
private View mAddKey;
private View mAddSubkey;
private static final int LOADER_ID_USER_IDS = 0;
private static final int LOADER_ID_KEYS = 1;
private static final int LOADER_ID_SUBKEYS = 1;
// cursor adapter
private UserIdsAdapter mUserIdsAdapter;
private SubkeysAdapter mKeysAdapter;
private UserIdsArrayAdapter mUserIdsAddedAdapter;
private SubkeysAdapter mSubkeysAdapter;
// array adapter
private UserIdsAddedAdapter mUserIdsAddedAdapter;
private SubkeysAddedAdapter mSubkeysAddedAdapter;
private ArrayList<UserIdsAddedAdapter.UserIdModel> mUserIdsAddedData;
private Uri mDataUri;
private SaveKeyringParcel mSaveKeyringParcel;
private String mCurrentPassphrase;
/**
* Creates new instance of this fragment
*/
@@ -102,12 +113,12 @@ public class EditKeyFragment extends LoaderFragment implements
View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
mKeysList = (ListView) view.findViewById(R.id.edit_key_keys);
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_key_user_ids_added);
mKeysAddedList = (ListView) view.findViewById(R.id.edit_key_keys_added);
mSubkeysAddedList = (ListView) view.findViewById(R.id.edit_key_subkeys_added);
mChangePassphrase = view.findViewById(R.id.edit_key_action_change_passphrase);
mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id);
mAddKey = view.findViewById(R.id.edit_key_action_add_key);
mAddSubkey = view.findViewById(R.id.edit_key_action_add_key);
return root;
}
@@ -123,7 +134,7 @@ public class EditKeyFragment extends LoaderFragment implements
@Override
public void onClick(View v) {
// Save
save();
save(mCurrentPassphrase);
}
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
new OnClickListener() {
@@ -164,6 +175,8 @@ public class EditKeyFragment extends LoaderFragment implements
getActivity().finish();
}
cachePassphraseForEdit();
mChangePassphrase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -178,6 +191,13 @@ public class EditKeyFragment extends LoaderFragment implements
}
});
mAddSubkey.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
addSubkey();
}
});
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel);
mUserIdsList.setAdapter(mUserIdsAdapter);
@@ -189,17 +209,21 @@ public class EditKeyFragment extends LoaderFragment implements
}
});
mUserIdsAddedAdapter = new UserIdsArrayAdapter(getActivity());
// TODO: mUserIdsAddedData and SaveParcel from savedInstance?!
mUserIdsAddedData = new ArrayList<UserIdsAddedAdapter.UserIdModel>();
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mUserIdsAddedData);
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
mUserIdsAddedAdapter.setData(mSaveKeyringParcel.addUserIds);
mKeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
mKeysList.setAdapter(mKeysAdapter);
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
mSubkeysList.setAdapter(mSubkeysAdapter);
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys);
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
getLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -212,10 +236,10 @@ public class EditKeyFragment extends LoaderFragment implements
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
}
case LOADER_ID_KEYS: {
case LOADER_ID_SUBKEYS: {
Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
SubkeysAdapter.KEYS_PROJECTION, null, null, null);
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
}
default:
@@ -231,8 +255,8 @@ public class EditKeyFragment extends LoaderFragment implements
mUserIdsAdapter.swapCursor(data);
break;
case LOADER_ID_KEYS:
mKeysAdapter.swapCursor(data);
case LOADER_ID_SUBKEYS:
mSubkeysAdapter.swapCursor(data);
break;
}
@@ -248,8 +272,8 @@ public class EditKeyFragment extends LoaderFragment implements
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(null);
break;
case LOADER_ID_KEYS:
mKeysAdapter.swapCursor(null);
case LOADER_ID_SUBKEYS:
mSubkeysAdapter.swapCursor(null);
break;
}
}
@@ -262,11 +286,9 @@ public class EditKeyFragment extends LoaderFragment implements
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
// set new returned passphrase!
String newPassphrase = data
// cache new returned passphrase!
mSaveKeyringParcel.newPassphrase = data
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
mSaveKeyringParcel.newPassphrase = newPassphrase;
}
}
};
@@ -275,7 +297,7 @@ public class EditKeyFragment extends LoaderFragment implements
Messenger messenger = new Messenger(returnHandler);
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
messenger, R.string.title_change_passphrase);
messenger, mCurrentPassphrase, R.string.title_change_passphrase);
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
}
@@ -321,56 +343,41 @@ public class EditKeyFragment extends LoaderFragment implements
}
private void addUserId() {
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case AddUserIdDialogFragment.MESSAGE_OKAY:
Bundle data = message.getData();
String userId = data.getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID);
if (userId != null) {
mSaveKeyringParcel.addUserIds.add(userId);
mUserIdsAddedAdapter.setData(mSaveKeyringParcel.addUserIds);
}
}
getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad();
}
};
// Create a new Messenger for the communication back
final Messenger messenger = new Messenger(returnHandler);
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
AddUserIdDialogFragment dialogFragment =
AddUserIdDialogFragment.newInstance(messenger);
dialogFragment.show(getActivity().getSupportFragmentManager(), "addUserIdDialog");
}
});
mUserIdsAddedAdapter.add(new UserIdsAddedAdapter.UserIdModel());
}
private void save() {
String passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(),
private void addSubkey() {
// default values
mSubkeysAddedAdapter.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
}
private void cachePassphraseForEdit() {
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(),
mSaveKeyringParcel.mMasterKeyId);
if (passphrase == null) {
if (mCurrentPassphrase == null) {
PassphraseDialogFragment.show(getActivity(), mSaveKeyringParcel.mMasterKeyId,
new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
saveFinal();
mCurrentPassphrase =
message.getData().getString(PassphraseDialogFragment.MESSAGE_DATA_PASSPHRASE);
Log.d(Constants.TAG, "after caching passphrase");
} else {
EditKeyFragment.this.getActivity().finish();
}
}
}
);
}
}
private void saveFinal() {
private void save(String passphrase) {
Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList());
Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase);
mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList();
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
getActivity(),
@@ -381,6 +388,9 @@ public class EditKeyFragment extends LoaderFragment implements
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
getActivity().finish();
// TODO below
// get returned data bundle
Bundle returnData = message.getData();
if (returnData == null) {
@@ -408,6 +418,7 @@ public class EditKeyFragment extends LoaderFragment implements
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.SAVE_KEYRING_PASSPHRASE, passphrase);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, mSaveKeyringParcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@@ -298,7 +298,7 @@ public class EncryptActivity extends DrawerActivity implements
// encrypt file based on Uri
mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris);
mSwitchToContent = PAGER_CONTENT_FILE;
} else {
} else if (ACTION_ENCRYPT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");
}

View File

@@ -17,21 +17,33 @@
package org.sufficientlysecure.keychain.ui;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.view.Menu;
import android.view.MenuItem;
import com.devspark.appmsg.AppMsg;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.util.ArrayList;
public class KeyListActivity extends DrawerActivity {
@@ -121,9 +133,42 @@ public class KeyListActivity extends DrawerActivity {
}
private void createKeyExpert() {
Intent intent = new Intent(this, EditKeyActivity.class);
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
startActivityForResult(intent, 0);
}
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
}
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
Bundle data = message.getData();
// OtherHelper.logDebugBundle(data, "message reply");
}
};
// fill values for this action
Bundle data = new Bundle();
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
parcel.addUserIds.add("swagerinho");
parcel.newPassphrase = "swag";
// get selected key entries
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(this);
startService(intent);
}
}

View File

@@ -47,7 +47,6 @@ import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -59,7 +58,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.util.Highlighter;
@@ -106,10 +104,10 @@ public class KeyListFragment extends LoaderFragment
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), EditKeyActivity.class);
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, ""); // show user id view
startActivityForResult(intent, 0);
}
});
@@ -205,7 +203,7 @@ public class KeyListFragment extends LoaderFragment
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
if (checked) {
mAdapter.setNewSelection(position, true);
mAdapter.setNewSelection(position, checked);
} else {
mAdapter.removeSelection(position);
}
@@ -439,9 +437,7 @@ public class KeyListFragment extends LoaderFragment
private class ItemViewHolder {
TextView mMainUserId;
TextView mMainUserIdRest;
View mStatusDivider;
FrameLayout mStatusLayout;
ImageButton mButton;
TextView mRevoked;
ImageView mVerified;
}
@@ -452,9 +448,7 @@ public class KeyListFragment extends LoaderFragment
ItemViewHolder holder = new ItemViewHolder();
holder.mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
holder.mStatusDivider = view.findViewById(R.id.status_divider);
holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
holder.mButton = (ImageButton) view.findViewById(R.id.edit);
holder.mRevoked = (TextView) view.findViewById(R.id.revoked);
holder.mVerified = (ImageView) view.findViewById(R.id.verified);
view.setTag(holder);
@@ -491,26 +485,12 @@ public class KeyListFragment extends LoaderFragment
{ // set edit button and revoked info, specific by key type
if (cursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
// this is a secret key - show the edit mButton
h.mStatusDivider.setVisibility(View.VISIBLE);
// this is a secret key
h.mStatusLayout.setVisibility(View.VISIBLE);
h.mRevoked.setVisibility(View.GONE);
h.mVerified.setVisibility(View.GONE);
h.mButton.setVisibility(View.VISIBLE);
final long id = cursor.getLong(INDEX_MASTER_KEY_ID);
h.mButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id)));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
}
});
} else {
// this is a public key - hide the edit mButton, show if it's revoked
h.mStatusDivider.setVisibility(View.GONE);
h.mButton.setVisibility(View.GONE);
// this is a public key - show if it's revoked
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = !cursor.isNull(INDEX_EXPIRY)

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.os.Bundle;
@@ -9,14 +26,14 @@ import android.view.animation.AnimationUtils;
import org.sufficientlysecure.keychain.R;
/** This is a fragment helper class, which implements a generic
/**
* This is a fragment helper class, which implements a generic
* progressbar/container view.
*
* <p/>
* To use it in a fragment, simply subclass, use onCreateView to create the
* layout's root view, and ues getContainer() as root view of your subclass.
* The layout shows a progress bar by default, and can be switched to the
* actual contents by calling setContentShown().
*
*/
public class LoaderFragment extends Fragment {
private boolean mContentShown;
@@ -35,7 +52,6 @@ public class LoaderFragment extends Fragment {
mContentShown = false;
return root;
}
protected ViewGroup getContainer() {

View File

@@ -87,7 +87,7 @@ public class ViewKeyKeysFragment extends LoaderFragment implements
setContentShown(false);
Uri baseUri = Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
SubkeysAdapter.KEYS_PROJECTION, null, null, null);
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

View File

@@ -49,7 +49,6 @@ public class ViewKeyMainFragment extends LoaderFragment implements
public static final String ARG_DATA_URI = "uri";
private View mActionEdit;
private View mActionEditNew;
private View mActionEditDivider;
private View mActionEncrypt;
private View mActionCertify;
@@ -74,7 +73,6 @@ public class ViewKeyMainFragment extends LoaderFragment implements
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mActionEdit = view.findViewById(R.id.view_key_action_edit);
mActionEditNew = view.findViewById(R.id.view_key_action_edit_new);
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt);
mActionCertify = view.findViewById(R.id.view_key_action_certify);
@@ -118,11 +116,6 @@ public class ViewKeyMainFragment extends LoaderFragment implements
editKey(mDataUri);
}
});
mActionEditNew.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
editKeyNew(mDataUri);
}
});
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0);
mUserIds.setAdapter(mUserIdsAdapter);
@@ -259,16 +252,9 @@ public class ViewKeyMainFragment extends LoaderFragment implements
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
}
private void editKeyNew(Uri dataUri) {
Intent editIntent = new Intent(getActivity(), EditKeyActivityNew.class);
// editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
// editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
// startActivityForResult(editIntent, 0);
startActivity(editIntent);
}
}

View File

@@ -142,7 +142,7 @@ public class WizardActivity extends ActionBarActivity {
emailView.setThreshold(1); // Start working from first character
emailView.setAdapter(
new ArrayAdapter<String>
(getActivity(), android.R.layout.simple_dropdown_item_1line,
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserEmails(getActivity())
)
);
@@ -177,7 +177,7 @@ public class WizardActivity extends ActionBarActivity {
nameView.setThreshold(1); // Start working from first character
nameView.setAdapter(
new ArrayAdapter<String>
(getActivity(), android.R.layout.simple_dropdown_item_1line,
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserNames(getActivity())
)
);

View File

@@ -38,22 +38,11 @@ import java.util.Date;
public class SubkeysAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private int mIndexKeyId;
private int mIndexAlgorithm;
private int mIndexKeySize;
private int mIndexRank;
private int mIndexCanCertify;
private int mIndexCanEncrypt;
private int mIndexCanSign;
private int mIndexHasSecret;
private int mIndexRevokedKey;
private int mIndexExpiry;
private boolean hasAnySecret;
private ColorStateList mDefaultTextColor;
public static final String[] KEYS_PROJECTION = new String[] {
public static final String[] SUBKEYS_PROJECTION = new String[]{
Keys._ID,
Keys.KEY_ID,
Keys.RANK,
@@ -68,24 +57,32 @@ public class SubkeysAdapter extends CursorAdapter {
Keys.EXPIRY,
Keys.FINGERPRINT
};
private static final int INDEX_ID = 0;
private static final int INDEX_KEY_ID = 1;
private static final int INDEX_RANK = 2;
private static final int INDEX_ALGORITHM = 3;
private static final int INDEX_KEY_SIZE = 4;
private static final int INDEX_HAS_SECRET = 5;
private static final int INDEX_CAN_CERTIFY = 6;
private static final int INDEX_CAN_ENCRYPT = 7;
private static final int INDEX_CAN_SIGN = 8;
private static final int INDEX_IS_REVOKED = 9;
private static final int INDEX_CREATION = 10;
private static final int INDEX_EXPIRY = 11;
private static final int INDEX_FINGERPRINT = 12;
public SubkeysAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
initIndex(c);
}
@Override
public Cursor swapCursor(Cursor newCursor) {
initIndex(newCursor);
hasAnySecret = false;
if (newCursor != null) {
newCursor.moveToFirst();
if (newCursor != null && newCursor.moveToFirst()) {
do {
if (newCursor.getInt(mIndexHasSecret) != 0) {
if (newCursor.getInt(INDEX_HAS_SECRET) != 0) {
hasAnySecret = true;
break;
}
@@ -95,27 +92,6 @@ public class SubkeysAdapter extends CursorAdapter {
return super.swapCursor(newCursor);
}
/**
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
* performance comparison see http://stackoverflow.com/a/17999582
*
* @param cursor
*/
private void initIndex(Cursor cursor) {
if (cursor != null) {
mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
mIndexRank = cursor.getColumnIndexOrThrow(Keys.RANK);
mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
mIndexHasSecret = cursor.getColumnIndexOrThrow(Keys.HAS_SECRET);
mIndexRevokedKey = cursor.getColumnIndexOrThrow(Keys.IS_REVOKED);
mIndexExpiry = cursor.getColumnIndexOrThrow(Keys.EXPIRY);
}
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView keyId = (TextView) view.findViewById(R.id.keyId);
@@ -127,16 +103,16 @@ public class SubkeysAdapter extends CursorAdapter {
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId));
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(INDEX_KEY_ID));
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
context,
cursor.getInt(mIndexAlgorithm),
cursor.getInt(mIndexKeySize)
cursor.getInt(INDEX_ALGORITHM),
cursor.getInt(INDEX_KEY_SIZE)
);
keyId.setText(keyIdStr);
// may be set with additional "stripped" later on
if (hasAnySecret && cursor.getInt(mIndexHasSecret) == 0) {
if (hasAnySecret && cursor.getInt(INDEX_HAS_SECRET) == 0) {
keyDetails.setText(algorithmStr + ", " +
context.getString(R.string.key_stripped));
} else {
@@ -144,13 +120,13 @@ public class SubkeysAdapter extends CursorAdapter {
}
// Set icons according to properties
masterKeyIcon.setVisibility(cursor.getInt(mIndexRank) == 0 ? View.VISIBLE : View.INVISIBLE);
certifyIcon.setVisibility(cursor.getInt(mIndexCanCertify) != 0 ? View.VISIBLE : View.GONE);
encryptIcon.setVisibility(cursor.getInt(mIndexCanEncrypt) != 0 ? View.VISIBLE : View.GONE);
signIcon.setVisibility(cursor.getInt(mIndexCanSign) != 0 ? View.VISIBLE : View.GONE);
masterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
certifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
encryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
signIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
boolean valid = true;
if (cursor.getInt(mIndexRevokedKey) > 0) {
if (cursor.getInt(INDEX_IS_REVOKED) > 0) {
revokedKeyIcon.setVisibility(View.VISIBLE);
valid = false;
@@ -162,17 +138,19 @@ public class SubkeysAdapter extends CursorAdapter {
revokedKeyIcon.setVisibility(View.GONE);
}
if (!cursor.isNull(mIndexExpiry)) {
Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000);
if (!cursor.isNull(INDEX_EXPIRY)) {
Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
valid = valid && expiryDate.after(new Date());
keyExpiry.setText(
context.getString(R.string.label_expiry) + ": " +
DateFormat.getDateFormat(context).format(expiryDate));
DateFormat.getDateFormat(context).format(expiryDate)
);
} else {
keyExpiry.setText(
context.getString(R.string.label_expiry) + ": " +
context.getString(R.string.none));
context.getString(R.string.none)
);
}
// if key is expired or revoked, strike through text

View File

@@ -0,0 +1,361 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.adapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
private LayoutInflater mInflater;
private Activity mActivity;
public interface OnAlgorithmSelectedListener {
public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
}
// hold a private reference to the underlying data List
private List<SaveKeyringParcel.SubkeyAdd> mData;
public SubkeysAddedAdapter(Activity activity, List<SaveKeyringParcel.SubkeyAdd> data) {
super(activity, -1, data);
mActivity = activity;
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mData = data;
}
static class ViewHolder {
public OnAlgorithmSelectedListener mAlgorithmSelectedListener;
public Spinner mAlgorithmSpinner;
public Spinner mKeySizeSpinner;
public TextView mCustomKeyTextView;
public EditText mCustomKeyEditText;
public TextView mCustomKeyInfoTextView;
public ImageButton vDelete;
// also hold a reference to the model item
public SaveKeyringParcel.SubkeyAdd mModel;
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.edit_key_subkey_added_item, null);
final ViewHolder holder = new ViewHolder();
holder.mAlgorithmSpinner = (Spinner) convertView.findViewById(R.id.create_key_algorithm);
holder.mKeySizeSpinner = (Spinner) convertView.findViewById(R.id.create_key_size);
holder.mCustomKeyTextView = (TextView) convertView.findViewById(R.id.custom_key_size_label);
holder.mCustomKeyEditText = (EditText) convertView.findViewById(R.id.custom_key_size_input);
holder.mCustomKeyInfoTextView = (TextView) convertView.findViewById(R.id.custom_key_size_info);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_added_item_delete);
convertView.setTag(holder);
holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem();
// update referenced model item
holder.mModel.mAlgorithm = newKeyAlgorithmChoice.getId();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem();
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(),
getSelectedKeyLength(holder.mKeySizeSpinner, holder.mCustomKeyEditText));
// update referenced model item
holder.mModel.mKeysize = newKeySize;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// remove reference model item from adapter (data and notify about change)
SubkeysAddedAdapter.this.remove(holder.mModel);
}
});
}
final ViewHolder holder = (ViewHolder) convertView.getTag();
// save reference to model item
holder.mModel = getItem(position);
// TODO
boolean wouldBeMasterKey = false;
// boolean wouldBeMasterKey = (childCount == 0);
ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(Constants.choice.algorithm.dsa, mActivity.getResources().getString(
R.string.dsa)));
if (!wouldBeMasterKey) {
choices.add(new Choice(Constants.choice.algorithm.elgamal, mActivity.getResources().getString(
R.string.elgamal)));
}
choices.add(new Choice(Constants.choice.algorithm.rsa, mActivity.getResources().getString(
R.string.rsa)));
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(mActivity,
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.mAlgorithmSpinner.setAdapter(adapter);
// make RSA the default
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Constants.choice.algorithm.rsa) {
holder.mAlgorithmSpinner.setSelection(i);
break;
}
}
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(mActivity, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(Arrays.asList(mActivity.getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.mKeySizeSpinner.setAdapter(keySizeAdapter);
holder.mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
holder.mCustomKeyEditText.addTextChangedListener(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) {
// setOkButtonAvailability(alertDialog);
}
});
holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText,
holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView);
// setOkButtonAvailability(alertDialog);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId(),
holder.mKeySizeSpinner, holder.mCustomKeyInfoTextView);
setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText,
holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView);
// setOkButtonAvailability(alertDialog);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
//
// holder.vAddress.setText(holder.mModel.address);
// holder.vAddress.setThreshold(1); // Start working from first character
// holder.vAddress.setAdapter(mAutoCompleteEmailAdapter);
//
// holder.vName.setText(holder.mModel.name);
// holder.vName.setThreshold(1); // Start working from first character
// holder.vName.setAdapter(mAutoCompleteNameAdapter);
//
// holder.vComment.setText(holder.mModel.comment);
return convertView;
}
private int getSelectedKeyLength(Spinner keySizeSpinner, EditText customKeyEditText) {
final String selectedItemString = (String) keySizeSpinner.getSelectedItem();
final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
String keyLengthString = customSelected ? customKeyEditText.getText().toString() : selectedItemString;
int keySize;
try {
keySize = Integer.parseInt(keyLengthString);
} catch (NumberFormatException e) {
keySize = 0;
}
return keySize;
}
/**
* <h3>RSA</h3>
* <p>for RSA algorithm, key length must be greater than 1024 (according to
* <a href="https://github.com/open-keychain/open-keychain/issues/102">#102</a>). Possibility to generate keys bigger
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
* multiplicity of 8.</p>
* <h3>ElGamal</h3>
* <p>For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.</p>
* <h3>DSA</h3>
* <p>For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.</p>
*
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
* inappropriate.
*/
private int getProperKeyLength(int algorithmId, int currentKeyLength) {
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
int properKeyLength = -1;
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
if (currentKeyLength > 1024 && currentKeyLength <= 8192) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
}
break;
case Constants.choice.algorithm.elgamal:
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
}
int minimalValue = Integer.MAX_VALUE;
int minimalIndex = -1;
for (int i = 0; i < elGammalKeyDiff.length; i++) {
if (elGammalKeyDiff[i] <= minimalValue) {
minimalValue = elGammalKeyDiff[i];
minimalIndex = i;
}
}
properKeyLength = elGamalSupportedLengths[minimalIndex];
break;
case Constants.choice.algorithm.dsa:
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
}
break;
}
return properKeyLength;
}
// TODO: make this an error message on the field
// private boolean setOkButtonAvailability(AlertDialog alertDialog) {
// final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem();
// final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem());
// final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize);
// alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0);
// }
private void setCustomKeyVisibility(Spinner keySizeSpinner, EditText customkeyedittext, TextView customKeyTextView, TextView customKeyInfoTextView) {
final String selectedItemString = (String) keySizeSpinner.getSelectedItem();
final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
final int visibility = customSelected ? View.VISIBLE : View.GONE;
customkeyedittext.setVisibility(visibility);
customKeyTextView.setVisibility(visibility);
customKeyInfoTextView.setVisibility(visibility);
// hide keyboard after setting visibility to gone
if (visibility == View.GONE) {
InputMethodManager imm = (InputMethodManager)
mActivity.getSystemService(mActivity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(customkeyedittext.getWindowToken(), 0);
}
}
private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId, Spinner keySizeSpinner, TextView customKeyInfoTextView) {
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) keySizeSpinner.getAdapter();
final Object selectedItem = keySizeSpinner.getSelectedItem();
keySizeAdapter.clear();
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_rsa));
break;
case Constants.choice.algorithm.elgamal:
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
customKeyInfoTextView.setText(""); // ElGamal does not support custom key length
break;
case Constants.choice.algorithm.dsa:
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_dsa));
break;
}
keySizeAdapter.notifyDataSetChanged();
// when switching algorithm, try to select same key length as before
for (int i = 0; i < keySizeAdapter.getCount(); i++) {
if (selectedItem.equals(keySizeAdapter.getItem(i))) {
keySizeSpinner.setSelection(i);
break;
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) {
final String[] spinnerValuesStringArray = mActivity.getResources().getStringArray(stringArrayResourceId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
arrayAdapter.addAll(spinnerValuesStringArray);
} else {
for (final String value : spinnerValuesStringArray) {
arrayAdapter.add(value);
}
}
}
}

View File

@@ -41,9 +41,6 @@ import java.util.ArrayList;
public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemClickListener {
private LayoutInflater mInflater;
private int mIndexUserId, mIndexRank;
private int mVerifiedId, mIsRevoked, mIsPrimary;
private final ArrayList<Boolean> mCheckStates;
private SaveKeyringParcel mSaveKeyringParcel;
@@ -56,6 +53,13 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
UserIds.IS_PRIMARY,
UserIds.IS_REVOKED
};
private static final int INDEX_ID = 0;
private static final int INDEX_USER_ID = 1;
private static final int INDEX_RANK = 2;
private static final int INDEX_VERIFIED = 3;
private static final int INDEX_IS_PRIMARY = 4;
private static final int INDEX_IS_REVOKED = 5;
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes,
SaveKeyringParcel saveKeyringParcel) {
@@ -64,8 +68,6 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null;
mSaveKeyringParcel = saveKeyringParcel;
initIndex(c);
}
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
@@ -82,7 +84,6 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
@Override
public Cursor swapCursor(Cursor newCursor) {
initIndex(newCursor);
if (mCheckStates != null) {
mCheckStates.clear();
if (newCursor != null) {
@@ -91,7 +92,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
// initialize to true (use case knowledge: we usually want to sign all uids)
for (int i = 0; i < count; i++) {
newCursor.moveToPosition(i);
int verified = newCursor.getInt(mVerifiedId);
int verified = newCursor.getInt(INDEX_VERIFIED);
mCheckStates.add(verified != Certs.VERIFIED_SECRET);
}
}
@@ -100,31 +101,15 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
return super.swapCursor(newCursor);
}
/**
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
* performance comparison see http://stackoverflow.com/a/17999582
*
* @param cursor
*/
private void initIndex(Cursor cursor) {
if (cursor != null) {
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
mVerifiedId = cursor.getColumnIndexOrThrow(UserIds.VERIFIED);
mIsRevoked = cursor.getColumnIndexOrThrow(UserIds.IS_REVOKED);
mIsPrimary = cursor.getColumnIndexOrThrow(UserIds.IS_PRIMARY);
}
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView vName = (TextView) view.findViewById(R.id.userId);
TextView vAddress = (TextView) view.findViewById(R.id.address);
TextView vComment = (TextView) view.findViewById(R.id.comment);
ImageView vVerified = (ImageView) view.findViewById(R.id.certified);
ImageView vHasChanges = (ImageView) view.findViewById(R.id.has_changes);
ImageView vEditImage = (ImageView) view.findViewById(R.id.edit_image);
String userId = cursor.getString(mIndexUserId);
String userId = cursor.getString(INDEX_USER_ID);
String[] splitUserId = KeyRing.splitUserId(userId);
if (splitUserId[0] != null) {
vName.setText(splitUserId[0]);
@@ -144,31 +129,29 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
vComment.setVisibility(View.GONE);
}
boolean isPrimary = cursor.getInt(mIsPrimary) != 0;
boolean isRevoked = cursor.getInt(mIsRevoked) > 0;
boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0;
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
// for edit key
if (mSaveKeyringParcel != null) {
boolean changeUserId = (mSaveKeyringParcel.changePrimaryUserId != null
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null);
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId));
boolean revoke = (mSaveKeyringParcel.revokeUserIds.contains(userId));
boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId));
if (changeUserId) {
isPrimary = !isPrimary;
if (changeAnyPrimaryUserId) {
// change all user ids, only this one should be primary
isPrimary = changeThisPrimaryUserId;
}
if (revoke) {
if (revokeThisUserId) {
if (!isRevoked) {
isRevoked = true;
}
}
if (changeUserId || revoke) {
vHasChanges.setVisibility(View.VISIBLE);
} else {
vHasChanges.setVisibility(View.GONE);
}
vEditImage.setVisibility(View.VISIBLE);
} else {
vHasChanges.setVisibility(View.GONE);
vEditImage.setVisibility(View.GONE);
}
if (isRevoked) {
@@ -178,15 +161,18 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
// disable and strike through text for revoked user ids
vName.setEnabled(false);
vAddress.setEnabled(false);
vComment.setEnabled(false);
vName.setText(OtherHelper.strikeOutText(vName.getText()));
vAddress.setText(OtherHelper.strikeOutText(vAddress.getText()));
vComment.setText(OtherHelper.strikeOutText(vComment.getText()));
} else {
vName.setEnabled(true);
vAddress.setEnabled(true);
vComment.setEnabled(true);
// verified: has been verified
// isPrimary: show small star icon for primary user ids
int verified = cursor.getInt(mVerifiedId);
int verified = cursor.getInt(INDEX_VERIFIED);
switch (verified) {
case Certs.VERIFIED_SECRET:
vVerified.setImageResource(isPrimary
@@ -234,7 +220,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
for (int i = 0; i < mCheckStates.size(); i++) {
if (mCheckStates.get(i)) {
mCursor.moveToPosition(i);
result.add(mCursor.getString(mIndexUserId));
result.add(mCursor.getString(INDEX_USER_ID));
}
}
return result;
@@ -242,7 +228,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
public String getUserId(int position) {
mCursor.moveToPosition(position);
return mCursor.getString(mIndexUserId);
return mCursor.getString(INDEX_USER_ID);
}
@Override

View File

@@ -0,0 +1,206 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.adapter;
import android.app.Activity;
import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ImageButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
public class UserIdsAddedAdapter extends ArrayAdapter<UserIdsAddedAdapter.UserIdModel> {
private LayoutInflater mInflater;
private Activity mActivity;
private ArrayAdapter<String> mAutoCompleteNameAdapter;
private ArrayAdapter<String> mAutoCompleteEmailAdapter;
// hold a private reference to the underlying data List
private List<UserIdModel> mData;
public static class UserIdModel {
String name = "";
String address = "";
String comment = "";
@Override
public String toString() {
String userId = name;
if (!TextUtils.isEmpty(comment)) {
userId += " (" + comment + ")";
}
if (!TextUtils.isEmpty(address)) {
userId += " <" + address + ">";
}
return userId;
}
}
public UserIdsAddedAdapter(Activity activity, List<UserIdModel> data) {
super(activity, -1, data);
mActivity = activity;
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mData = data;
mAutoCompleteNameAdapter = new ArrayAdapter<String>
(mActivity, android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserNames(mActivity)
);
mAutoCompleteEmailAdapter = new ArrayAdapter<String>
(mActivity, android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserEmails(mActivity)
);
}
public ArrayList<String> getDataAsStringList() {
ArrayList<String> out = new ArrayList<String>();
for (UserIdModel id : mData) {
// ignore empty user ids
if (!TextUtils.isEmpty(id.toString())) {
out.add(id.toString());
}
}
return out;
}
static class ViewHolder {
public AutoCompleteTextView vAddress;
public AutoCompleteTextView vName;
public EditText vComment;
public ImageButton vDelete;
// also hold a reference to the model item
public UserIdModel mModel;
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.edit_key_user_id_added_item, null);
final ViewHolder holder = new ViewHolder();
holder.vAddress = (AutoCompleteTextView) convertView.findViewById(R.id.user_id_added_item_address);
holder.vName = (AutoCompleteTextView) convertView.findViewById(R.id.user_id_added_item_name);
holder.vComment = (EditText) convertView.findViewById(R.id.user_id_added_item_comment);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.user_id_added_item_delete);
convertView.setTag(holder);
holder.vAddress.addTextChangedListener(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 referenced item in view holder
holder.mModel.address = s.toString();
// show icon on valid email addresses
if (holder.mModel.address.length() > 0) {
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(holder.mModel.address);
if (emailMatcher.matches()) {
holder.vAddress.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.uid_mail_ok, 0);
} else {
holder.vAddress.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.uid_mail_bad, 0);
}
} else {
// remove drawable if email is empty
holder.vAddress.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
}
});
holder.vName.addTextChangedListener(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 referenced item in view holder
holder.mModel.name = s.toString();
}
});
holder.vComment.addTextChangedListener(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 referenced item in view holder
holder.mModel.comment = s.toString();
}
});
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// remove reference model item from adapter (data and notify about change)
UserIdsAddedAdapter.this.remove(holder.mModel);
}
});
}
final ViewHolder holder = (ViewHolder) convertView.getTag();
// save reference to model item
holder.mModel = getItem(position);
holder.vAddress.setText(holder.mModel.address);
holder.vAddress.setThreshold(1); // Start working from first character
holder.vAddress.setAdapter(mAutoCompleteEmailAdapter);
holder.vName.setText(holder.mModel.name);
holder.vName.setThreshold(1); // Start working from first character
holder.vName.setAdapter(mAutoCompleteNameAdapter);
holder.vComment.setText(holder.mModel.comment);
return convertView;
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.adapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import java.util.List;
public class UserIdsArrayAdapter extends ArrayAdapter<String> {
protected LayoutInflater mInflater;
protected Activity mActivity;
protected List<String> mData;
static class ViewHolder {
public TextView vName;
public TextView vAddress;
public TextView vComment;
public ImageView vVerified;
public ImageView vHasChanges;
public CheckBox vCheckBox;
}
public UserIdsArrayAdapter(Activity activity) {
super(activity, -1);
mActivity = activity;
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setData(List<String> data) {
clear();
if (data != null) {
this.mData = data;
// add data to extended ArrayAdapter
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
addAll(data);
} else {
for (String entry : data) {
add(entry);
}
}
}
}
public List<String> getData() {
return mData;
}
@Override
public boolean hasStableIds() {
return true;
}
public View getView(int position, View convertView, ViewGroup parent) {
String entry = mData.get(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.view_key_userids_item, null);
holder.vName = (TextView) convertView.findViewById(R.id.userId);
holder.vAddress = (TextView) convertView.findViewById(R.id.address);
holder.vComment = (TextView) convertView.findViewById(R.id.comment);
holder.vVerified = (ImageView) convertView.findViewById(R.id.certified);
holder.vHasChanges = (ImageView) convertView.findViewById(R.id.has_changes);
holder.vCheckBox = (CheckBox) convertView.findViewById(R.id.checkBox);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// user id
String[] splitUserId = KeyRing.splitUserId(entry);
if (splitUserId[0] != null) {
holder.vName.setText(splitUserId[0]);
} else {
holder.vName.setText(R.string.user_id_no_name);
}
if (splitUserId[1] != null) {
holder.vAddress.setText(splitUserId[1]);
holder.vAddress.setVisibility(View.VISIBLE);
} else {
holder.vAddress.setVisibility(View.GONE);
}
if (splitUserId[2] != null) {
holder.vComment.setText(splitUserId[2]);
holder.vComment.setVisibility(View.VISIBLE);
} else {
holder.vComment.setVisibility(View.GONE);
}
holder.vCheckBox.setVisibility(View.GONE);
holder.vVerified.setImageResource(R.drawable.key_certify_ok_depth0);
// all items are "new"
holder.vHasChanges.setVisibility(View.VISIBLE);
return convertView;
}
}

View File

@@ -1,165 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class AddUserIdDialogFragment extends DialogFragment implements EditText.OnEditorActionListener {
private static final String ARG_MESSENGER = "messenger";
public static final int MESSAGE_OKAY = 1;
public static final String MESSAGE_DATA_USER_ID = "user_id";
private Messenger mMessenger;
EditText mName;
EditText mAddress;
EditText mComment;
/**
* Creates new instance of this dialog fragment
*/
public static AddUserIdDialogFragment newInstance(Messenger messenger) {
AddUserIdDialogFragment frag = new AddUserIdDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
frag.setArguments(args);
return frag;
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.add_user_id_dialog, null);
alert.setView(view);
alert.setTitle("Add Identity");
mName = (EditText) view.findViewById(R.id.name);
mAddress = (EditText) view.findViewById(R.id.address);
mComment = (EditText) view.findViewById(R.id.comment);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
done();
}
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
return alert.show();
}
@Override
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);
// Show soft keyboard automatically
mName.requestFocus();
getDialog().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
mComment.setOnEditorActionListener(this);
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
done();
return true;
}
return false;
}
private void done() {
String name = mName.getText().toString();
String email = mAddress.getText().toString();
String comment = mComment.getText().toString();
String userId = null;
if (!TextUtils.isEmpty(name)) {
userId = name;
if (!TextUtils.isEmpty(comment)) {
userId += " (" + comment + ")";
}
if (!TextUtils.isEmpty(email)) {
userId += " <" + email + ">";
}
}
Bundle data = new Bundle();
data.putString(MESSAGE_DATA_USER_ID, userId);
sendMessageToHandler(MESSAGE_OKAY, data);
this.dismiss();
}
/**
* Send message back to handler which is initialized in a activity
*
* @param what Message integer you want to send
*/
private void sendMessageToHandler(Integer what, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
}

View File

@@ -51,8 +51,6 @@ public class CreateKeyDialogFragment extends DialogFragment {
private static final String ARG_EDITOR_CHILD_COUNT = "child_count";
private int mNewKeySize;
private Choice mNewKeyAlgorithmChoice;
private OnAlgorithmSelectedListener mAlgorithmSelectedListener;
private Spinner mAlgorithmSpinner;
private Spinner mKeySizeSpinner;
@@ -131,9 +129,9 @@ public class CreateKeyDialogFragment extends DialogFragment {
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface di, int id) {
di.dismiss();
mNewKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
mNewKeySize = getProperKeyLength(mNewKeyAlgorithmChoice.getId(), getSelectedKeyLength());
mAlgorithmSelectedListener.onAlgorithmSelected(mNewKeyAlgorithmChoice, mNewKeySize);
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength());
mAlgorithmSelectedListener.onAlgorithmSelected(newKeyAlgorithmChoice, newKeySize);
}
}
);

View File

@@ -26,6 +26,7 @@ import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class EditUserIdDialogFragment extends DialogFragment {
@@ -57,9 +58,9 @@ public class EditUserIdDialogFragment extends DialogFragment {
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity());
CharSequence[] array = {"change to primary user id", "revoke"};
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id);
builder.setTitle("select action!");
builder.setTitle(R.string.edit_key_edit_user_id_title);
builder.setItems(array, new DialogInterface.OnClickListener() {
@Override
@@ -76,7 +77,7 @@ public class EditUserIdDialogFragment extends DialogFragment {
}
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();

View File

@@ -26,12 +26,15 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -44,6 +47,7 @@ import org.sufficientlysecure.keychain.util.Log;
public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_TITLE = "title";
private static final String ARG_OLD_PASSPHRASE = "old_passphrase";
public static final int MESSAGE_OKAY = 1;
@@ -52,6 +56,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
private Messenger mMessenger;
private EditText mPassphraseEditText;
private EditText mPassphraseAgainEditText;
private CheckBox mNoPassphraseCheckBox;
/**
* Creates new instance of this dialog fragment
@@ -60,11 +65,12 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
* @param messenger to communicate back after setting the passphrase
* @return
*/
public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
public static SetPassphraseDialogFragment newInstance(Messenger messenger, String oldPassphrase, int title) {
SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_TITLE, title);
args.putParcelable(ARG_MESSENGER, messenger);
args.putString(ARG_OLD_PASSPHRASE, oldPassphrase);
frag.setArguments(args);
@@ -80,6 +86,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
int title = getArguments().getInt(ARG_TITLE);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
String oldPassphrase = getArguments().getString(ARG_OLD_PASSPHRASE);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
@@ -92,6 +99,21 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
mNoPassphraseCheckBox = (CheckBox) view.findViewById(R.id.passphrase_no_passphrase);
if (TextUtils.isEmpty(oldPassphrase)) {
mNoPassphraseCheckBox.setChecked(true);
mPassphraseEditText.setEnabled(false);
mPassphraseAgainEditText.setEnabled(false);
}
mNoPassphraseCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mPassphraseEditText.setEnabled(!isChecked);
mPassphraseAgainEditText.setEnabled(!isChecked);
}
});
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -99,24 +121,31 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
public void onClick(DialogInterface dialog, int id) {
dismiss();
String passphrase1 = mPassphraseEditText.getText().toString();
String passphrase2 = mPassphraseAgainEditText.getText().toString();
if (!passphrase1.equals(passphrase2)) {
Toast.makeText(
activity,
getString(R.string.error_message,
getString(R.string.passphrases_do_not_match)), Toast.LENGTH_SHORT)
.show();
return;
}
String passphrase1;
if (mNoPassphraseCheckBox.isChecked()) {
passphrase1 = "";
} else {
passphrase1 = mPassphraseEditText.getText().toString();
String passphrase2 = mPassphraseAgainEditText.getText().toString();
if (!passphrase1.equals(passphrase2)) {
Toast.makeText(
activity,
getString(R.string.error_message,
getString(R.string.passphrases_do_not_match)), Toast.LENGTH_SHORT
)
.show();
return;
}
if (passphrase1.equals("")) {
Toast.makeText(
activity,
getString(R.string.error_message,
getString(R.string.passphrase_must_not_be_empty)),
Toast.LENGTH_SHORT).show();
return;
if (passphrase1.equals("")) {
Toast.makeText(
activity,
getString(R.string.error_message,
getString(R.string.passphrase_must_not_be_empty)),
Toast.LENGTH_SHORT
).show();
return;
}
}
// return resulting data back to activity

View File

@@ -25,9 +25,7 @@ import android.widget.ListView;
* Automatically calculate height of ListView based on contained items. This enables to put this
* ListView into a ScrollView without messing up.
* <p/>
* from
* http://stackoverflow.com/questions/2419246/how-do-i-create-a-listview-thats-not-in-a-scrollview-
* or-has-the-scrollview-dis
* from http://stackoverflow.com/a/3580117
*/
public class FixedListView extends ListView {

View File

@@ -388,7 +388,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service
Bundle data = message.getData();
Bundle data = message.getDataAsStringList();
UncachedSecretKey newKey = PgpConversionHelper
.BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));