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:
@@ -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);
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user