From dbfa55f6b963ff8c5a975c45a2805838eb1781f7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Jun 2015 00:05:55 +0200 Subject: introduce CachingDataDecryptorFactory towards cached session keys this commit introduces the CachingDataDecryptorFactory, which wraps a DataDecryptorFactory but supports caching of decrypted session keys. this change also gets rid of runtimeexception based control flow in PgpDecryptVerify. --- .../jcajce/CachingDataDecryptorFactory.java | 76 +++++ ...fcSyncPublicKeyDataDecryptorFactoryBuilder.java | 278 ------------------ .../operations/results/DecryptVerifyResult.java | 13 + .../keychain/pgp/CanonicalizedSecretKey.java | 23 +- .../keychain/pgp/PgpDecryptVerify.java | 38 ++- .../keychain/service/input/CryptoInputParcel.java | 7 +- .../keychain/ui/DecryptFilesFragment.java | 318 -------------------- .../keychain/ui/DecryptFilesInputFragment.java | 323 +++++++++++++++++++++ 8 files changed, 451 insertions(+), 625 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java delete mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java new file mode 100644 index 000000000..d35f1d751 --- /dev/null +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann + * + * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details. + */ + +package org.spongycastle.openpgp.operator.jcajce; + +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; + +import java.nio.ByteBuffer; +import java.util.Map; + +public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory +{ + private final PublicKeyDataDecryptorFactory mWrappedDecryptor; + private final Map mSessionKeyCache; + + private OperatorHelper mOperatorHelper; + + public CachingDataDecryptorFactory(String providerName, + final Map sessionKeyCache) + { + mWrappedDecryptor = null; + mSessionKeyCache = sessionKeyCache; + + mOperatorHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + } + + public CachingDataDecryptorFactory(PublicKeyDataDecryptorFactory wrapped, + final Map sessionKeyCache) + { + mWrappedDecryptor = wrapped; + mSessionKeyCache = sessionKeyCache; + + } + + public boolean hasCachedSessionData(PGPPublicKeyEncryptedData encData) throws PGPException { + ByteBuffer bi = ByteBuffer.wrap(encData.getSessionKey()[0]); + return mSessionKeyCache.containsKey(bi); + } + + public Map getCachedSessionKeys() { + return mSessionKeyCache; + } + + public boolean canDecrypt() { + return mWrappedDecryptor != null; + } + + @Override + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { + ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI + if (mSessionKeyCache.containsKey(bi)) { + return mSessionKeyCache.get(bi); + } + + byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData); + mSessionKeyCache.put(bi, sessionData); + return sessionData; + } + + @Override + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException { + if (mWrappedDecryptor != null) { + return mWrappedDecryptor.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + return mOperatorHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + +} diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java deleted file mode 100644 index 067bb3e19..000000000 --- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann - * - * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details. - */ - -package org.spongycastle.openpgp.operator.jcajce; - -import org.spongycastle.bcpg.PublicKeyAlgorithmTags; -import org.spongycastle.jcajce.util.DefaultJcaJceHelper; -import org.spongycastle.jcajce.util.NamedJcaJceHelper; -import org.spongycastle.jcajce.util.ProviderJcaJceHelper; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.operator.PGPDataDecryptor; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; - -import java.nio.ByteBuffer; -import java.security.Provider; -import java.util.Map; - - -/** - * This class is based on JcePublicKeyDataDecryptorFactoryBuilder - * - */ -public class NfcSyncPublicKeyDataDecryptorFactoryBuilder -{ - private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); - private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); - private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); -// private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); -// private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); - - public static class NfcInteractionNeeded extends RuntimeException - { - public byte[] encryptedSessionKey; - - public NfcInteractionNeeded(byte[] encryptedSessionKey) - { - super("NFC interaction required!"); - this.encryptedSessionKey = encryptedSessionKey; - } - } - - public NfcSyncPublicKeyDataDecryptorFactoryBuilder() - { - } - - /** - * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. - * - * @param provider provider object for cryptographic primitives. - * @return the current builder. - */ - public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider) - { - this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); - keyConverter.setProvider(provider); - this.contentHelper = helper; - - return this; - } - - /** - * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. - * - * @param providerName the name of the provider to reference for cryptographic primitives. - * @return the current builder. - */ - public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(String providerName) - { - this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); - keyConverter.setProvider(providerName); - this.contentHelper = helper; - - return this; - } - - public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider) - { - this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider)); - - return this; - } - - public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName) - { - this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); - - return this; - } - - public PublicKeyDataDecryptorFactory build(final Map nfcDecryptedMap) { - return new PublicKeyDataDecryptorFactory() - { - public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) - throws PGPException - { - if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) - { - throw new PGPException("ECDH not supported!"); - } - - return decryptSessionData(keyAlgorithm, secKeyData, nfcDecryptedMap); - } - - public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) - throws PGPException - { - return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); - } - }; - } - -// public PublicKeyDataDecryptorFactory build(final PrivateKey privKey) -// { -// return new PublicKeyDataDecryptorFactory() -// { -// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) -// throws PGPException -// { -// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) -// { -// throw new PGPException("ECDH requires use of PGPPrivateKey for decryption"); -// } -// return decryptSessionData(keyAlgorithm, privKey, secKeyData); -// } -// -// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) -// throws PGPException -// { -// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); -// } -// }; -// } - -// public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey, final byte[] nfcDecrypted) -// { -// return new PublicKeyDataDecryptorFactory() -// { -// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) -// throws PGPException -// { -// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) -// { -// return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData); -// } -// -// return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData, nfcDecrypted); -// } -// -// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) -// throws PGPException -// { -// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); -// } -// }; -// } - -// private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData) -// throws PGPException -// { -// ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); -// X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); -// -// byte[] enc = secKeyData[0]; -// -// int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; -// byte[] pEnc = new byte[pLen]; -// -// System.arraycopy(enc, 2, pEnc, 0, pLen); -// -// byte[] keyEnc = new byte[enc[pLen + 2]]; -// -// System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); -// -// Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); -// -// ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize(); -// -// RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); -// Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap"); -// -// try -// { -// c.init(Cipher.UNWRAP_MODE, key); -// -// Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); -// -// return PGPPad.unpadSessionData(paddedSessionKey.getEncoded()); -// } -// catch (InvalidKeyException e) -// { -// throw new PGPException("error setting asymmetric cipher", e); -// } -// catch (NoSuchAlgorithmException e) -// { -// throw new PGPException("error setting asymmetric cipher", e); -// } -// } - - private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData, - Map nfcDecryptedMap) - throws PGPException - { -// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); -// -// try -// { -// c1.init(Cipher.DECRYPT_MODE, privKey); -// } -// catch (InvalidKeyException e) -// { -// throw new PGPException("error setting asymmetric cipher", e); -// } - - if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT - || keyAlgorithm == PGPPublicKey.RSA_GENERAL) - { - ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI - - if (nfcDecryptedMap.containsKey(bi)) { - return nfcDecryptedMap.get(bi); - } else { - // catch this when decryptSessionData() is executed and divert digest to card, - // when doing the operation again reuse nfcDecrypted - throw new NfcInteractionNeeded(bi.array()); - } - -// c1.update(bi, 2, bi.length - 2); - } - else - { - throw new PGPException("ElGamal not supported!"); - -// ElGamalKey k = (ElGamalKey)privKey; -// int size = (k.getParameters().getP().bitLength() + 7) / 8; -// byte[] tmp = new byte[size]; -// -// byte[] bi = secKeyData[0]; // encoded MPI -// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... -// { -// c1.update(bi, 3, bi.length - 3); -// } -// else -// { -// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); -// c1.update(tmp); -// } -// -// bi = secKeyData[1]; // encoded MPI -// for (int i = 0; i != tmp.length; i++) -// { -// tmp[i] = 0; -// } -// -// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... -// { -// c1.update(bi, 3, bi.length - 3); -// } -// else -// { -// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); -// c1.update(tmp); -// } - } - -// try -// { -// return c1.doFinal(); -// } -// catch (Exception e) -// { -// throw new PGPException("exception decrypting session data", e); -// } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index ac571390a..25a86f137 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -22,6 +22,7 @@ import android.os.Parcel; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; @@ -36,6 +37,8 @@ public class DecryptVerifyResult extends InputPendingResult { // https://tools.ietf.org/html/rfc4880#page56 String mCharset; + CryptoInputParcel mCachedCryptoInputParcel; + byte[] mOutputBytes; public DecryptVerifyResult(int result, OperationLog log) { @@ -50,6 +53,7 @@ public class DecryptVerifyResult extends InputPendingResult { super(source); mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader()); + mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader()); } @@ -65,6 +69,14 @@ public class DecryptVerifyResult extends InputPendingResult { mSignatureResult = signatureResult; } + public CryptoInputParcel getCachedCryptoInputParcel() { + return mCachedCryptoInputParcel; + } + + public void setCachedCryptoInputParcel(CryptoInputParcel cachedCryptoInputParcel) { + mCachedCryptoInputParcel = cachedCryptoInputParcel; + } + public OpenPgpMetadata getDecryptMetadata() { return mDecryptMetadata; } @@ -97,6 +109,7 @@ public class DecryptVerifyResult extends InputPendingResult { super.writeToParcel(dest, flags); dest.writeParcelable(mSignatureResult, 0); dest.writeParcelable(mDecryptMetadata, 0); + dest.writeParcelable(mCachedCryptoInputParcel, 0); } public static final Creator CREATOR = new Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 17d342341..31a3925da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -21,23 +21,18 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.S2K; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -51,7 +46,6 @@ import java.security.interfaces.RSAPrivateCrtKey; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; @@ -270,19 +264,20 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } } - public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) { + public CachingDataDecryptorFactory getCachingDecryptorFactory(CryptoInputParcel cryptoInput) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { - return new NfcSyncPublicKeyDataDecryptorFactoryBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - cryptoInput.getCryptoData() - ); + return new CachingDataDecryptorFactory( + Constants.BOUNCY_CASTLE_PROVIDER_NAME, + cryptoInput.getCryptoData()); } else { - return new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey); + return new CachingDataDecryptorFactory( + new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey), + cryptoInput.getCryptoData()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 4382c9fae..235b0a671 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -24,6 +24,7 @@ import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; @@ -40,11 +41,10 @@ import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; @@ -541,24 +541,33 @@ public class PgpDecryptVerify extends BaseOperation { currentProgress += 2; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - try { - PublicKeyDataDecryptorFactory decryptorFactory - = secretEncryptionKey.getDecryptorFactory(cryptoInput); - try { - clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); - } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) { - log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); - } + CachingDataDecryptorFactory decryptorFactory + = secretEncryptionKey.getCachingDecryptorFactory(cryptoInput); + + // special case: if the decryptor does not have a session key cached for this encrypted + // data, and can't actually decrypt on its own, return a pending intent + if (!decryptorFactory.canDecrypt() + && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) { - symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); - } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) { log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation( secretEncryptionKey.getRing().getMasterKeyId(), - secretEncryptionKey.getKeyId(), e.encryptedSessionKey + secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0] )); + } + + try { + clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); + } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) { + log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); + + cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys()); + encryptedData = encryptedDataAsymmetric; } else { // there wasn't even any useful data @@ -821,6 +830,7 @@ public class PgpDecryptVerify extends BaseOperation { // Return a positive result, with metadata and verification info DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setCachedCryptoInputParcel(cryptoInput); result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); result.setCharset(charset); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 3d1ccaca1..ee7caf2d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -97,8 +97,12 @@ public class CryptoInputParcel implements Parcelable { mCryptoData.put(ByteBuffer.wrap(hash), signedHash); } + public void addCryptoData(Map cachedSessionKeys) { + mCryptoData.putAll(cachedSessionKeys); + } + public Map getCryptoData() { - return Collections.unmodifiableMap(mCryptoData); + return mCryptoData; } public Date getSignatureTime() { @@ -138,4 +142,5 @@ public class CryptoInputParcel implements Parcelable { b.append("}"); return b.toString(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java deleted file mode 100644 index 254b926d4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.File; - -public class DecryptFilesFragment extends DecryptFragment { - public static final String ARG_URI = "uri"; - public static final String ARG_OPEN_DIRECTLY = "open_directly"; - - private static final int REQUEST_CODE_INPUT = 0x00007003; - private static final int REQUEST_CODE_OUTPUT = 0x00007007; - - // view - private TextView mFilename; - private CheckBox mDeleteAfter; - private View mDecryptButton; - - // model - private Uri mInputUri = null; - private Uri mOutputUri = null; - - private String mCurrentCryptoOperation; - - /** - * Creates new instance of this fragment - */ - public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) { - DecryptFilesFragment frag = new DecryptFilesFragment(); - - Bundle args = new Bundle(); - args.putParcelable(ARG_URI, uri); - args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); - - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false); - - mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); - mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption); - mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); - view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", - REQUEST_CODE_INPUT); - } - } - }); - mDecryptButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - decryptAction(); - } - }); - - return view; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putParcelable(ARG_URI, mInputUri); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - Bundle state = savedInstanceState != null ? savedInstanceState : getArguments(); - setInputUri(state.getParcelable(ARG_URI)); - - // should only come from args - if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); - } - } - } - - private void setInputUri(Uri inputUri) { - if (inputUri == null) { - mInputUri = null; - mFilename.setText(""); - return; - } - - mInputUri = inputUri; - mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); - } - - private void decryptAction() { - if (mInputUri == null) { - Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); - return; - } - - startDecryptFilenames(); - } - - private String removeEncryptedAppend(String name) { - if (name.endsWith(Constants.FILE_EXTENSION_ASC) - || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) - || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { - return name.substring(0, name.length() - 4); - } - return name; - } - - private void askForOutputFilename(String originalFilename) { - if (TextUtils.isEmpty(originalFilename)) { - originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(mInputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, originalFilename); - FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); - } - } - - private void startDecrypt() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY; - cryptoOperation(new CryptoInputParcel()); - } - - private void startDecryptFilenames() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA; - cryptoOperation(new CryptoInputParcel()); - } - - @Override - @SuppressLint("HandlerLeak") - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - // use current operation, either decrypt metadata or decrypt payload - intent.setAction(mCurrentCryptoOperation); - - // data - - Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) - .setAllowSymmetricDecryption(true); - - data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); - data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after decrypting is done in KeychainIntentService - ServiceProgressHandler saveHandler = new ServiceProgressHandler( - getActivity(), - getString(R.string.progress_decrypting), - ProgressDialog.STYLE_HORIZONTAL, - ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - // handle pending messages - if (handlePendingMessage(message)) { - return; - } - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - DecryptVerifyResult pgpResult = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - if (pgpResult.success()) { - switch (mCurrentCryptoOperation) { - case KeychainIntentService.ACTION_DECRYPT_METADATA: { - askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); - break; - } - case KeychainIntentService.ACTION_DECRYPT_VERIFY: { - // display signature result in activity - loadVerifyResult(pgpResult); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); - setInputUri(null); - } - - /* - // A future open after decryption feature - if () { - Intent viewFile = new Intent(Intent.ACTION_VIEW); - viewFile.setInputData(mOutputUri); - startActivity(viewFile); - } - */ - break; - } - default: { - Log.e(Constants.TAG, "Bug: not supported operation!"); - break; - } - } - } - pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this); - } - - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(getActivity()); - - // start service with intent - getActivity().startService(intent); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_INPUT: { - if (resultCode == Activity.RESULT_OK && data != null) { - setInputUri(data.getData()); - } - return; - } - - case REQUEST_CODE_OUTPUT: { - // This happens after output file was selected, so start our operation - if (resultCode == Activity.RESULT_OK && data != null) { - mOutputUri = data.getData(); - startDecrypt(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java new file mode 100644 index 000000000..3f15376cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.File; + +public class DecryptFilesInputFragment extends DecryptFragment { + public static final String ARG_URI = "uri"; + public static final String ARG_OPEN_DIRECTLY = "open_directly"; + + private static final int REQUEST_CODE_INPUT = 0x00007003; + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + // view + private TextView mFilename; + private CheckBox mDeleteAfter; + private View mDecryptButton; + + // model + private Uri mInputUri = null; + private Uri mOutputUri = null; + + private String mCurrentCryptoOperation; + + /** + * Creates new instance of this fragment + */ + public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { + DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_URI, uri); + args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); + + frag.setArguments(args); + + return frag; + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false); + + mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); + mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption); + mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); + view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); + } else { + FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", + REQUEST_CODE_INPUT); + } + } + }); + mDecryptButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + decryptAction(); + } + }); + + return view; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(ARG_URI, mInputUri); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Bundle state = savedInstanceState != null ? savedInstanceState : getArguments(); + setInputUri(state.getParcelable(ARG_URI)); + + // should only come from args + if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); + } else { + FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); + } + } + } + + private void setInputUri(Uri inputUri) { + if (inputUri == null) { + mInputUri = null; + mFilename.setText(""); + return; + } + + mInputUri = inputUri; + mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); + } + + private void decryptAction() { + if (mInputUri == null) { + Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); + return; + } + + startDecryptFilenames(); + } + + private String removeEncryptedAppend(String name) { + if (name.endsWith(Constants.FILE_EXTENSION_ASC) + || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) + || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { + return name.substring(0, name.length() - 4); + } + return name; + } + + private void askForOutputFilename(String originalFilename) { + if (TextUtils.isEmpty(originalFilename)) { + originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(mInputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, originalFilename); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); + } + } + + private void displayMetadata(DecryptVerifyResult result) { + + } + + private void startDecrypt() { + mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY; + cryptoOperation(new CryptoInputParcel()); + } + + private void startDecryptFilenames() { + mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA; + cryptoOperation(new CryptoInputParcel()); + } + + @Override + @SuppressLint("HandlerLeak") + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + // use current operation, either decrypt metadata or decrypt payload + intent.setAction(mCurrentCryptoOperation); + + // data + + Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); + + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) + .setAllowSymmetricDecryption(true); + + data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after decrypting is done in KeychainIntentService + ServiceProgressHandler saveHandler = new ServiceProgressHandler( + getActivity(), + getString(R.string.progress_decrypting), + ProgressDialog.STYLE_HORIZONTAL, + ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { + @Override + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + DecryptVerifyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + if (result.success()) { + switch (mCurrentCryptoOperation) { + case KeychainIntentService.ACTION_DECRYPT_METADATA: { + displayMetadata(result); + // askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); + break; + } + case KeychainIntentService.ACTION_DECRYPT_VERIFY: { + // display signature result in activity + loadVerifyResult(result); + + if (mDeleteAfter.isChecked()) { + // Create and show dialog to delete original file + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + setInputUri(null); + } + + /* + // A future open after decryption feature + if () { + Intent viewFile = new Intent(Intent.ACTION_VIEW); + viewFile.setInputData(mOutputUri); + startActivity(viewFile); + } + */ + break; + } + default: { + Log.e(Constants.TAG, "Bug: not supported operation!"); + break; + } + } + } + result.createNotify(getActivity()).show(DecryptFilesInputFragment.this); + } + + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_INPUT: { + if (resultCode == Activity.RESULT_OK && data != null) { + setInputUri(data.getData()); + } + return; + } + + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mOutputUri = data.getData(); + startDecrypt(); + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + protected void onVerifyLoaded(boolean hideErrorOverlay) { + + } +} -- cgit v1.2.3 From cee1a8c7531904b55d13bda972b79e8b7c7ed67c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Jun 2015 03:23:51 +0200 Subject: multi-decrypt: first steps, split up DecryptFilesFragment --- .../keychain/ui/DecryptFilesActivity.java | 19 +- .../keychain/ui/DecryptFilesInputFragment.java | 136 ++----- .../keychain/ui/DecryptFilesListFragment.java | 398 +++++++++++++++++++++ 3 files changed, 435 insertions(+), 118 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index 81fb6a392..f08ee5d3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -21,15 +21,15 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.view.View; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.util.Log; + public class DecryptFilesActivity extends BaseActivity { @@ -94,13 +94,24 @@ public class DecryptFilesActivity extends BaseActivity { } boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); - DecryptFilesFragment frag = DecryptFilesFragment.newInstance(uri, showOpenDialog); + DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(uri, showOpenDialog); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.decrypt_files_fragment_container, frag) - .commitAllowingStateLoss(); + .commit(); + + } + + public void displayListFragment(Uri inputUri, DecryptVerifyResult result) { + + DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(inputUri, result); + + getSupportFragmentManager().beginTransaction() + .replace(R.id.decrypt_files_fragment_container, frag) + .addToBackStack("list") + .commit(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 3f15376cb..25494e0d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -26,11 +26,9 @@ import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Messenger; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; @@ -40,35 +38,26 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; -import java.io.File; - -public class DecryptFilesInputFragment extends DecryptFragment { +public class DecryptFilesInputFragment extends CryptoOperationFragment { public static final String ARG_URI = "uri"; public static final String ARG_OPEN_DIRECTLY = "open_directly"; private static final int REQUEST_CODE_INPUT = 0x00007003; - private static final int REQUEST_CODE_OUTPUT = 0x00007007; // view private TextView mFilename; - private CheckBox mDeleteAfter; private View mDecryptButton; // model private Uri mInputUri = null; private Uri mOutputUri = null; - private String mCurrentCryptoOperation; - - /** - * Creates new instance of this fragment - */ public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); @@ -81,15 +70,14 @@ public class DecryptFilesInputFragment extends DecryptFragment { return frag; } - /** - * Inflate the layout for this fragment - */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false); + View view = inflater.inflate(R.layout.decrypt_files_input_fragment, container, false); + + // hide result view for this fragment + getActivity().findViewById(R.id.result_main_layout).setVisibility(View.GONE); mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); - mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption); mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -152,45 +140,6 @@ public class DecryptFilesInputFragment extends DecryptFragment { return; } - startDecryptFilenames(); - } - - private String removeEncryptedAppend(String name) { - if (name.endsWith(Constants.FILE_EXTENSION_ASC) - || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) - || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { - return name.substring(0, name.length() - 4); - } - return name; - } - - private void askForOutputFilename(String originalFilename) { - if (TextUtils.isEmpty(originalFilename)) { - originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(mInputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, originalFilename); - FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); - } - } - - private void displayMetadata(DecryptVerifyResult result) { - - } - - private void startDecrypt() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY; - cryptoOperation(new CryptoInputParcel()); - } - - private void startDecryptFilenames() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA; cryptoOperation(new CryptoInputParcel()); } @@ -203,7 +152,7 @@ public class DecryptFilesInputFragment extends DecryptFragment { // fill values for this action Bundle data = new Bundle(); // use current operation, either decrypt metadata or decrypt payload - intent.setAction(mCurrentCryptoOperation); + intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); // data @@ -240,41 +189,18 @@ public class DecryptFilesInputFragment extends DecryptFragment { DecryptVerifyResult result = returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + DecryptFilesActivity activity = ((DecryptFilesActivity) getActivity()); + if (activity == null) { + // nothing we can do + return; + } + if (result.success()) { - switch (mCurrentCryptoOperation) { - case KeychainIntentService.ACTION_DECRYPT_METADATA: { - displayMetadata(result); - // askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); - break; - } - case KeychainIntentService.ACTION_DECRYPT_VERIFY: { - // display signature result in activity - loadVerifyResult(result); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); - setInputUri(null); - } - - /* - // A future open after decryption feature - if () { - Intent viewFile = new Intent(Intent.ACTION_VIEW); - viewFile.setInputData(mOutputUri); - startActivity(viewFile); - } - */ - break; - } - default: { - Log.e(Constants.TAG, "Bug: not supported operation!"); - break; - } - } + activity.displayListFragment(mInputUri, result); + return; } - result.createNotify(getActivity()).show(DecryptFilesInputFragment.this); + result.createNotify(activity).show(DecryptFilesInputFragment.this); + } } @@ -293,31 +219,13 @@ public class DecryptFilesInputFragment extends DecryptFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_INPUT: { - if (resultCode == Activity.RESULT_OK && data != null) { - setInputUri(data.getData()); - } - return; - } - - case REQUEST_CODE_OUTPUT: { - // This happens after output file was selected, so start our operation - if (resultCode == Activity.RESULT_OK && data != null) { - mOutputUri = data.getData(); - startDecrypt(); - } - return; - } + if (requestCode != REQUEST_CODE_INPUT) { + return; + } - default: { - super.onActivityResult(requestCode, resultCode, data); - } + if (resultCode == Activity.RESULT_OK && data != null) { + setInputUri(data.getData()); } } - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java new file mode 100644 index 000000000..e86275b5d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.OpenPgpMetadata; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; +import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + + +public class DecryptFilesListFragment extends DecryptFragment { + public static final String ARG_URI = "uri"; + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + private Uri mInputUri = null; + private DecryptVerifyResult mResult; + + private Uri mOutputUri = null; + private RecyclerView mFilesList; + private DecryptFilesAdapter mAdapter; + + /** + * Creates new instance of this fragment + */ + public static DecryptFilesListFragment newInstance(Uri uri, DecryptVerifyResult result) { + DecryptFilesListFragment frag = new DecryptFilesListFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_URI, uri); + args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result); + + frag.setArguments(args); + + return frag; + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); + + mFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); + + mFilesList.addItemDecoration(new SpacesItemDecoration( + FormattingUtils.dpToPx(getActivity(), 4))); + // mFilesList.setHasFixedSize(true); + mFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); + mFilesList.setItemAnimator(new DefaultItemAnimator()); + + mAdapter = new DecryptFilesAdapter(getActivity(), new ArrayList()); + mFilesList.setAdapter(mAdapter); + + return view; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(ARG_URI, mInputUri); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Bundle state = getArguments(); + mInputUri = state.getParcelable(ARG_URI); + + if (savedInstanceState == null) { + displayMetadata(state.getParcelable(ARG_DECRYPT_VERIFY_RESULT)); + } + + } + + private String removeEncryptedAppend(String name) { + if (name.endsWith(Constants.FILE_EXTENSION_ASC) + || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) + || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { + return name.substring(0, name.length() - 4); + } + return name; + } + + private void askForOutputFilename(String originalFilename) { + if (TextUtils.isEmpty(originalFilename)) { + originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(mInputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, originalFilename); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); + } + } + + private void displayMetadata(DecryptVerifyResult result) { + loadVerifyResult(result); + + OpenPgpMetadata metadata = result.getDecryptMetadata(); + mAdapter.add(metadata); + mFilesList.requestFocus(); + + } + + @Override + @SuppressLint("HandlerLeak") + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + // use current operation, either decrypt metadata or decrypt payload + intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); + + // data + + Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); + + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) + .setAllowSymmetricDecryption(true); + + data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after decrypting is done in KeychainIntentService + ServiceProgressHandler saveHandler = new ServiceProgressHandler( + getActivity(), + getString(R.string.progress_decrypting), + ProgressDialog.STYLE_HORIZONTAL, + ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { + @Override + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + DecryptVerifyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + if (result.success()) { + // display signature result in activity + loadVerifyResult(result); + + /* + // A future open after decryption feature + if () { + Intent viewFile = new Intent(Intent.ACTION_VIEW); + viewFile.setInputData(mOutputUri); + startActivity(viewFile); + } + */ + } + result.createNotify(getActivity()).show(DecryptFilesListFragment.this); + } + + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mOutputUri = data.getData(); + // startDecrypt(); + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + protected void onVerifyLoaded(boolean hideErrorOverlay) { + + } + + public static class DecryptFilesAdapter extends RecyclerView.Adapter { + private Context mContext; + private List mDataset; + + public static class ViewModel { + OpenPgpMetadata mMetadata; + Bitmap thumbnail; + + ViewModel(Context context, OpenPgpMetadata metadata) { + mMetadata = metadata; + int px = FormattingUtils.dpToPx(context, 48); + // this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px)); + } + + // Depends on inputUri only + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViewModel viewModel = (ViewModel) o; + return !(mMetadata != null ? !mMetadata.equals(viewModel.mMetadata) + : viewModel.mMetadata != null); + } + + // Depends on inputUri only + @Override + public int hashCode() { + return mMetadata != null ? mMetadata.hashCode() : 0; + } + + @Override + public String toString() { + return mMetadata.toString(); + } + } + + // Provide a reference to the views for each data item + // Complex data items may need more than one view per item, and + // you provide access to all the views for a data item in a view holder + class ViewHolder extends RecyclerView.ViewHolder { + public TextView filename; + public TextView fileSize; + public View removeButton; + public ImageView thumbnail; + + public ViewHolder(View itemView) { + super(itemView); + filename = (TextView) itemView.findViewById(R.id.filename); + fileSize = (TextView) itemView.findViewById(R.id.filesize); + removeButton = itemView.findViewById(R.id.action_remove_file_from_list); + thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + } + } + + // Provide a suitable constructor (depends on the kind of dataset) + public DecryptFilesAdapter(Context context, List myDataset) { + mContext = context; + mDataset = myDataset; + } + + // Create new views (invoked by the layout manager) + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + //inflate your layout and pass it to view holder + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.file_list_entry, parent, false); + return new ViewHolder(v); + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { + ViewHolder thisHolder = (ViewHolder) holder; + // - get element from your dataset at this position + // - replace the contents of the view with that element + final ViewModel model = mDataset.get(position); + + thisHolder.filename.setText(model.mMetadata.getFilename()); + + long size = model.mMetadata.getOriginalSize(); + if (size == -1) { + thisHolder.fileSize.setText(""); + } else { + thisHolder.fileSize.setText(FileHelper.readableFileSize(size)); + } + thisHolder.removeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + remove(model); + } + }); + + if (model.thumbnail != null) { + thisHolder.thumbnail.setImageBitmap(model.thumbnail); + } else { + thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return mDataset.size(); + } + + public void add(OpenPgpMetadata metadata) { + ViewModel newModel = new ViewModel(mContext, metadata); + mDataset.add(newModel); + notifyItemInserted(mDataset.size()); + } + + public void addAll(ArrayList metadatas) { + if (metadatas != null) { + int startIndex = mDataset.size(); + for (OpenPgpMetadata metadata : metadatas) { + ViewModel newModel = new ViewModel(mContext, metadata); + if (mDataset.contains(newModel)) { + Log.e(Constants.TAG, "Skipped duplicate " + metadata); + } else { + mDataset.add(newModel); + } + } + notifyItemRangeInserted(startIndex, mDataset.size() - startIndex); + } + } + + public void remove(ViewModel model) { + int position = mDataset.indexOf(model); + mDataset.remove(position); + notifyItemRemoved(position); + } + + } + +} -- cgit v1.2.3 From fc9a7bfcb3175e9f6d20176c6d42b09ca25ac539 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 2 Jun 2015 12:14:27 +0200 Subject: multi-decrypt: working recyclerview which decrypt files sequentially, ui in progress --- .../keychain/ui/DecryptFilesActivity.java | 9 +- .../keychain/ui/DecryptFilesInputFragment.java | 96 +----- .../keychain/ui/DecryptFilesListFragment.java | 350 ++++++++++++--------- 3 files changed, 212 insertions(+), 243 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index f08ee5d3d..b56c38d19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; + import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -27,7 +29,6 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -104,9 +105,11 @@ public class DecryptFilesActivity extends BaseActivity { } - public void displayListFragment(Uri inputUri, DecryptVerifyResult result) { + public void displayListFragment(Uri inputUri) { - DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(inputUri, result); + ArrayList uris = new ArrayList<>(); + uris.add(inputUri); + DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(uris); getSupportFragmentManager().beginTransaction() .replace(R.id.decrypt_files_fragment_container, frag) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 25494e0d7..be00183b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -17,46 +17,31 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.SuppressLint; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; +import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.Log; -public class DecryptFilesInputFragment extends CryptoOperationFragment { +public class DecryptFilesInputFragment extends Fragment { public static final String ARG_URI = "uri"; public static final String ARG_OPEN_DIRECTLY = "open_directly"; private static final int REQUEST_CODE_INPUT = 0x00007003; - // view private TextView mFilename; private View mDecryptButton; - // model private Uri mInputUri = null; - private Uri mOutputUri = null; public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); @@ -140,81 +125,8 @@ public class DecryptFilesInputFragment extends CryptoOperationFragment { return; } - cryptoOperation(new CryptoInputParcel()); - } - - @Override - @SuppressLint("HandlerLeak") - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - // use current operation, either decrypt metadata or decrypt payload - intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); - - // data - - Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) - .setAllowSymmetricDecryption(true); - - data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); - data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after decrypting is done in KeychainIntentService - ServiceProgressHandler saveHandler = new ServiceProgressHandler( - getActivity(), - getString(R.string.progress_decrypting), - ProgressDialog.STYLE_HORIZONTAL, - ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - // handle pending messages - if (handlePendingMessage(message)) { - return; - } - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - DecryptVerifyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - DecryptFilesActivity activity = ((DecryptFilesActivity) getActivity()); - if (activity == null) { - // nothing we can do - return; - } - - if (result.success()) { - activity.displayListFragment(mInputUri, result); - return; - } - result.createNotify(activity).show(DecryptFilesInputFragment.this); - - } - - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(getActivity()); - - // start service with intent - getActivity().startService(intent); + DecryptFilesActivity activity = (DecryptFilesActivity) getActivity(); + activity.displayListFragment(mInputUri); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index e86275b5d..8768a7935 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -18,55 +18,54 @@ package org.sufficientlysecure.keychain.ui; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; -import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.net.Uri; -import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpMetadata; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; -import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; - -public class DecryptFilesListFragment extends DecryptFragment { - public static final String ARG_URI = "uri"; +public class DecryptFilesListFragment extends CryptoOperationFragment { + public static final String ARG_URIS = "uris"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; - private Uri mInputUri = null; - private DecryptVerifyResult mResult; + private ArrayList mInputUris; + private ArrayList mPendingInputUris; + private Uri mCurrentInputUri; private Uri mOutputUri = null; private RecyclerView mFilesList; @@ -75,13 +74,11 @@ public class DecryptFilesListFragment extends DecryptFragment { /** * Creates new instance of this fragment */ - public static DecryptFilesListFragment newInstance(Uri uri, DecryptVerifyResult result) { + public static DecryptFilesListFragment newInstance(ArrayList uris) { DecryptFilesListFragment frag = new DecryptFilesListFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_URI, uri); - args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result); - + args.putParcelableArrayList(ARG_URIS, uris); frag.setArguments(args); return frag; @@ -98,11 +95,11 @@ public class DecryptFilesListFragment extends DecryptFragment { mFilesList.addItemDecoration(new SpacesItemDecoration( FormattingUtils.dpToPx(getActivity(), 4))); - // mFilesList.setHasFixedSize(true); + mFilesList.setHasFixedSize(true); mFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); mFilesList.setItemAnimator(new DefaultItemAnimator()); - mAdapter = new DecryptFilesAdapter(getActivity(), new ArrayList()); + mAdapter = new DecryptFilesAdapter(getActivity()); mFilesList.setAdapter(mAdapter); return view; @@ -112,20 +109,14 @@ public class DecryptFilesListFragment extends DecryptFragment { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(ARG_URI, mInputUri); + outState.putParcelableArrayList(ARG_URIS, mInputUris); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Bundle state = getArguments(); - mInputUri = state.getParcelable(ARG_URI); - - if (savedInstanceState == null) { - displayMetadata(state.getParcelable(ARG_DECRYPT_VERIFY_RESULT)); - } - + displayInputUris(getArguments().getParcelableArrayList(ARG_URIS)); } private String removeEncryptedAppend(String name) { @@ -137,34 +128,37 @@ public class DecryptFilesListFragment extends DecryptFragment { return name; } - private void askForOutputFilename(String originalFilename) { - if (TextUtils.isEmpty(originalFilename)) { - originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(mInputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, originalFilename); - FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); + private void displayInputUris(ArrayList uris) { + mInputUris = uris; + mPendingInputUris = uris; + for (Uri uri : uris) { + mAdapter.add(uri); } + cryptoOperation(); } - private void displayMetadata(DecryptVerifyResult result) { - loadVerifyResult(result); - - OpenPgpMetadata metadata = result.getDecryptMetadata(); - mAdapter.add(metadata); - mFilesList.requestFocus(); + private void displayProgress(Uri uri, int progress, int max, String msg) { + mAdapter.setProgress(uri, progress, max, msg); + } + private void displayInputResult(Uri uri, DecryptVerifyResult result) { + mAdapter.addResult(uri, result); } @Override @SuppressLint("HandlerLeak") protected void cryptoOperation(CryptoInputParcel cryptoInput) { + + if (mCurrentInputUri == null) { + + if (mPendingInputUris.isEmpty()) { + return; + } + + mCurrentInputUri = mPendingInputUris.remove(0); + + } + // Send all information needed to service to decrypt in other thread Intent intent = new Intent(getActivity(), KeychainIntentService.class); @@ -175,9 +169,9 @@ public class DecryptFilesListFragment extends DecryptFragment { // data - Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); + Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + mOutputUri); - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, mOutputUri) .setAllowSymmetricDecryption(true); data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); @@ -186,11 +180,7 @@ public class DecryptFilesListFragment extends DecryptFragment { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after decrypting is done in KeychainIntentService - ServiceProgressHandler saveHandler = new ServiceProgressHandler( - getActivity(), - getString(R.string.progress_decrypting), - ProgressDialog.STYLE_HORIZONTAL, - ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { + Handler saveHandler = new Handler() { @Override public void handleMessage(Message message) { // handle messages by standard KeychainIntentServiceHandler first @@ -201,27 +191,47 @@ public class DecryptFilesListFragment extends DecryptFragment { return; } - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); + MessageStatus status = MessageStatus.fromInt(message.arg1); + Bundle data = message.getData(); + + switch (status) { + case UNKNOWN: + case EXCEPTION: { + Log.e(Constants.TAG, "error: " + status); + break; + } + + case UPDATE_PROGRESS: { + int progress = data.getInt(ServiceProgressHandler.DATA_PROGRESS); + int max = data.getInt(ServiceProgressHandler.DATA_PROGRESS_MAX); + String msg; + if (data.containsKey(ServiceProgressHandler.DATA_MESSAGE_ID)) { + msg = getString(data.getInt(ServiceProgressHandler.DATA_MESSAGE_ID)); + } else if (data.containsKey(ServiceProgressHandler.DATA_MESSAGE)) { + msg = data.getString(ServiceProgressHandler.DATA_MESSAGE); + } else { + msg = null; + } + displayProgress(mCurrentInputUri, progress, max, msg); + break; + } - DecryptVerifyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + case OKAY: { + // get returned data bundle + Bundle returnData = message.getData(); - if (result.success()) { - // display signature result in activity - loadVerifyResult(result); + DecryptVerifyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - /* - // A future open after decryption feature - if () { - Intent viewFile = new Intent(Intent.ACTION_VIEW); - viewFile.setInputData(mOutputUri); - startActivity(viewFile); + if (result.success()) { + // display signature result in activity + displayInputResult(mCurrentInputUri, result); + mCurrentInputUri = null; + return; } - */ + + result.createNotify(getActivity()).show(DecryptFilesListFragment.this); } - result.createNotify(getActivity()).show(DecryptFilesListFragment.this); } } @@ -231,9 +241,6 @@ public class DecryptFilesListFragment extends DecryptFragment { Messenger messenger = new Messenger(saveHandler); intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - // show progress dialog - saveHandler.showProgressDialog(getActivity()); - // start service with intent getActivity().startService(intent); } @@ -256,23 +263,39 @@ public class DecryptFilesListFragment extends DecryptFragment { } } - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - - } - - public static class DecryptFilesAdapter extends RecyclerView.Adapter { + public static class DecryptFilesAdapter extends RecyclerView.Adapter { private Context mContext; - private List mDataset; + private ArrayList mDataset; public static class ViewModel { - OpenPgpMetadata mMetadata; - Bitmap thumbnail; + Context mContext; + Uri mUri; + DecryptVerifyResult mResult; + + int mProgress, mMax; + String mProgressMsg; + + ViewModel(Context context, Uri uri) { + mContext = context; + mUri = uri; + mProgress = 0; + mMax = 100; + } + + void addResult(DecryptVerifyResult result) { + mResult = result; + } - ViewModel(Context context, OpenPgpMetadata metadata) { - mMetadata = metadata; - int px = FormattingUtils.dpToPx(context, 48); - // this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px)); + boolean hasResult() { + return mResult != null; + } + + void setProgress(int progress, int max, String msg) { + if (msg != null) { + mProgressMsg = msg; + } + mProgress = progress; + mMax = max; } // Depends on inputUri only @@ -281,83 +304,79 @@ public class DecryptFilesListFragment extends DecryptFragment { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ViewModel viewModel = (ViewModel) o; - return !(mMetadata != null ? !mMetadata.equals(viewModel.mMetadata) - : viewModel.mMetadata != null); + return !(mResult != null ? !mResult.equals(viewModel.mResult) + : viewModel.mResult != null); } // Depends on inputUri only @Override public int hashCode() { - return mMetadata != null ? mMetadata.hashCode() : 0; + return mResult != null ? mResult.hashCode() : 0; } @Override public String toString() { - return mMetadata.toString(); - } - } - - // Provide a reference to the views for each data item - // Complex data items may need more than one view per item, and - // you provide access to all the views for a data item in a view holder - class ViewHolder extends RecyclerView.ViewHolder { - public TextView filename; - public TextView fileSize; - public View removeButton; - public ImageView thumbnail; - - public ViewHolder(View itemView) { - super(itemView); - filename = (TextView) itemView.findViewById(R.id.filename); - fileSize = (TextView) itemView.findViewById(R.id.filesize); - removeButton = itemView.findViewById(R.id.action_remove_file_from_list); - thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + return mResult.toString(); } } // Provide a suitable constructor (depends on the kind of dataset) - public DecryptFilesAdapter(Context context, List myDataset) { + public DecryptFilesAdapter(Context context) { mContext = context; - mDataset = myDataset; + mDataset = new ArrayList<>(); } // Create new views (invoked by the layout manager) @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //inflate your layout and pass it to view holder View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.file_list_entry, parent, false); + .inflate(R.layout.decrypt_list_entry, parent, false); return new ViewHolder(v); } // Replace the contents of a view (invoked by the layout manager) @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { - ViewHolder thisHolder = (ViewHolder) holder; + public void onBindViewHolder(ViewHolder holder, final int position) { // - get element from your dataset at this position // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); - thisHolder.filename.setText(model.mMetadata.getFilename()); + if (model.hasResult()) { + if (holder.vAnimator.getDisplayedChild() != 1) { + holder.vAnimator.setDisplayedChild(1); + } - long size = model.mMetadata.getOriginalSize(); - if (size == -1) { - thisHolder.fileSize.setText(""); - } else { - thisHolder.fileSize.setText(FileHelper.readableFileSize(size)); - } - thisHolder.removeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - remove(model); + OpenPgpSignatureResult signature = model.mResult.getSignatureResult(); + if (signature != null) { + KeyFormattingUtils.setStatusImage(mContext, holder.vStatusIcon, holder.vStatusText, + State.VERIFIED); + holder.vStatusText.setText("Yolo!"); + holder.vSignatureName.setText(signature.getPrimaryUserId()); + } else { + KeyFormattingUtils.setStatusImage(mContext, + holder.vStatusIcon, holder.vStatusText, State.UNAVAILABLE); } - }); - if (model.thumbnail != null) { - thisHolder.thumbnail.setImageBitmap(model.thumbnail); + OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); + holder.vFilename.setText(metadata.getFilename()); + + long size = metadata.getOriginalSize(); + if (size == -1) { + holder.vFilesize.setText(""); + } else { + holder.vFilesize.setText(FileHelper.readableFileSize(size)); + } } else { - thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am); + if (holder.vAnimator.getDisplayedChild() != 0) { + holder.vAnimator.setDisplayedChild(0); + } + + holder.vProgress.setProgress(model.mProgress); + holder.vProgress.setMax(model.mMax); + holder.vProgressMsg.setText(model.mProgressMsg); } + } // Return the size of your dataset (invoked by the layout manager) @@ -366,31 +385,66 @@ public class DecryptFilesListFragment extends DecryptFragment { return mDataset.size(); } - public void add(OpenPgpMetadata metadata) { - ViewModel newModel = new ViewModel(mContext, metadata); + public void add(Uri uri) { + ViewModel newModel = new ViewModel(mContext, uri); mDataset.add(newModel); notifyItemInserted(mDataset.size()); } - public void addAll(ArrayList metadatas) { - if (metadatas != null) { - int startIndex = mDataset.size(); - for (OpenPgpMetadata metadata : metadatas) { - ViewModel newModel = new ViewModel(mContext, metadata); - if (mDataset.contains(newModel)) { - Log.e(Constants.TAG, "Skipped duplicate " + metadata); - } else { - mDataset.add(newModel); - } - } - notifyItemRangeInserted(startIndex, mDataset.size() - startIndex); - } + public void setProgress(Uri uri, int progress, int max, String msg) { + ViewModel newModel = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).setProgress(progress, max, msg); + notifyItemChanged(pos); + } + + public void addResult(Uri uri, DecryptVerifyResult result) { + ViewModel newModel = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).addResult(result); + notifyItemChanged(pos); } - public void remove(ViewModel model) { - int position = mDataset.indexOf(model); - mDataset.remove(position); - notifyItemRemoved(position); + } + + + // Provide a reference to the views for each data item + // Complex data items may need more than one view per item, and + // you provide access to all the views for a data item in a view holder + public static class ViewHolder extends RecyclerView.ViewHolder { + public ViewAnimator vAnimator; + + public ProgressBar vProgress; + public TextView vProgressMsg; + + public TextView vFilename; + public TextView vFilesize; + public View vRemoveButton; + public ImageView vThumbnail; + + public ImageView vStatusIcon; + public TextView vStatusText; + public TextView vSignatureName; + public TextView vSignatureMail; + + public ViewHolder(View itemView) { + super(itemView); + + vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator); + + vProgress = (ProgressBar) itemView.findViewById(R.id.progress); + vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); + + vFilename = (TextView) itemView.findViewById(R.id.filename); + vFilesize = (TextView) itemView.findViewById(R.id.filesize); + vRemoveButton = itemView.findViewById(R.id.action_remove_file_from_list); + vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + + vStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); + vStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); + vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); + vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); + } } -- cgit v1.2.3 From 441704f163279b45cb71c5f242df4d5c637aa34a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 2 Jun 2015 22:19:28 +0200 Subject: multi-decrypt: working in principle --- .../keychain/pgp/PgpDecryptVerify.java | 48 +++-- .../keychain/service/KeychainIntentService.java | 4 - .../keychain/ui/DecryptFilesListFragment.java | 214 ++++++++++++++++++--- .../keychain/ui/util/KeyFormattingUtils.java | 163 +++++++++++++++- 4 files changed, 375 insertions(+), 54 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 235b0a671..5b4f7a65b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -24,7 +24,6 @@ import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; -import org.spongycastle.bcpg.PublicKeyEncSessionPacket; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; @@ -674,9 +673,6 @@ public class PgpDecryptVerify extends BaseOperation { PGPLiteralData literalData = (PGPLiteralData) dataChunk; - // reported size may be null if partial packets are involved (highly unlikely though) - Long originalSize = literalData.getDataLengthIfAvailable(); - String originalFilename = literalData.getFileName(); String mimeType = null; if (literalData.getFormat() == PGPLiteralData.TEXT @@ -699,12 +695,6 @@ public class PgpDecryptVerify extends BaseOperation { } } - metadata = new OpenPgpMetadata( - originalFilename, - mimeType, - literalData.getModificationTime().getTime(), - originalSize == null ? 0 : originalSize); - if (!"".equals(originalFilename)) { log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); } @@ -712,15 +702,26 @@ public class PgpDecryptVerify extends BaseOperation { mimeType); log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, new Date(literalData.getModificationTime().getTime()).toString()); - if (originalSize != null) { - log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, - Long.toString(originalSize)); - } else { - log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); - } // return here if we want to decrypt the metadata only if (input.isDecryptMetadataOnly()) { + + // this operation skips the entire stream to find the data length! + Long originalSize = literalData.findDataLength(); + + if (originalSize != null) { + log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, + Long.toString(originalSize)); + } else { + log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); + } + + metadata = new OpenPgpMetadata( + originalFilename, + mimeType, + literalData.getModificationTime().getTime(), + originalSize == null ? 0 : originalSize); + log.add(LogType.MSG_DC_OK_META_ONLY, indent); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); @@ -769,6 +770,21 @@ public class PgpDecryptVerify extends BaseOperation { // TODO: slow annealing to fake a progress? } + // after going through the stream, size should be available + Long originalSize = literalData.getDataLengthIfAvailable(); + if (originalSize != null) { + log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, + Long.toString(originalSize)); + } else { + log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); + } + + metadata = new OpenPgpMetadata( + originalFilename, + mimeType, + literalData.getModificationTime().getTime(), + originalSize == null ? 0 : originalSize); + if (signature != null) { updateProgress(R.string.progress_verifying_signature, 90, 100); log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 5f9c98ac5..c1e4ceef5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -358,10 +358,6 @@ public class KeychainIntentService extends IntentService implements Progressable CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); PgpDecryptVerifyInputParcel input = data.getParcelable(DECRYPT_VERIFY_PARCEL); - // for compatibility - // TODO merge with ACTION_DECRYPT_METADATA - input.setDecryptMetadataOnly(false); - /* Operation */ PgpDecryptVerify op = new PgpDecryptVerify(this, new ProviderHelper(this), this); DecryptVerifyResult decryptVerifyResult = op.execute(input, cryptoInput); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index 8768a7935..bf21421a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -19,11 +19,14 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; +import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -34,6 +37,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; @@ -46,6 +50,8 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; @@ -54,7 +60,7 @@ import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; @@ -64,10 +70,12 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { private static final int REQUEST_CODE_OUTPUT = 0x00007007; private ArrayList mInputUris; + private ArrayList mOutputUris; private ArrayList mPendingInputUris; - private Uri mCurrentInputUri; - private Uri mOutputUri = null; + private Uri mCurrentInputUri, mCurrentOutputUri; + private boolean mDecryptingMetadata; + private RecyclerView mFilesList; private DecryptFilesAdapter mAdapter; @@ -130,10 +138,18 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { private void displayInputUris(ArrayList uris) { mInputUris = uris; - mPendingInputUris = uris; + mOutputUris = new ArrayList<>(uris.size()); for (Uri uri : uris) { mAdapter.add(uri); + String targetName = (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) + + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); + filenameCounter++; } + + mPendingInputUris = uris; + mDecryptingMetadata = true; + cryptoOperation(); } @@ -141,8 +157,50 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { mAdapter.setProgress(uri, progress, max, msg); } - private void displayInputResult(Uri uri, DecryptVerifyResult result) { - mAdapter.addResult(uri, result); + private void displayInputResult(final Uri uri, DecryptVerifyResult result) { + Drawable icon = null; + OnClickListener onFileClick = null, onKeyClick = null; + + if (result.success()) { + + if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { + icon = loadIcon(result.getDecryptMetadata().getMimeType()); + } + + OpenPgpSignatureResult sigResult = result.getSignatureResult(); + if (sigResult != null) { + final long keyId = sigResult.getKeyId(); + if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { + onKeyClick = new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); + getActivity().startActivity(intent); + } + }; + } + } + + if (result.success()) { + onFileClick = new OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentInputUri != null) { + return; + } + + mCurrentInputUri = uri; + mDecryptingMetadata = false; + cryptoOperation(); + } + }; + } + + } + + mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + } @Override @@ -169,10 +227,11 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { // data - Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + mOutputUri); + Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + mCurrentOutputUri); - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, mOutputUri) - .setAllowSymmetricDecryption(true); + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, mCurrentOutputUri) + .setAllowSymmetricDecryption(true) + .setDecryptMetadataOnly(true); data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); @@ -251,7 +310,7 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { - mOutputUri = data.getData(); + // mCurrentOutputUri = data.getData(); // startDecrypt(); } return; @@ -271,6 +330,10 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { Context mContext; Uri mUri; DecryptVerifyResult mResult; + Drawable mIcon; + + OnClickListener mOnFileClickListener; + OnClickListener mOnKeyClickListener; int mProgress, mMax; String mProgressMsg; @@ -286,6 +349,15 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { mResult = result; } + void addIcon(Drawable icon) { + mIcon = icon; + } + + void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { + mOnFileClickListener = onFileClick; + mOnKeyClickListener = onKeyClick; + } + boolean hasResult() { return mResult != null; } @@ -347,26 +419,28 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { holder.vAnimator.setDisplayedChild(1); } - OpenPgpSignatureResult signature = model.mResult.getSignatureResult(); - if (signature != null) { - KeyFormattingUtils.setStatusImage(mContext, holder.vStatusIcon, holder.vStatusText, - State.VERIFIED); - holder.vStatusText.setText("Yolo!"); - holder.vSignatureName.setText(signature.getPrimaryUserId()); - } else { - KeyFormattingUtils.setStatusImage(mContext, - holder.vStatusIcon, holder.vStatusText, State.UNAVAILABLE); - } + KeyFormattingUtils.setStatus(mContext, holder, model.mResult); OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); holder.vFilename.setText(metadata.getFilename()); long size = metadata.getOriginalSize(); - if (size == -1) { + if (size == -1 || size == 0) { holder.vFilesize.setText(""); } else { holder.vFilesize.setText(FileHelper.readableFileSize(size)); } + + // TODO thumbnail from OpenPgpMetadata + if (model.mIcon != null) { + holder.vThumbnail.setImageDrawable(model.mIcon); + } else { + holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + + holder.vFile.setOnClickListener(model.mOnFileClickListener); + holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + } else { if (holder.vAnimator.getDisplayedChild() != 0) { holder.vAnimator.setDisplayedChild(0); @@ -398,10 +472,19 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { notifyItemChanged(pos); } - public void addResult(Uri uri, DecryptVerifyResult result) { - ViewModel newModel = new ViewModel(mContext, uri); - int pos = mDataset.indexOf(newModel); - mDataset.get(pos).addResult(result); + public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, + OnClickListener onFileClick, OnClickListener onKeyClick) { + + ViewModel model = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(model); + model = mDataset.get(pos); + + model.addResult(result); + if (icon != null) { + model.addIcon(icon); + } + model.setOnClickListeners(onFileClick, onKeyClick); + notifyItemChanged(pos); } @@ -411,21 +494,26 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder - public static class ViewHolder extends RecyclerView.ViewHolder { + public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder { public ViewAnimator vAnimator; public ProgressBar vProgress; public TextView vProgressMsg; + public View vFile; public TextView vFilename; public TextView vFilesize; - public View vRemoveButton; public ImageView vThumbnail; - public ImageView vStatusIcon; - public TextView vStatusText; + public ImageView vEncStatusIcon; + public TextView vEncStatusText; + + public ImageView vSigStatusIcon; + public TextView vSigStatusText; + public View vSignatureLayout; public TextView vSignatureName; public TextView vSignatureMail; + public TextView vSignatureAction; public ViewHolder(View itemView) { super(itemView); @@ -435,17 +523,79 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { vProgress = (ProgressBar) itemView.findViewById(R.id.progress); vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); + vFile = itemView.findViewById(R.id.file); vFilename = (TextView) itemView.findViewById(R.id.filename); vFilesize = (TextView) itemView.findViewById(R.id.filesize); - vRemoveButton = itemView.findViewById(R.id.action_remove_file_from_list); vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); - vStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); - vStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); + vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); + vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); + + vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); + vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); + vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); + vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + + } + + @Override + public ImageView getEncryptionStatusIcon() { + return vEncStatusIcon; + } + + @Override + public TextView getEncryptionStatusText() { + return vEncStatusText; + } + + @Override + public ImageView getSignatureStatusIcon() { + return vSigStatusIcon; + } + + @Override + public TextView getSignatureStatusText() { + return vSigStatusText; + } + + @Override + public View getSignatureLayout() { + return vSignatureLayout; + } + + @Override + public TextView getSignatureAction() { + return vSignatureAction; + } + + @Override + public TextView getSignatureUserName() { + return vSignatureName; + } + + @Override + public TextView getSignatureUserEmail() { + return vSignatureMail; + } + + @Override + public boolean hasEncrypt() { + return true; + } + } + + private Drawable loadIcon(String mimeType) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType(mimeType); + final List matches = getActivity() + .getPackageManager().queryIntentActivities(intent, 0); + for (ResolveInfo match : matches) { + return match.loadIcon(getActivity().getPackageManager()); } + return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index dd85a6e46..11524aa08 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -24,9 +24,11 @@ import android.graphics.PorterDuff; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; +import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.nist.NISTNamedCurves; import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; @@ -34,6 +36,8 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; @@ -376,7 +380,6 @@ public class KeyFormattingUtils { /** * Converts the given bytes to a unique RGB color using SHA1 algorithm * - * @param bytes * @return an integer array containing 3 numeric color representations (Red, Green, Black) * @throws java.security.NoSuchAlgorithmException * @throws java.security.DigestException @@ -394,7 +397,7 @@ public class KeyFormattingUtils { public static final int DEFAULT_COLOR = -1; - public static enum State { + public enum State { REVOKED, EXPIRED, VERIFIED, @@ -420,9 +423,165 @@ public class KeyFormattingUtils { setStatusImage(context, statusIcon, statusText, state, color, false); } + public interface StatusHolder { + ImageView getEncryptionStatusIcon(); + TextView getEncryptionStatusText(); + + ImageView getSignatureStatusIcon(); + TextView getSignatureStatusText(); + + View getSignatureLayout(); + TextView getSignatureUserName(); + TextView getSignatureUserEmail(); + TextView getSignatureAction(); + + boolean hasEncrypt(); + + } + + @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated + public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) { + + OpenPgpSignatureResult signatureResult = result.getSignatureResult(); + + if (holder.hasEncrypt()) { + int encText, encIcon, encColor; + if (signatureResult != null && signatureResult.isSignatureOnly()) { + encIcon = R.drawable.status_lock_open_24dp; + encText = R.string.decrypt_result_not_encrypted; + encColor = R.color.android_red_light; + } else { + encIcon = R.drawable.status_lock_closed_24dp; + encText = R.string.decrypt_result_encrypted; + encColor = R.color.android_green_light; + } + + int encColorRes = context.getResources().getColor(encColor); + holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon)); + holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN); + holder.getEncryptionStatusText().setText(encText); + holder.getEncryptionStatusText().setTextColor(encColorRes); + } + + int sigText, sigIcon, sigColor; + int sigActionText, sigActionIcon; + + if (signatureResult == null) { + + sigText = R.string.decrypt_result_no_signature; + sigIcon = R.drawable.status_signature_invalid_cutout_24dp; + sigColor = R.color.bg_gray; + + // won't be used, but makes compiler happy + sigActionText = 0; + sigActionIcon = 0; + + } else switch (signatureResult.getStatus()) { + + case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { + sigText = R.string.decrypt_result_signature_certified; + sigIcon = R.drawable.status_signature_verified_cutout_24dp; + sigColor = R.color.android_green_light; + + sigActionText = R.string.decrypt_result_action_show; + sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; + break; + } + + case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { + sigText = R.string.decrypt_result_signature_uncertified; + sigIcon = R.drawable.status_signature_unverified_cutout_24dp; + sigColor = R.color.android_orange_light; + + sigActionText = R.string.decrypt_result_action_show; + sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; + break; + } + + case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { + sigText = R.string.decrypt_result_signature_revoked_key; + sigIcon = R.drawable.status_signature_revoked_cutout_24dp; + sigColor = R.color.android_red_light; + + sigActionText = R.string.decrypt_result_action_show; + sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; + break; + } + + case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { + sigText = R.string.decrypt_result_signature_expired_key; + sigIcon = R.drawable.status_signature_expired_cutout_24dp; + sigColor = R.color.android_red_light; + + sigActionText = R.string.decrypt_result_action_show; + sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; + break; + } + + case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { + sigText = R.string.decrypt_result_signature_missing_key; + sigIcon = R.drawable.status_signature_unknown_cutout_24dp; + sigColor = R.color.android_red_light; + + sigActionText = R.string.decrypt_result_action_Lookup; + sigActionIcon = R.drawable.ic_file_download_grey_24dp; + break; + } + + default: + case OpenPgpSignatureResult.SIGNATURE_ERROR: { + sigText = R.string.decrypt_result_invalid_signature; + sigIcon = R.drawable.status_signature_invalid_cutout_24dp; + sigColor = R.color.android_red_light; + + sigActionText = R.string.decrypt_result_action_show; + sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; + break; + } + + } + + int sigColorRes = context.getResources().getColor(sigColor); + holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); + holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); + holder.getSignatureStatusText().setText(sigText); + holder.getSignatureStatusText().setTextColor(sigColorRes); + + if (signatureResult != null) { + + holder.getSignatureLayout().setVisibility(View.VISIBLE); + + holder.getSignatureAction().setText(sigActionText); + holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds( + 0, 0, sigActionIcon, 0); + + String userId = signatureResult.getPrimaryUserId(); + KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); + if (userIdSplit.name != null) { + holder.getSignatureUserName().setText(userIdSplit.name); + } else { + holder.getSignatureUserName().setText(R.string.user_id_no_name); + } + if (userIdSplit.email != null) { + holder.getSignatureUserEmail().setVisibility(View.VISIBLE); + holder.getSignatureUserEmail().setText(userIdSplit.email); + } else { + holder.getSignatureUserEmail().setVisibility(View.GONE); + } + + } else { + + holder.getSignatureLayout().setVisibility(View.GONE); + + } + + + } + /** * Sets status image based on constant */ + @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21 public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, State state, int color, boolean big) { switch (state) { -- cgit v1.2.3 From 5496b42fe6c0250859d6d1341d8b6333197a3a80 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 3 Jun 2015 02:04:43 +0200 Subject: get back to a working state --- .../sufficientlysecure/keychain/ui/DecryptFilesListFragment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index bf21421a1..72f6d9d03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -138,13 +138,15 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { private void displayInputUris(ArrayList uris) { mInputUris = uris; - mOutputUris = new ArrayList<>(uris.size()); + // mOutputUris = new ArrayList<>(uris.size()); for (Uri uri : uris) { mAdapter.add(uri); + /* String targetName = (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); filenameCounter++; + */ } mPendingInputUris = uris; @@ -230,8 +232,8 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + mCurrentOutputUri); PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, mCurrentOutputUri) - .setAllowSymmetricDecryption(true) - .setDecryptMetadataOnly(true); + // .setDecryptMetadataOnly(true) + .setAllowSymmetricDecryption(true); data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); -- cgit v1.2.3 From 6db9de221cea05497b51d6d7f7eaa238dcd93485 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 3 Jun 2015 13:59:38 +0200 Subject: multi-decrypt: working context menu --- .../keychain/ui/DecryptFilesListFragment.java | 74 ++++++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index 72f6d9d03..97e1412b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -35,11 +35,17 @@ import android.os.Messenger; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnDismissListener; +import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.ViewAnimator; @@ -51,7 +57,6 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; @@ -61,10 +66,12 @@ import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; -public class DecryptFilesListFragment extends CryptoOperationFragment { +public class DecryptFilesListFragment extends CryptoOperationFragment implements OnMenuItemClickListener { public static final String ARG_URIS = "uris"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; @@ -107,7 +114,7 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { mFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); mFilesList.setItemAnimator(new DefaultItemAnimator()); - mAdapter = new DecryptFilesAdapter(getActivity()); + mAdapter = new DecryptFilesAdapter(getActivity(), this); mFilesList.setAdapter(mAdapter); return view; @@ -306,6 +313,11 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { getActivity().startService(intent); } + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -324,11 +336,40 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { } } + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { + return false; + } + Activity activity = getActivity(); + if (activity == null) { + return false; + } + + DecryptVerifyResult result = mAdapter.mMenuClickedModel.mResult; + switch (menuItem.getItemId()) { + case R.id.view_log: + Intent intent = new Intent(activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); + activity.startActivity(intent); + return true; + case R.id.decrypt_save: + Notify.create(activity, "decrypt/save not yet implemented", Style.ERROR).show(this); + return true; + case R.id.decrypt_delete: + Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); + return true; + } + return false; + } + public static class DecryptFilesAdapter extends RecyclerView.Adapter { private Context mContext; private ArrayList mDataset; + private OnMenuItemClickListener mMenuItemClickListener; + private ViewModel mMenuClickedModel; - public static class ViewModel { + public class ViewModel { Context mContext; Uri mUri; DecryptVerifyResult mResult; @@ -395,8 +436,9 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { } // Provide a suitable constructor (depends on the kind of dataset) - public DecryptFilesAdapter(Context context) { + public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) { mContext = context; + mMenuItemClickListener = menuItemClickListener; mDataset = new ArrayList<>(); } @@ -443,6 +485,24 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { holder.vFile.setOnClickListener(model.mOnFileClickListener); holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + holder.vContextMenu.setTag(model); + holder.vContextMenu.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + mMenuClickedModel = model; + PopupMenu menu = new PopupMenu(mContext, view); + menu.inflate(R.menu.decrypt_item_context_menu); + menu.setOnMenuItemClickListener(mMenuItemClickListener); + menu.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(PopupMenu popupMenu) { + mMenuClickedModel = null; + } + }); + menu.show(); + } + }); + } else { if (holder.vAnimator.getDisplayedChild() != 0) { holder.vAnimator.setDisplayedChild(0); @@ -517,6 +577,8 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { public TextView vSignatureMail; public TextView vSignatureAction; + public View vContextMenu; + public ViewHolder(View itemView) { super(itemView); @@ -540,6 +602,8 @@ public class DecryptFilesListFragment extends CryptoOperationFragment { vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + vContextMenu = itemView.findViewById(R.id.context_menu); + } @Override -- cgit v1.2.3 From fa614383065a22269c5ca249819c46b82e20a194 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 3 Jun 2015 17:01:29 +0200 Subject: multi-decrypt: implement save operation (missing actual copy) --- .../provider/TemporaryStorageProvider.java | 5 + .../keychain/ui/DecryptFilesListFragment.java | 126 +++++++++++++-------- 2 files changed, 81 insertions(+), 50 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index 45f806960..a3d00a9a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -56,6 +56,11 @@ public class TemporaryStorageProvider extends ContentProvider { return context.getContentResolver().insert(BASE_URI, contentValues); } + public static Uri createFile(Context context) { + ContentValues contentValues = new ContentValues(); + return context.getContentResolver().insert(BASE_URI, contentValues); + } + public static int cleanUp(Context context) { return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?", new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index 97e1412b3..b89b8a226 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -18,7 +18,9 @@ package org.sufficientlysecure.keychain.ui; +import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import android.annotation.SuppressLint; @@ -28,6 +30,7 @@ import android.content.Intent; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -57,10 +60,12 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -77,13 +82,11 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements private static final int REQUEST_CODE_OUTPUT = 0x00007007; private ArrayList mInputUris; - private ArrayList mOutputUris; + private HashMap mOutputUris; private ArrayList mPendingInputUris; - private Uri mCurrentInputUri, mCurrentOutputUri; - private boolean mDecryptingMetadata; + private Uri mCurrentInputUri; - private RecyclerView mFilesList; private DecryptFilesAdapter mAdapter; /** @@ -106,16 +109,16 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); - mFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); + RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); - mFilesList.addItemDecoration(new SpacesItemDecoration( + vFilesList.addItemDecoration(new SpacesItemDecoration( FormattingUtils.dpToPx(getActivity(), 4))); - mFilesList.setHasFixedSize(true); - mFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); - mFilesList.setItemAnimator(new DefaultItemAnimator()); + vFilesList.setHasFixedSize(true); + vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); + vFilesList.setItemAnimator(new DefaultItemAnimator()); mAdapter = new DecryptFilesAdapter(getActivity(), this); - mFilesList.setAdapter(mAdapter); + vFilesList.setAdapter(mAdapter); return view; } @@ -143,21 +146,48 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements return name; } + private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(inputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, originalFilename); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + Uri saveUri = data.getData(); + Uri outputUri = mOutputUris.get(mCurrentInputUri); + // TODO save from outputUri to saveUri + + mCurrentInputUri = null; + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + private void displayInputUris(ArrayList uris) { mInputUris = uris; - // mOutputUris = new ArrayList<>(uris.size()); + mOutputUris = new HashMap<>(uris.size()); for (Uri uri : uris) { mAdapter.add(uri); - /* - String targetName = (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) - + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); - mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); - filenameCounter++; - */ + mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); } mPendingInputUris = uris; - mDecryptingMetadata = true; cryptoOperation(); } @@ -183,25 +213,32 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements onKeyClick = new OnClickListener() { @Override public void onClick(View view) { - Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + Activity activity = getActivity(); + if (activity == null) { + return; + } + Intent intent = new Intent(activity, ViewKeyActivity.class); intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); - getActivity().startActivity(intent); + activity.startActivity(intent); } }; } } - if (result.success()) { + if (result.success() && result.getDecryptMetadata() != null) { + final OpenPgpMetadata metadata = result.getDecryptMetadata(); onFileClick = new OnClickListener() { @Override public void onClick(View view) { - if (mCurrentInputUri != null) { + Activity activity = getActivity(); + if (activity == null || mCurrentInputUri != null) { return; } - mCurrentInputUri = uri; - mDecryptingMetadata = false; - cryptoOperation(); + Uri outputUri = mOutputUris.get(uri); + Intent intent = new Intent(); + intent.setDataAndType(outputUri, metadata.getMimeType()); + activity.startActivity(intent); } }; } @@ -236,10 +273,10 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements // data - Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + mCurrentOutputUri); + Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); + Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, mCurrentOutputUri) - // .setDecryptMetadataOnly(true) + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) .setAllowSymmetricDecryption(true); data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); @@ -318,24 +355,6 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements super.onCreateContextMenu(menu, v, menuInfo); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_OUTPUT: { - // This happens after output file was selected, so start our operation - if (resultCode == Activity.RESULT_OK && data != null) { - // mCurrentOutputUri = data.getData(); - // startDecrypt(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - @Override public boolean onMenuItemClick(MenuItem menuItem) { if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { @@ -346,7 +365,8 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements return false; } - DecryptVerifyResult result = mAdapter.mMenuClickedModel.mResult; + ViewModel model = mAdapter.mMenuClickedModel; + DecryptVerifyResult result = model.mResult; switch (menuItem.getItemId()) { case R.id.view_log: Intent intent = new Intent(activity, LogDisplayActivity.class); @@ -354,7 +374,12 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements activity.startActivity(intent); return true; case R.id.decrypt_save: - Notify.create(activity, "decrypt/save not yet implemented", Style.ERROR).show(this); + OpenPgpMetadata metadata = result.getDecryptMetadata(); + if (metadata == null) { + return true; + } + mCurrentInputUri = model.mInputUri; + askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); return true; case R.id.decrypt_delete: Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); @@ -371,7 +396,7 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements public class ViewModel { Context mContext; - Uri mUri; + Uri mInputUri; DecryptVerifyResult mResult; Drawable mIcon; @@ -383,7 +408,7 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements ViewModel(Context context, Uri uri) { mContext = context; - mUri = uri; + mInputUri = uri; mProgress = 0; mMax = 100; } @@ -658,6 +683,7 @@ public class DecryptFilesListFragment extends CryptoOperationFragment implements final List matches = getActivity() .getPackageManager().queryIntentActivities(intent, 0); + //noinspection LoopStatementThatDoesntLoop for (ResolveInfo match : matches) { return match.loadIcon(getActivity().getPackageManager()); } -- cgit v1.2.3 From d43671b2ed42dba8c0ab8c43a5d171bf842e2782 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 7 Jun 2015 02:00:14 +0200 Subject: multi-decrypt: fix progress with new CryptoFragment interfaces --- .../keychain/ui/DecryptFilesListFragment.java | 189 +++++++-------------- .../keychain/ui/base/CryptoOperationFragment.java | 23 +-- 2 files changed, 73 insertions(+), 139 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index 4fa400638..92790b98c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -32,9 +31,6 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -61,10 +57,6 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; @@ -78,7 +70,7 @@ import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; public class DecryptFilesListFragment - extends CryptoOperationFragment + extends CryptoOperationFragment implements OnMenuItemClickListener { public static final String ARG_URIS = "uris"; @@ -195,57 +187,77 @@ public class DecryptFilesListFragment cryptoOperation(); } - private void displayProgress(Uri uri, int progress, int max, String msg) { - mAdapter.setProgress(uri, progress, max, msg); + @Override + protected void onCryptoSetProgress(String msg, int progress, int max) { + mAdapter.setProgress(mCurrentInputUri, progress, max, msg); } - private void displayInputResult(final Uri uri, DecryptVerifyResult result) { - Drawable icon = null; - OnClickListener onFileClick = null, onKeyClick = null; + @Override + public void showProgressFragment( + String progressDialogMessage, int progressDialogStyle, boolean cancelable) { + // progress shown inline, so never mind + } - if (result.success()) { + @Override + protected void dismissProgress() { + // progress shown inline, so never mind + } - if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { - icon = loadIcon(result.getDecryptMetadata().getMimeType()); - } + @Override + protected void onCryptoOperationError(DecryptVerifyResult result) { + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; - OpenPgpSignatureResult sigResult = result.getSignatureResult(); - if (sigResult != null) { - final long keyId = sigResult.getKeyId(); - if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { - onKeyClick = new OnClickListener() { - @Override - public void onClick(View view) { - Activity activity = getActivity(); - if (activity == null) { - return; - } - Intent intent = new Intent(activity, ViewKeyActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); - activity.startActivity(intent); - } - }; - } - } + mAdapter.addResult(uri, result, null, null, null); + } - if (result.success() && result.getDecryptMetadata() != null) { - final OpenPgpMetadata metadata = result.getDecryptMetadata(); - onFileClick = new OnClickListener() { + @Override + protected void onCryptoOperationSuccess(DecryptVerifyResult result) { + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; + + Drawable icon = null; + OnClickListener onFileClick = null, onKeyClick = null; + + if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { + icon = loadIcon(result.getDecryptMetadata().getMimeType()); + } + + OpenPgpSignatureResult sigResult = result.getSignatureResult(); + if (sigResult != null) { + final long keyId = sigResult.getKeyId(); + if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { + onKeyClick = new OnClickListener() { @Override public void onClick(View view) { Activity activity = getActivity(); - if (activity == null || mCurrentInputUri != null) { + if (activity == null) { return; } - - Uri outputUri = mOutputUris.get(uri); - Intent intent = new Intent(); - intent.setDataAndType(outputUri, metadata.getMimeType()); + Intent intent = new Intent(activity, ViewKeyActivity.class); + intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); activity.startActivity(intent); } }; } + } + + if (result.success() && result.getDecryptMetadata() != null) { + final OpenPgpMetadata metadata = result.getDecryptMetadata(); + onFileClick = new OnClickListener() { + @Override + public void onClick(View view) { + Activity activity = getActivity(); + if (activity == null || mCurrentInputUri != null) { + return; + } + Uri outputUri = mOutputUris.get(uri); + Intent intent = new Intent(); + intent.setDataAndType(outputUri, metadata.getMimeType()); + activity.startActivity(intent); + } + }; } mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); @@ -253,104 +265,23 @@ public class DecryptFilesListFragment } @Override - @SuppressLint("HandlerLeak") - protected void cryptoOperation(CryptoInputParcel cryptoInput) { + protected PgpDecryptVerifyInputParcel createOperationInput() { if (mCurrentInputUri == null) { - if (mPendingInputUris.isEmpty()) { - return; + // nothing left to do + return null; } mCurrentInputUri = mPendingInputUris.remove(0); - } - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - - // fill values for this action - Bundle data = new Bundle(); - // use current operation, either decrypt metadata or decrypt payload - intent.setAction(KeychainService.ACTION_DECRYPT_VERIFY); - - // data - Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) + return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) .setAllowSymmetricDecryption(true); - data.putParcelable(KeychainService.DECRYPT_VERIFY_PARCEL, input); - data.putParcelable(KeychainService.EXTRA_CRYPTO_INPUT, cryptoInput); - - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Message is received after decrypting is done in KeychainIntentService - Handler saveHandler = new Handler() { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - // handle pending messages - if (handlePendingMessage(message)) { - return; - } - - MessageStatus status = MessageStatus.fromInt(message.arg1); - Bundle data = message.getData(); - - switch (status) { - case UNKNOWN: - case EXCEPTION: { - Log.e(Constants.TAG, "error: " + status); - break; - } - - case UPDATE_PROGRESS: { - int progress = data.getInt(ServiceProgressHandler.DATA_PROGRESS); - int max = data.getInt(ServiceProgressHandler.DATA_PROGRESS_MAX); - String msg; - if (data.containsKey(ServiceProgressHandler.DATA_MESSAGE_ID)) { - msg = getString(data.getInt(ServiceProgressHandler.DATA_MESSAGE_ID)); - } else if (data.containsKey(ServiceProgressHandler.DATA_MESSAGE)) { - msg = data.getString(ServiceProgressHandler.DATA_MESSAGE); - } else { - msg = null; - } - displayProgress(mCurrentInputUri, progress, max, msg); - break; - } - - case OKAY: { - // get returned data bundle - Bundle returnData = message.getData(); - - DecryptVerifyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - if (result.success()) { - // display signature result in activity - displayInputResult(mCurrentInputUri, result); - mCurrentInputUri = null; - return; - } - - result.createNotify(getActivity()).show(DecryptFilesListFragment.this); - } - } - - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // start service with intent - getActivity().startService(intent); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 407904369..eb8b7cf3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -193,6 +193,18 @@ public abstract class CryptoOperationFragment Date: Wed, 10 Jun 2015 17:39:06 +0200 Subject: fix progress for decrypt --- .../keychain/service/ServiceProgressHandler.java | 30 +++++++++++++++++----- .../keychain/ui/DecryptFilesListFragment.java | 9 ++----- .../keychain/ui/base/CryptoOperationFragment.java | 12 +++++++++ 3 files changed, 37 insertions(+), 14 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 76aa1a618..8b90e41bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java @@ -128,17 +128,18 @@ public class ServiceProgressHandler extends Handler { case UPDATE_PROGRESS: if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { + String msg = null; + int progress = data.getInt(DATA_PROGRESS), max =data.getInt(DATA_PROGRESS_MAX); + // update progress from service if (data.containsKey(DATA_MESSAGE)) { - progressDialogFragment.setProgress(data.getString(DATA_MESSAGE), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); + msg = data.getString(DATA_MESSAGE); } else if (data.containsKey(DATA_MESSAGE_ID)) { - progressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); - } else { - progressDialogFragment.setProgress(data.getInt(DATA_PROGRESS), - data.getInt(DATA_PROGRESS_MAX)); + msg = mActivity.getString(data.getInt(DATA_MESSAGE_ID)); } + + onSetProgress(msg, progress, max); + } break; @@ -152,4 +153,19 @@ public class ServiceProgressHandler extends Handler { break; } } + + protected void onSetProgress(String msg, int progress, int max) { + + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (msg != null) { + progressDialogFragment.setProgress(msg, progress, max); + } else { + progressDialogFragment.setProgress(progress, max); + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index 92790b98c..d3b52fe78 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -188,14 +188,9 @@ public class DecryptFilesListFragment } @Override - protected void onCryptoSetProgress(String msg, int progress, int max) { + protected boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); - } - - @Override - public void showProgressFragment( - String progressDialogMessage, int progressDialogStyle, boolean cancelable) { - // progress shown inline, so never mind + return true; } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index e21589bcd..0bba2f964 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -155,6 +155,14 @@ public abstract class CryptoOperationFragment Date: Thu, 11 Jun 2015 18:30:39 +0200 Subject: add ToolableViewAnimator --- .../keychain/ui/DecryptFilesInputFragment.java | 1 + .../keychain/ui/widget/ToolableViewAnimator.java | 61 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index be00183b2..5b0b191e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser * * 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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java new file mode 100644 index 000000000..44f2b7c3e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.sufficientlysecure.keychain.ui.widget; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.R; + + +/** This view is essentially identical to ViewAnimator, but allows specifying the initial view + * for preview as an xml attribute. */ +public class ToolableViewAnimator extends ViewAnimator { + + private int mInitChild = -1; + + public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs); + + if (isInEditMode()) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator, defStyleAttr, 0); + mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1); + a.recycle(); + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (isInEditMode() && mInitChild-- > 0) { + return; + } + super.addView(child, index, params); + } +} -- cgit v1.2.3 From 3b4ff4644daa0f1d815d1f5599c72f34430d0df6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 18 Jun 2015 02:09:50 +0200 Subject: fix small oversight in ToolableViewAnimator --- .../keychain/ui/widget/ToolableViewAnimator.java | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java index 44f2b7c3e..f0d7ae5ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -41,6 +41,14 @@ public class ToolableViewAnimator extends ViewAnimator { private int mInitChild = -1; + public ToolableViewAnimator(Context context) { + super(context); + } + + public ToolableViewAnimator(Context context, AttributeSet attrs) { + super(context, attrs); + } + public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs); -- cgit v1.2.3 From 870ca3f5c3145ab71ccdf98133f092dfe1566cab Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 18 Jun 2015 02:11:32 +0200 Subject: minor code style fixes --- .../sufficientlysecure/keychain/service/ServiceProgressHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 8b90e41bf..701bfc053 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.service; + import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; @@ -129,7 +130,8 @@ public class ServiceProgressHandler extends Handler { if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { String msg = null; - int progress = data.getInt(DATA_PROGRESS), max =data.getInt(DATA_PROGRESS_MAX); + int progress = data.getInt(DATA_PROGRESS); + int max = data.getInt(DATA_PROGRESS_MAX); // update progress from service if (data.containsKey(DATA_MESSAGE)) { -- cgit v1.2.3 From f978aca8e587e4b9cd0574cac6f4bc7cc3d9fef9 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 18 Jun 2015 03:09:34 +0200 Subject: fix typo in excluder activity name --- .../java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index d290c57a6..3b972a1ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -542,7 +542,7 @@ public class EncryptFilesFragment // we don't want to encrypt the encrypted, no inception ;) String[] blacklist = new String[]{ - Constants.PACKAGE_NAME + ".ui.EncryptFileActivity", + Constants.PACKAGE_NAME + ".ui.EncryptFilesActivity", "org.thialfihar.android.apg.ui.EncryptActivity" }; -- cgit v1.2.3 From c11fef6e7c80681ce69e5fdc7f4796b0b7a18e2b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 18 Jun 2015 03:10:59 +0200 Subject: handle multiple input URIs and Intent.SEND_MULTIPLE --- .../keychain/ui/DecryptFilesActivity.java | 93 ++++++++++++++-------- .../keychain/ui/DecryptFilesInputFragment.java | 10 ++- .../keychain/ui/DecryptFilesListFragment.java | 10 +++ .../keychain/ui/base/CryptoOperationFragment.java | 20 +++-- 4 files changed, 91 insertions(+), 42 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index b56c38d19..672015aaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -23,6 +23,8 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.view.View; import android.widget.Toast; @@ -65,56 +67,83 @@ public class DecryptFilesActivity extends BaseActivity { * Handles all actions with this intent */ private void handleActions(Bundle savedInstanceState, Intent intent) { - String action = intent.getAction(); - String type = intent.getType(); - Uri uri = intent.getData(); - - if (Intent.ACTION_SEND.equals(action) && type != null) { - // When sending to Keychain Decrypt via share menu - // Binary via content provider (could also be files) - // override uri to get stream from send - uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); - action = ACTION_DECRYPT_DATA; - } else if (Intent.ACTION_VIEW.equals(action)) { - // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) - - // override action - action = ACTION_DECRYPT_DATA; - } - // No need to initialize fragments if we are being restored + // No need to initialize fragments if we are just being restored if (savedInstanceState != null) { return; } - // Definitely need a data uri with the decrypt_data intent - if (ACTION_DECRYPT_DATA.equals(action) && uri == null) { - Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); - setResult(Activity.RESULT_CANCELED); - finish(); + ArrayList uris = new ArrayList<>(); + + String action = intent.getAction(); + + switch (action) { + case Intent.ACTION_SEND: { + // When sending to Keychain Decrypt via share menu + // Binary via content provider (could also be files) + // override uri to get stream from send + action = ACTION_DECRYPT_DATA; + uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); + break; + } + + case Intent.ACTION_SEND_MULTIPLE: { + action = ACTION_DECRYPT_DATA; + uris.addAll(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)); + break; + } + + case Intent.ACTION_VIEW: + // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) + action = ACTION_DECRYPT_DATA; + + // fallthrough + default: + uris.add(intent.getData()); + + } + + if (ACTION_DECRYPT_DATA.equals(action)) { + // Definitely need a data uri with the decrypt_data intent + if (uris.isEmpty()) { + Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); + setResult(Activity.RESULT_CANCELED); + finish(); + } + displayListFragment(uris); + return; } boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); - DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(uri, showOpenDialog); + displayInputFragment(showOpenDialog); + + } + + public void displayInputFragment(boolean showOpenDialog) { + DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(showOpenDialog); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.decrypt_files_fragment_container, frag) .commit(); - } - public void displayListFragment(Uri inputUri) { + public void displayListFragment(ArrayList inputUris) { - ArrayList uris = new ArrayList<>(); - uris.add(inputUri); - DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(uris); + DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(inputUris); - getSupportFragmentManager().beginTransaction() - .replace(R.id.decrypt_files_fragment_container, frag) - .addToBackStack("list") - .commit(); + FragmentManager fragMan = getSupportFragmentManager(); + + FragmentTransaction trans = fragMan.beginTransaction(); + trans.replace(R.id.decrypt_files_fragment_container, frag); + + // if there already is a fragment, allow going back to that. otherwise, we're top level! + if (fragMan.getFragments() != null && !fragMan.getFragments().isEmpty()) { + trans.addToBackStack("list"); + } + + trans.commit(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 5b0b191e0..2b9219f49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -18,6 +18,8 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; + import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -44,11 +46,10 @@ public class DecryptFilesInputFragment extends Fragment { private Uri mInputUri = null; - public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { + public static DecryptFilesInputFragment newInstance(boolean openDirectly) { DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_URI, uri); args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); frag.setArguments(args); @@ -127,7 +128,10 @@ public class DecryptFilesInputFragment extends Fragment { } DecryptFilesActivity activity = (DecryptFilesActivity) getActivity(); - activity.displayListFragment(mInputUri); + + ArrayList uris = new ArrayList<>(); + uris.add(mInputUri); + activity.displayListFragment(uris); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java index d3b52fe78..35f534c90 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; @@ -187,6 +188,11 @@ public class DecryptFilesListFragment cryptoOperation(); } + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + super.cryptoOperation(cryptoInput, false); + } + @Override protected boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); @@ -204,6 +210,8 @@ public class DecryptFilesListFragment mCurrentInputUri = null; mAdapter.addResult(uri, result, null, null, null); + + cryptoOperation(); } @Override @@ -257,6 +265,8 @@ public class DecryptFilesListFragment mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + cryptoOperation(); + } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 5f1097588..764602735 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -123,6 +123,14 @@ public abstract class CryptoOperationFragment Date: Thu, 18 Jun 2015 03:11:29 +0200 Subject: fix ToolableViewAnimator (againl) --- .../keychain/ui/widget/ToolableViewAnimator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java index f0d7ae5ac..18e830139 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -27,6 +27,7 @@ package org.sufficientlysecure.keychain.ui.widget; import android.content.Context; import android.content.res.TypedArray; +import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -47,6 +48,12 @@ public class ToolableViewAnimator extends ViewAnimator { public ToolableViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); + + if (isInEditMode()) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator); + mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1); + a.recycle(); + } } public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { @@ -60,7 +67,7 @@ public class ToolableViewAnimator extends ViewAnimator { } @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { + public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { if (isInEditMode() && mInitChild-- > 0) { return; } -- cgit v1.2.3 From 5e5febaee38836d546d3cc5b9ce0003f206d4aa5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 18 Jun 2015 16:25:18 +0200 Subject: rename activities to match new purpose --- .../keychain/ui/DecryptActivity.java | 153 +++++ .../keychain/ui/DecryptFilesActivity.java | 150 ----- .../keychain/ui/DecryptFilesInputFragment.java | 2 +- .../keychain/ui/DecryptFilesListFragment.java | 633 --------------------- .../keychain/ui/DecryptListFragment.java | 633 +++++++++++++++++++++ .../keychain/ui/DecryptTextActivity.java | 228 -------- .../keychain/ui/DecryptTextFragment.java | 189 ------ .../keychain/ui/DisplayTextActivity.java | 230 ++++++++ .../keychain/ui/DisplayTextFragment.java | 189 ++++++ .../ui/EncryptDecryptOverviewFragment.java | 8 +- 10 files changed, 1210 insertions(+), 1205 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java new file mode 100644 index 000000000..aba680807 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import java.util.ArrayList; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + + +public class DecryptActivity extends BaseActivity { + + /* Intents */ + public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA; + // TODO handle this intent + public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; + + // intern + public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(Activity.RESULT_CANCELED); + finish(); + } + }, false); + + // Handle intent actions + handleActions(savedInstanceState, getIntent()); + } + + @Override + protected void initLayout() { + setContentView(R.layout.decrypt_files_activity); + } + + /** + * Handles all actions with this intent + */ + private void handleActions(Bundle savedInstanceState, Intent intent) { + + // No need to initialize fragments if we are just being restored + if (savedInstanceState != null) { + return; + } + + ArrayList uris = new ArrayList<>(); + + String action = intent.getAction(); + + // TODO handle ACTION_DECRYPT_FROM_CLIPBOARD + switch (action) { + case Intent.ACTION_SEND: { + // When sending to Keychain Decrypt via share menu + // Binary via content provider (could also be files) + // override uri to get stream from send + action = ACTION_DECRYPT_DATA; + uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); + break; + } + + case Intent.ACTION_SEND_MULTIPLE: { + action = ACTION_DECRYPT_DATA; + uris.addAll(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)); + break; + } + + case Intent.ACTION_VIEW: + // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) + action = ACTION_DECRYPT_DATA; + + // fallthrough + default: + uris.add(intent.getData()); + + } + + if (ACTION_DECRYPT_DATA.equals(action)) { + // Definitely need a data uri with the decrypt_data intent + if (uris.isEmpty()) { + Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); + setResult(Activity.RESULT_CANCELED); + finish(); + } + displayListFragment(uris); + return; + } + + boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); + displayInputFragment(showOpenDialog); + + } + + public void displayInputFragment(boolean showOpenDialog) { + DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(showOpenDialog); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.decrypt_files_fragment_container, frag) + .commit(); + } + + public void displayListFragment(ArrayList inputUris) { + + DecryptListFragment frag = DecryptListFragment.newInstance(inputUris); + + FragmentManager fragMan = getSupportFragmentManager(); + + FragmentTransaction trans = fragMan.beginTransaction(); + trans.replace(R.id.decrypt_files_fragment_container, frag); + + // if there already is a fragment, allow going back to that. otherwise, we're top level! + if (fragMan.getFragments() != null && !fragMan.getFragments().isEmpty()) { + trans.addToBackStack("list"); + } + + trans.commit(); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java deleted file mode 100644 index 672015aaa..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import java.util.ArrayList; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.view.View; -import android.widget.Toast; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; -import org.sufficientlysecure.keychain.ui.base.BaseActivity; - - -public class DecryptFilesActivity extends BaseActivity { - - /* Intents */ - public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA; - - // intern - public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); - - // Handle intent actions - handleActions(savedInstanceState, getIntent()); - } - - @Override - protected void initLayout() { - setContentView(R.layout.decrypt_files_activity); - } - - /** - * Handles all actions with this intent - */ - private void handleActions(Bundle savedInstanceState, Intent intent) { - - // No need to initialize fragments if we are just being restored - if (savedInstanceState != null) { - return; - } - - ArrayList uris = new ArrayList<>(); - - String action = intent.getAction(); - - switch (action) { - case Intent.ACTION_SEND: { - // When sending to Keychain Decrypt via share menu - // Binary via content provider (could also be files) - // override uri to get stream from send - action = ACTION_DECRYPT_DATA; - uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); - break; - } - - case Intent.ACTION_SEND_MULTIPLE: { - action = ACTION_DECRYPT_DATA; - uris.addAll(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)); - break; - } - - case Intent.ACTION_VIEW: - // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) - action = ACTION_DECRYPT_DATA; - - // fallthrough - default: - uris.add(intent.getData()); - - } - - if (ACTION_DECRYPT_DATA.equals(action)) { - // Definitely need a data uri with the decrypt_data intent - if (uris.isEmpty()) { - Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); - setResult(Activity.RESULT_CANCELED); - finish(); - } - displayListFragment(uris); - return; - } - - boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); - displayInputFragment(showOpenDialog); - - } - - public void displayInputFragment(boolean showOpenDialog) { - DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(showOpenDialog); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.decrypt_files_fragment_container, frag) - .commit(); - } - - public void displayListFragment(ArrayList inputUris) { - - DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(inputUris); - - FragmentManager fragMan = getSupportFragmentManager(); - - FragmentTransaction trans = fragMan.beginTransaction(); - trans.replace(R.id.decrypt_files_fragment_container, frag); - - // if there already is a fragment, allow going back to that. otherwise, we're top level! - if (fragMan.getFragments() != null && !fragMan.getFragments().isEmpty()) { - trans.addToBackStack("list"); - } - - trans.commit(); - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 2b9219f49..9a0d95bb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -127,7 +127,7 @@ public class DecryptFilesInputFragment extends Fragment { return; } - DecryptFilesActivity activity = (DecryptFilesActivity) getActivity(); + DecryptActivity activity = (DecryptActivity) getActivity(); ArrayList uris = new ArrayList<>(); uris.add(mInputUri); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java deleted file mode 100644 index 35f534c90..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java +++ /dev/null @@ -1,633 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnDismissListener; -import android.widget.PopupMenu.OnMenuItemClickListener; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.ViewAnimator; - -import org.openintents.openpgp.OpenPgpMetadata; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; -import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; -import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.Log; - -public class DecryptFilesListFragment - extends CryptoOperationFragment - implements OnMenuItemClickListener { - public static final String ARG_URIS = "uris"; - - private static final int REQUEST_CODE_OUTPUT = 0x00007007; - - private ArrayList mInputUris; - private HashMap mOutputUris; - private ArrayList mPendingInputUris; - - private Uri mCurrentInputUri; - - private DecryptFilesAdapter mAdapter; - - /** - * Creates new instance of this fragment - */ - public static DecryptFilesListFragment newInstance(ArrayList uris) { - DecryptFilesListFragment frag = new DecryptFilesListFragment(); - - Bundle args = new Bundle(); - args.putParcelableArrayList(ARG_URIS, uris); - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); - - RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); - - vFilesList.addItemDecoration(new SpacesItemDecoration( - FormattingUtils.dpToPx(getActivity(), 4))); - vFilesList.setHasFixedSize(true); - vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); - vFilesList.setItemAnimator(new DefaultItemAnimator()); - - mAdapter = new DecryptFilesAdapter(getActivity(), this); - vFilesList.setAdapter(mAdapter); - - return view; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putParcelableArrayList(ARG_URIS, mInputUris); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - displayInputUris(getArguments().getParcelableArrayList(ARG_URIS)); - } - - private String removeEncryptedAppend(String name) { - if (name.endsWith(Constants.FILE_EXTENSION_ASC) - || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) - || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { - return name.substring(0, name.length() - 4); - } - return name; - } - - private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(inputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, originalFilename); - FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_OUTPUT: { - // This happens after output file was selected, so start our operation - if (resultCode == Activity.RESULT_OK && data != null) { - Uri saveUri = data.getData(); - Uri outputUri = mOutputUris.get(mCurrentInputUri); - // TODO save from outputUri to saveUri - - mCurrentInputUri = null; - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - - private void displayInputUris(ArrayList uris) { - mInputUris = uris; - mOutputUris = new HashMap<>(uris.size()); - for (Uri uri : uris) { - mAdapter.add(uri); - mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); - } - - mPendingInputUris = uris; - - cryptoOperation(); - } - - @Override - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - super.cryptoOperation(cryptoInput, false); - } - - @Override - protected boolean onCryptoSetProgress(String msg, int progress, int max) { - mAdapter.setProgress(mCurrentInputUri, progress, max, msg); - return true; - } - - @Override - protected void dismissProgress() { - // progress shown inline, so never mind - } - - @Override - protected void onCryptoOperationError(DecryptVerifyResult result) { - final Uri uri = mCurrentInputUri; - mCurrentInputUri = null; - - mAdapter.addResult(uri, result, null, null, null); - - cryptoOperation(); - } - - @Override - protected void onCryptoOperationSuccess(DecryptVerifyResult result) { - final Uri uri = mCurrentInputUri; - mCurrentInputUri = null; - - Drawable icon = null; - OnClickListener onFileClick = null, onKeyClick = null; - - if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { - icon = loadIcon(result.getDecryptMetadata().getMimeType()); - } - - OpenPgpSignatureResult sigResult = result.getSignatureResult(); - if (sigResult != null) { - final long keyId = sigResult.getKeyId(); - if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { - onKeyClick = new OnClickListener() { - @Override - public void onClick(View view) { - Activity activity = getActivity(); - if (activity == null) { - return; - } - Intent intent = new Intent(activity, ViewKeyActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); - activity.startActivity(intent); - } - }; - } - } - - if (result.success() && result.getDecryptMetadata() != null) { - final OpenPgpMetadata metadata = result.getDecryptMetadata(); - onFileClick = new OnClickListener() { - @Override - public void onClick(View view) { - Activity activity = getActivity(); - if (activity == null || mCurrentInputUri != null) { - return; - } - - Uri outputUri = mOutputUris.get(uri); - Intent intent = new Intent(); - intent.setDataAndType(outputUri, metadata.getMimeType()); - activity.startActivity(intent); - } - }; - } - - mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); - - cryptoOperation(); - - } - - @Override - protected PgpDecryptVerifyInputParcel createOperationInput() { - - if (mCurrentInputUri == null) { - if (mPendingInputUris.isEmpty()) { - // nothing left to do - return null; - } - - mCurrentInputUri = mPendingInputUris.remove(0); - } - - Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); - Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); - - return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) - .setAllowSymmetricDecryption(true); - - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - } - - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { - return false; - } - Activity activity = getActivity(); - if (activity == null) { - return false; - } - - ViewModel model = mAdapter.mMenuClickedModel; - DecryptVerifyResult result = model.mResult; - switch (menuItem.getItemId()) { - case R.id.view_log: - Intent intent = new Intent(activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); - activity.startActivity(intent); - return true; - case R.id.decrypt_save: - OpenPgpMetadata metadata = result.getDecryptMetadata(); - if (metadata == null) { - return true; - } - mCurrentInputUri = model.mInputUri; - askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); - return true; - case R.id.decrypt_delete: - Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); - return true; - } - return false; - } - - public static class DecryptFilesAdapter extends RecyclerView.Adapter { - private Context mContext; - private ArrayList mDataset; - private OnMenuItemClickListener mMenuItemClickListener; - private ViewModel mMenuClickedModel; - - public class ViewModel { - Context mContext; - Uri mInputUri; - DecryptVerifyResult mResult; - Drawable mIcon; - - OnClickListener mOnFileClickListener; - OnClickListener mOnKeyClickListener; - - int mProgress, mMax; - String mProgressMsg; - - ViewModel(Context context, Uri uri) { - mContext = context; - mInputUri = uri; - mProgress = 0; - mMax = 100; - } - - void addResult(DecryptVerifyResult result) { - mResult = result; - } - - void addIcon(Drawable icon) { - mIcon = icon; - } - - void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { - mOnFileClickListener = onFileClick; - mOnKeyClickListener = onKeyClick; - } - - boolean hasResult() { - return mResult != null; - } - - void setProgress(int progress, int max, String msg) { - if (msg != null) { - mProgressMsg = msg; - } - mProgress = progress; - mMax = max; - } - - // Depends on inputUri only - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ViewModel viewModel = (ViewModel) o; - return !(mResult != null ? !mResult.equals(viewModel.mResult) - : viewModel.mResult != null); - } - - // Depends on inputUri only - @Override - public int hashCode() { - return mResult != null ? mResult.hashCode() : 0; - } - - @Override - public String toString() { - return mResult.toString(); - } - } - - // Provide a suitable constructor (depends on the kind of dataset) - public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) { - mContext = context; - mMenuItemClickListener = menuItemClickListener; - mDataset = new ArrayList<>(); - } - - // Create new views (invoked by the layout manager) - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - //inflate your layout and pass it to view holder - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.decrypt_list_entry, parent, false); - return new ViewHolder(v); - } - - // Replace the contents of a view (invoked by the layout manager) - @Override - public void onBindViewHolder(ViewHolder holder, final int position) { - // - get element from your dataset at this position - // - replace the contents of the view with that element - final ViewModel model = mDataset.get(position); - - if (model.hasResult()) { - if (holder.vAnimator.getDisplayedChild() != 1) { - holder.vAnimator.setDisplayedChild(1); - } - - KeyFormattingUtils.setStatus(mContext, holder, model.mResult); - - OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); - holder.vFilename.setText(metadata.getFilename()); - - long size = metadata.getOriginalSize(); - if (size == -1 || size == 0) { - holder.vFilesize.setText(""); - } else { - holder.vFilesize.setText(FileHelper.readableFileSize(size)); - } - - // TODO thumbnail from OpenPgpMetadata - if (model.mIcon != null) { - holder.vThumbnail.setImageDrawable(model.mIcon); - } else { - holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); - } - - holder.vFile.setOnClickListener(model.mOnFileClickListener); - holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); - - holder.vContextMenu.setTag(model); - holder.vContextMenu.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - mMenuClickedModel = model; - PopupMenu menu = new PopupMenu(mContext, view); - menu.inflate(R.menu.decrypt_item_context_menu); - menu.setOnMenuItemClickListener(mMenuItemClickListener); - menu.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(PopupMenu popupMenu) { - mMenuClickedModel = null; - } - }); - menu.show(); - } - }); - - } else { - if (holder.vAnimator.getDisplayedChild() != 0) { - holder.vAnimator.setDisplayedChild(0); - } - - holder.vProgress.setProgress(model.mProgress); - holder.vProgress.setMax(model.mMax); - holder.vProgressMsg.setText(model.mProgressMsg); - } - - } - - // Return the size of your dataset (invoked by the layout manager) - @Override - public int getItemCount() { - return mDataset.size(); - } - - public void add(Uri uri) { - ViewModel newModel = new ViewModel(mContext, uri); - mDataset.add(newModel); - notifyItemInserted(mDataset.size()); - } - - public void setProgress(Uri uri, int progress, int max, String msg) { - ViewModel newModel = new ViewModel(mContext, uri); - int pos = mDataset.indexOf(newModel); - mDataset.get(pos).setProgress(progress, max, msg); - notifyItemChanged(pos); - } - - public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, - OnClickListener onFileClick, OnClickListener onKeyClick) { - - ViewModel model = new ViewModel(mContext, uri); - int pos = mDataset.indexOf(model); - model = mDataset.get(pos); - - model.addResult(result); - if (icon != null) { - model.addIcon(icon); - } - model.setOnClickListeners(onFileClick, onKeyClick); - - notifyItemChanged(pos); - } - - } - - - // Provide a reference to the views for each data item - // Complex data items may need more than one view per item, and - // you provide access to all the views for a data item in a view holder - public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder { - public ViewAnimator vAnimator; - - public ProgressBar vProgress; - public TextView vProgressMsg; - - public View vFile; - public TextView vFilename; - public TextView vFilesize; - public ImageView vThumbnail; - - public ImageView vEncStatusIcon; - public TextView vEncStatusText; - - public ImageView vSigStatusIcon; - public TextView vSigStatusText; - public View vSignatureLayout; - public TextView vSignatureName; - public TextView vSignatureMail; - public TextView vSignatureAction; - - public View vContextMenu; - - public ViewHolder(View itemView) { - super(itemView); - - vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator); - - vProgress = (ProgressBar) itemView.findViewById(R.id.progress); - vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); - - vFile = itemView.findViewById(R.id.file); - vFilename = (TextView) itemView.findViewById(R.id.filename); - vFilesize = (TextView) itemView.findViewById(R.id.filesize); - vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); - - vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); - vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); - - vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); - vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); - vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); - vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); - vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); - vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); - - vContextMenu = itemView.findViewById(R.id.context_menu); - - } - - @Override - public ImageView getEncryptionStatusIcon() { - return vEncStatusIcon; - } - - @Override - public TextView getEncryptionStatusText() { - return vEncStatusText; - } - - @Override - public ImageView getSignatureStatusIcon() { - return vSigStatusIcon; - } - - @Override - public TextView getSignatureStatusText() { - return vSigStatusText; - } - - @Override - public View getSignatureLayout() { - return vSignatureLayout; - } - - @Override - public TextView getSignatureAction() { - return vSignatureAction; - } - - @Override - public TextView getSignatureUserName() { - return vSignatureName; - } - - @Override - public TextView getSignatureUserEmail() { - return vSignatureMail; - } - - @Override - public boolean hasEncrypt() { - return true; - } - } - - private Drawable loadIcon(String mimeType) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(mimeType); - - final List matches = getActivity() - .getPackageManager().queryIntentActivities(intent, 0); - //noinspection LoopStatementThatDoesntLoop - for (ResolveInfo match : matches) { - return match.loadIcon(getActivity().getPackageManager()); - } - return null; - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java new file mode 100644 index 000000000..65d0fc8cf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnDismissListener; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.openintents.openpgp.OpenPgpMetadata; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; +import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; +import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +public class DecryptListFragment + extends CryptoOperationFragment + implements OnMenuItemClickListener { + public static final String ARG_URIS = "uris"; + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + private ArrayList mInputUris; + private HashMap mOutputUris; + private ArrayList mPendingInputUris; + + private Uri mCurrentInputUri; + + private DecryptFilesAdapter mAdapter; + + /** + * Creates new instance of this fragment + */ + public static DecryptListFragment newInstance(ArrayList uris) { + DecryptListFragment frag = new DecryptListFragment(); + + Bundle args = new Bundle(); + args.putParcelableArrayList(ARG_URIS, uris); + frag.setArguments(args); + + return frag; + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); + + RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); + + vFilesList.addItemDecoration(new SpacesItemDecoration( + FormattingUtils.dpToPx(getActivity(), 4))); + vFilesList.setHasFixedSize(true); + vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); + vFilesList.setItemAnimator(new DefaultItemAnimator()); + + mAdapter = new DecryptFilesAdapter(getActivity(), this); + vFilesList.setAdapter(mAdapter); + + return view; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelableArrayList(ARG_URIS, mInputUris); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + displayInputUris(getArguments().getParcelableArrayList(ARG_URIS)); + } + + private String removeEncryptedAppend(String name) { + if (name.endsWith(Constants.FILE_EXTENSION_ASC) + || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) + || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { + return name.substring(0, name.length() - 4); + } + return name; + } + + private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(inputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, originalFilename); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + Uri saveUri = data.getData(); + Uri outputUri = mOutputUris.get(mCurrentInputUri); + // TODO save from outputUri to saveUri + + mCurrentInputUri = null; + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + private void displayInputUris(ArrayList uris) { + mInputUris = uris; + mOutputUris = new HashMap<>(uris.size()); + for (Uri uri : uris) { + mAdapter.add(uri); + mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); + } + + mPendingInputUris = uris; + + cryptoOperation(); + } + + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + super.cryptoOperation(cryptoInput, false); + } + + @Override + protected boolean onCryptoSetProgress(String msg, int progress, int max) { + mAdapter.setProgress(mCurrentInputUri, progress, max, msg); + return true; + } + + @Override + protected void dismissProgress() { + // progress shown inline, so never mind + } + + @Override + protected void onCryptoOperationError(DecryptVerifyResult result) { + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; + + mAdapter.addResult(uri, result, null, null, null); + + cryptoOperation(); + } + + @Override + protected void onCryptoOperationSuccess(DecryptVerifyResult result) { + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; + + Drawable icon = null; + OnClickListener onFileClick = null, onKeyClick = null; + + if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { + icon = loadIcon(result.getDecryptMetadata().getMimeType()); + } + + OpenPgpSignatureResult sigResult = result.getSignatureResult(); + if (sigResult != null) { + final long keyId = sigResult.getKeyId(); + if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { + onKeyClick = new OnClickListener() { + @Override + public void onClick(View view) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + Intent intent = new Intent(activity, ViewKeyActivity.class); + intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); + activity.startActivity(intent); + } + }; + } + } + + if (result.success() && result.getDecryptMetadata() != null) { + final OpenPgpMetadata metadata = result.getDecryptMetadata(); + onFileClick = new OnClickListener() { + @Override + public void onClick(View view) { + Activity activity = getActivity(); + if (activity == null || mCurrentInputUri != null) { + return; + } + + Uri outputUri = mOutputUris.get(uri); + Intent intent = new Intent(); + intent.setDataAndType(outputUri, metadata.getMimeType()); + activity.startActivity(intent); + } + }; + } + + mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + + cryptoOperation(); + + } + + @Override + protected PgpDecryptVerifyInputParcel createOperationInput() { + + if (mCurrentInputUri == null) { + if (mPendingInputUris.isEmpty()) { + // nothing left to do + return null; + } + + mCurrentInputUri = mPendingInputUris.remove(0); + } + + Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); + Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); + + return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) + .setAllowSymmetricDecryption(true); + + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { + return false; + } + Activity activity = getActivity(); + if (activity == null) { + return false; + } + + ViewModel model = mAdapter.mMenuClickedModel; + DecryptVerifyResult result = model.mResult; + switch (menuItem.getItemId()) { + case R.id.view_log: + Intent intent = new Intent(activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); + activity.startActivity(intent); + return true; + case R.id.decrypt_save: + OpenPgpMetadata metadata = result.getDecryptMetadata(); + if (metadata == null) { + return true; + } + mCurrentInputUri = model.mInputUri; + askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); + return true; + case R.id.decrypt_delete: + Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); + return true; + } + return false; + } + + public static class DecryptFilesAdapter extends RecyclerView.Adapter { + private Context mContext; + private ArrayList mDataset; + private OnMenuItemClickListener mMenuItemClickListener; + private ViewModel mMenuClickedModel; + + public class ViewModel { + Context mContext; + Uri mInputUri; + DecryptVerifyResult mResult; + Drawable mIcon; + + OnClickListener mOnFileClickListener; + OnClickListener mOnKeyClickListener; + + int mProgress, mMax; + String mProgressMsg; + + ViewModel(Context context, Uri uri) { + mContext = context; + mInputUri = uri; + mProgress = 0; + mMax = 100; + } + + void addResult(DecryptVerifyResult result) { + mResult = result; + } + + void addIcon(Drawable icon) { + mIcon = icon; + } + + void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { + mOnFileClickListener = onFileClick; + mOnKeyClickListener = onKeyClick; + } + + boolean hasResult() { + return mResult != null; + } + + void setProgress(int progress, int max, String msg) { + if (msg != null) { + mProgressMsg = msg; + } + mProgress = progress; + mMax = max; + } + + // Depends on inputUri only + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViewModel viewModel = (ViewModel) o; + return !(mResult != null ? !mResult.equals(viewModel.mResult) + : viewModel.mResult != null); + } + + // Depends on inputUri only + @Override + public int hashCode() { + return mResult != null ? mResult.hashCode() : 0; + } + + @Override + public String toString() { + return mResult.toString(); + } + } + + // Provide a suitable constructor (depends on the kind of dataset) + public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) { + mContext = context; + mMenuItemClickListener = menuItemClickListener; + mDataset = new ArrayList<>(); + } + + // Create new views (invoked by the layout manager) + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + //inflate your layout and pass it to view holder + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.decrypt_list_entry, parent, false); + return new ViewHolder(v); + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + // - get element from your dataset at this position + // - replace the contents of the view with that element + final ViewModel model = mDataset.get(position); + + if (model.hasResult()) { + if (holder.vAnimator.getDisplayedChild() != 1) { + holder.vAnimator.setDisplayedChild(1); + } + + KeyFormattingUtils.setStatus(mContext, holder, model.mResult); + + OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); + holder.vFilename.setText(metadata.getFilename()); + + long size = metadata.getOriginalSize(); + if (size == -1 || size == 0) { + holder.vFilesize.setText(""); + } else { + holder.vFilesize.setText(FileHelper.readableFileSize(size)); + } + + // TODO thumbnail from OpenPgpMetadata + if (model.mIcon != null) { + holder.vThumbnail.setImageDrawable(model.mIcon); + } else { + holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + + holder.vFile.setOnClickListener(model.mOnFileClickListener); + holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + + holder.vContextMenu.setTag(model); + holder.vContextMenu.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + mMenuClickedModel = model; + PopupMenu menu = new PopupMenu(mContext, view); + menu.inflate(R.menu.decrypt_item_context_menu); + menu.setOnMenuItemClickListener(mMenuItemClickListener); + menu.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(PopupMenu popupMenu) { + mMenuClickedModel = null; + } + }); + menu.show(); + } + }); + + } else { + if (holder.vAnimator.getDisplayedChild() != 0) { + holder.vAnimator.setDisplayedChild(0); + } + + holder.vProgress.setProgress(model.mProgress); + holder.vProgress.setMax(model.mMax); + holder.vProgressMsg.setText(model.mProgressMsg); + } + + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return mDataset.size(); + } + + public void add(Uri uri) { + ViewModel newModel = new ViewModel(mContext, uri); + mDataset.add(newModel); + notifyItemInserted(mDataset.size()); + } + + public void setProgress(Uri uri, int progress, int max, String msg) { + ViewModel newModel = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).setProgress(progress, max, msg); + notifyItemChanged(pos); + } + + public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, + OnClickListener onFileClick, OnClickListener onKeyClick) { + + ViewModel model = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(model); + model = mDataset.get(pos); + + model.addResult(result); + if (icon != null) { + model.addIcon(icon); + } + model.setOnClickListeners(onFileClick, onKeyClick); + + notifyItemChanged(pos); + } + + } + + + // Provide a reference to the views for each data item + // Complex data items may need more than one view per item, and + // you provide access to all the views for a data item in a view holder + public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder { + public ViewAnimator vAnimator; + + public ProgressBar vProgress; + public TextView vProgressMsg; + + public View vFile; + public TextView vFilename; + public TextView vFilesize; + public ImageView vThumbnail; + + public ImageView vEncStatusIcon; + public TextView vEncStatusText; + + public ImageView vSigStatusIcon; + public TextView vSigStatusText; + public View vSignatureLayout; + public TextView vSignatureName; + public TextView vSignatureMail; + public TextView vSignatureAction; + + public View vContextMenu; + + public ViewHolder(View itemView) { + super(itemView); + + vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator); + + vProgress = (ProgressBar) itemView.findViewById(R.id.progress); + vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); + + vFile = itemView.findViewById(R.id.file); + vFilename = (TextView) itemView.findViewById(R.id.filename); + vFilesize = (TextView) itemView.findViewById(R.id.filesize); + vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + + vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); + vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); + + vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); + vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); + vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); + vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); + vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); + vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + + vContextMenu = itemView.findViewById(R.id.context_menu); + + } + + @Override + public ImageView getEncryptionStatusIcon() { + return vEncStatusIcon; + } + + @Override + public TextView getEncryptionStatusText() { + return vEncStatusText; + } + + @Override + public ImageView getSignatureStatusIcon() { + return vSigStatusIcon; + } + + @Override + public TextView getSignatureStatusText() { + return vSigStatusText; + } + + @Override + public View getSignatureLayout() { + return vSignatureLayout; + } + + @Override + public TextView getSignatureAction() { + return vSignatureAction; + } + + @Override + public TextView getSignatureUserName() { + return vSignatureName; + } + + @Override + public TextView getSignatureUserEmail() { + return vSignatureMail; + } + + @Override + public boolean hasEncrypt() { + return true; + } + } + + private Drawable loadIcon(String mimeType) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType(mimeType); + + final List matches = getActivity() + .getPackageManager().queryIntentActivities(intent, 0); + //noinspection LoopStatementThatDoesntLoop + for (ResolveInfo match : matches) { + return match.loadIcon(getActivity().getPackageManager()); + } + return null; + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java deleted file mode 100644 index 0c463c2cd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2012-2015 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.text.TextUtils; -import android.view.View; -import android.widget.Toast; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.regex.Matcher; - -public class DecryptTextActivity extends BaseActivity { - - /* Intents */ - public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; - public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT; - - // intern - public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); - - // Handle intent actions - handleActions(savedInstanceState, getIntent()); - } - - @Override - protected void initLayout() { - setContentView(R.layout.decrypt_text_activity); - } - - /** - * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail - */ - private String fixPgpMessage(String message) { - // windows newline -> unix newline - message = message.replaceAll("\r\n", "\n"); - // Mac OS before X newline -> unix newline - message = message.replaceAll("\r", "\n"); - - // remove whitespaces before newline - message = message.replaceAll(" +\n", "\n"); - // only two consecutive newlines are allowed - message = message.replaceAll("\n\n+", "\n\n"); - - // replace non breakable spaces - message = message.replaceAll("\\xa0", " "); - - return message; - } - - /** - * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail - */ - private String fixPgpCleartextSignature(CharSequence input) { - if (!TextUtils.isEmpty(input)) { - String text = input.toString(); - - // windows newline -> unix newline - text = text.replaceAll("\r\n", "\n"); - // Mac OS before X newline -> unix newline - text = text.replaceAll("\r", "\n"); - - return text; - } else { - return null; - } - } - - private String getPgpContent(CharSequence input) { - // only decrypt if clipboard content is available and a pgp message or cleartext signature - if (!TextUtils.isEmpty(input)) { - Log.dEscaped(Constants.TAG, "input: " + input); - - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); - if (matcher.matches()) { - String text = matcher.group(1); - text = fixPgpMessage(text); - - Log.dEscaped(Constants.TAG, "input fixed: " + text); - return text; - } else { - matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); - if (matcher.matches()) { - String text = matcher.group(1); - text = fixPgpCleartextSignature(text); - - Log.dEscaped(Constants.TAG, "input fixed: " + text); - return text; - } else { - return null; - } - } - } else { - return null; - } - } - - /** - * Handles all actions with this intent - */ - private void handleActions(Bundle savedInstanceState, Intent intent) { - String action = intent.getAction(); - Bundle extras = intent.getExtras(); - String type = intent.getType(); - - if (extras == null) { - extras = new Bundle(); - } - - if (savedInstanceState != null) { - return; - } - - if (Intent.ACTION_SEND.equals(action) && type != null) { - Log.d(Constants.TAG, "ACTION_SEND"); - Log.logDebugBundle(extras, "SEND extras"); - - // When sending to Keychain Decrypt via share menu - if ("text/plain".equals(type)) { - String sharedText = extras.getString(Intent.EXTRA_TEXT); - sharedText = getPgpContent(sharedText); - - if (sharedText != null) { - loadFragment(sharedText); - } else { - Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else { - Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else if (ACTION_DECRYPT_TEXT.equals(action)) { - Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); - - String extraText = extras.getString(EXTRA_TEXT); - extraText = getPgpContent(extraText); - - if (extraText != null) { - loadFragment(extraText); - } else { - Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) { - Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); - - CharSequence clipboardText = ClipboardReflection.getClipboardText(this); - String text = getPgpContent(clipboardText); - - if (text != null) { - loadFragment(text); - } else { - returnInvalidResult(); - } - } else if (ACTION_DECRYPT_TEXT.equals(action)) { - Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } - - private void returnInvalidResult() { - SingletonResult result = new SingletonResult( - SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_NO_VALID_ENC); - Intent intent = new Intent(); - intent.putExtra(SingletonResult.EXTRA_RESULT, result); - setResult(RESULT_OK, intent); - finish(); - } - - private void loadFragment(String ciphertext) { - // Create an instance of the fragment - Fragment frag = DecryptTextFragment.newInstance(ciphertext); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.decrypt_text_fragment_container, frag) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java deleted file mode 100644 index 051da5d6b..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.content.Intent; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.ShareHelper; - -import java.io.UnsupportedEncodingException; - -public class DecryptTextFragment extends DecryptFragment { - public static final String ARG_CIPHERTEXT = "ciphertext"; - public static final String ARG_SHOW_MENU = "show_menu"; - - // view - private TextView mText; - - // model - private String mCiphertext; - private boolean mShowMenuOptions; - - public static DecryptTextFragment newInstance(String ciphertext) { - DecryptTextFragment frag = new DecryptTextFragment(); - - Bundle args = new Bundle(); - args.putString(ARG_CIPHERTEXT, ciphertext); - - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); - mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); - - return view; - } - - /** - * Create Intent Chooser but exclude decrypt activites - */ - private Intent sendWithChooserExcludingDecrypt(String text) { - Intent prototype = createSendIntent(text); - String title = getString(R.string.title_share_message); - - // we don't want to decrypt the decrypted, no inception ;) - String[] blacklist = new String[]{ - Constants.PACKAGE_NAME + ".ui.DecryptTextActivity", - "org.thialfihar.android.apg.ui.DecryptActivity" - }; - - return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist); - } - - private Intent createSendIntent(String text) { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, text); - sendIntent.setType("text/plain"); - return sendIntent; - } - - private void copyToClipboard(String text) { - ClipboardReflection.copyToClipboard(getActivity(), text); - Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setHasOptionsMenu(true); - - Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; - mCiphertext = args.getString(ARG_CIPHERTEXT); - mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false); - - if (savedInstanceState == null) { - cryptoOperation(); - } - - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putString(ARG_CIPHERTEXT, mCiphertext); - outState.putBoolean(ARG_SHOW_MENU, mShowMenuOptions); - // no need to save the decrypted text, it's in the textview - - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - if (mShowMenuOptions) { - inflater.inflate(R.menu.decrypt_menu, menu); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.decrypt_share: { - startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString())); - break; - } - case R.id.decrypt_copy: { - copyToClipboard(mText.getText().toString()); - break; - } - default: { - return super.onOptionsItemSelected(item); - } - } - - return true; - } - - @Override - protected PgpDecryptVerifyInputParcel createOperationInput() { - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes()); - input.setAllowSymmetricDecryption(true); - return input; - } - - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - mShowMenuOptions = hideErrorOverlay; - getActivity().supportInvalidateOptionsMenu(); - } - - @Override - protected void onCryptoOperationSuccess(DecryptVerifyResult result) { - - byte[] decryptedMessage = result.getOutputBytes(); - String displayMessage; - if (result.getCharset() != null) { - try { - displayMessage = new String(decryptedMessage, result.getCharset()); - } catch (UnsupportedEncodingException e) { - // if we can't decode properly, just fall back to utf-8 - displayMessage = new String(decryptedMessage); - } - } else { - displayMessage = new String(decryptedMessage); - } - mText.setText(displayMessage); - - // display signature result in activity - loadVerifyResult(result); - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java new file mode 100644 index 000000000..5c44e345e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2012-2015 Dominik Schürmann + * Copyright (C) 2010-2014 Thialfihar + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.SingletonResult; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.regex.Matcher; + +public class DisplayTextActivity extends BaseActivity { + + // TODO make this only display text (maybe we need only the fragment?) + + /* Intents */ + public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; + public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT; + + // intern + public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(Activity.RESULT_CANCELED); + finish(); + } + }, false); + + // Handle intent actions + handleActions(savedInstanceState, getIntent()); + } + + @Override + protected void initLayout() { + setContentView(R.layout.decrypt_text_activity); + } + + /** + * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail + */ + private String fixPgpMessage(String message) { + // windows newline -> unix newline + message = message.replaceAll("\r\n", "\n"); + // Mac OS before X newline -> unix newline + message = message.replaceAll("\r", "\n"); + + // remove whitespaces before newline + message = message.replaceAll(" +\n", "\n"); + // only two consecutive newlines are allowed + message = message.replaceAll("\n\n+", "\n\n"); + + // replace non breakable spaces + message = message.replaceAll("\\xa0", " "); + + return message; + } + + /** + * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail + */ + private String fixPgpCleartextSignature(CharSequence input) { + if (!TextUtils.isEmpty(input)) { + String text = input.toString(); + + // windows newline -> unix newline + text = text.replaceAll("\r\n", "\n"); + // Mac OS before X newline -> unix newline + text = text.replaceAll("\r", "\n"); + + return text; + } else { + return null; + } + } + + private String getPgpContent(CharSequence input) { + // only decrypt if clipboard content is available and a pgp message or cleartext signature + if (!TextUtils.isEmpty(input)) { + Log.dEscaped(Constants.TAG, "input: " + input); + + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); + if (matcher.matches()) { + String text = matcher.group(1); + text = fixPgpMessage(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; + } else { + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); + if (matcher.matches()) { + String text = matcher.group(1); + text = fixPgpCleartextSignature(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; + } else { + return null; + } + } + } else { + return null; + } + } + + /** + * Handles all actions with this intent + */ + private void handleActions(Bundle savedInstanceState, Intent intent) { + String action = intent.getAction(); + Bundle extras = intent.getExtras(); + String type = intent.getType(); + + if (extras == null) { + extras = new Bundle(); + } + + if (savedInstanceState != null) { + return; + } + + if (Intent.ACTION_SEND.equals(action) && type != null) { + Log.d(Constants.TAG, "ACTION_SEND"); + Log.logDebugBundle(extras, "SEND extras"); + + // When sending to Keychain Decrypt via share menu + if ("text/plain".equals(type)) { + String sharedText = extras.getString(Intent.EXTRA_TEXT); + sharedText = getPgpContent(sharedText); + + if (sharedText != null) { + loadFragment(sharedText); + } else { + Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); + } + } else { + Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); + } + } else if (ACTION_DECRYPT_TEXT.equals(action)) { + Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); + + String extraText = extras.getString(EXTRA_TEXT); + extraText = getPgpContent(extraText); + + if (extraText != null) { + loadFragment(extraText); + } else { + Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); + } + } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) { + Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); + + CharSequence clipboardText = ClipboardReflection.getClipboardText(this); + String text = getPgpContent(clipboardText); + + if (text != null) { + loadFragment(text); + } else { + returnInvalidResult(); + } + } else if (ACTION_DECRYPT_TEXT.equals(action)) { + Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); + } + } + + private void returnInvalidResult() { + SingletonResult result = new SingletonResult( + SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_NO_VALID_ENC); + Intent intent = new Intent(); + intent.putExtra(SingletonResult.EXTRA_RESULT, result); + setResult(RESULT_OK, intent); + finish(); + } + + private void loadFragment(String ciphertext) { + // Create an instance of the fragment + Fragment frag = DisplayTextFragment.newInstance(ciphertext); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.decrypt_text_fragment_container, frag) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java new file mode 100644 index 000000000..cd75e2bc3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.ShareHelper; + +import java.io.UnsupportedEncodingException; + +public class DisplayTextFragment extends DecryptFragment { + public static final String ARG_CIPHERTEXT = "ciphertext"; + public static final String ARG_SHOW_MENU = "show_menu"; + + // view + private TextView mText; + + // model + private String mCiphertext; + private boolean mShowMenuOptions; + + public static DisplayTextFragment newInstance(String ciphertext) { + DisplayTextFragment frag = new DisplayTextFragment(); + + Bundle args = new Bundle(); + args.putString(ARG_CIPHERTEXT, ciphertext); + + frag.setArguments(args); + + return frag; + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); + mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); + + return view; + } + + /** + * Create Intent Chooser but exclude decrypt activites + */ + private Intent sendWithChooserExcludingDecrypt(String text) { + Intent prototype = createSendIntent(text); + String title = getString(R.string.title_share_message); + + // we don't want to decrypt the decrypted, no inception ;) + String[] blacklist = new String[]{ + Constants.PACKAGE_NAME + ".ui.DecryptTextActivity", + "org.thialfihar.android.apg.ui.DecryptActivity" + }; + + return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist); + } + + private Intent createSendIntent(String text) { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, text); + sendIntent.setType("text/plain"); + return sendIntent; + } + + private void copyToClipboard(String text) { + ClipboardReflection.copyToClipboard(getActivity(), text); + Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + + Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; + mCiphertext = args.getString(ARG_CIPHERTEXT); + mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false); + + if (savedInstanceState == null) { + cryptoOperation(); + } + + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putString(ARG_CIPHERTEXT, mCiphertext); + outState.putBoolean(ARG_SHOW_MENU, mShowMenuOptions); + // no need to save the decrypted text, it's in the textview + + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + if (mShowMenuOptions) { + inflater.inflate(R.menu.decrypt_menu, menu); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.decrypt_share: { + startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString())); + break; + } + case R.id.decrypt_copy: { + copyToClipboard(mText.getText().toString()); + break; + } + default: { + return super.onOptionsItemSelected(item); + } + } + + return true; + } + + @Override + protected PgpDecryptVerifyInputParcel createOperationInput() { + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes()); + input.setAllowSymmetricDecryption(true); + return input; + } + + @Override + protected void onVerifyLoaded(boolean hideErrorOverlay) { + mShowMenuOptions = hideErrorOverlay; + getActivity().supportInvalidateOptionsMenu(); + } + + @Override + protected void onCryptoOperationSuccess(DecryptVerifyResult result) { + + byte[] decryptedMessage = result.getOutputBytes(); + String displayMessage; + if (result.getCharset() != null) { + try { + displayMessage = new String(decryptedMessage, result.getCharset()); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 + displayMessage = new String(decryptedMessage); + } + } else { + displayMessage = new String(decryptedMessage); + } + mText.setText(displayMessage); + + // display signature result in activity + loadVerifyResult(result); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index a6fad8881..c4007b214 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -74,8 +74,8 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent filesDecrypt = new Intent(getActivity(), DecryptFilesActivity.class); - filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN); + Intent filesDecrypt = new Intent(getActivity(), DecryptActivity.class); + filesDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_DATA_OPEN); startActivity(filesDecrypt); } }); @@ -83,8 +83,8 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent clipboardDecrypt = new Intent(getActivity(), DecryptTextActivity.class); - clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD); + Intent clipboardDecrypt = new Intent(getActivity(), DisplayTextActivity.class); + clipboardDecrypt.setAction(DisplayTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD); startActivityForResult(clipboardDecrypt, 0); } }); -- cgit v1.2.3 From d1ceb73eb0e995dcddbfe4bbafa0cf4b5d1371f0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 19 Jun 2015 18:53:17 +0200 Subject: save instance state in DecryptListFragment --- .../keychain/ui/DecryptListFragment.java | 109 ++++++++++++++++----- .../keychain/util/ParcelableHashMap.java | 63 ++++++++++++ 2 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 65d0fc8cf..d54512076 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -69,11 +69,16 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableHashMap; + public class DecryptListFragment extends CryptoOperationFragment implements OnMenuItemClickListener { - public static final String ARG_URIS = "uris"; + + public static final String ARG_INPUT_URIS = "input_uris"; + public static final String ARG_OUTPUT_URIS = "output_uris"; + public static final String ARG_RESULTS = "results"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; @@ -92,7 +97,7 @@ public class DecryptListFragment DecryptListFragment frag = new DecryptListFragment(); Bundle args = new Bundle(); - args.putParcelableArrayList(ARG_URIS, uris); + args.putParcelableArrayList(ARG_INPUT_URIS, uris); frag.setArguments(args); return frag; @@ -123,14 +128,59 @@ public class DecryptListFragment public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelableArrayList(ARG_URIS, mInputUris); + outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris); + + HashMap results = new HashMap<>(mInputUris.size()); + for (Uri uri : mInputUris) { + if (mPendingInputUris.contains(uri)) { + continue; + } + DecryptVerifyResult result = mAdapter.getItemResult(uri); + if (result != null) { + results.put(uri, result); + } + } + + outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); + outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mOutputUris)); + } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - displayInputUris(getArguments().getParcelableArrayList(ARG_URIS)); + Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + + ArrayList inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); + ParcelableHashMap outputUris = args.getParcelable(ARG_OUTPUT_URIS); + ParcelableHashMap results = args.getParcelable(ARG_RESULTS); + + displayInputUris(inputUris, + outputUris != null ? outputUris.getMap() : null, + results != null ? results.getMap() : null + ); + } + + private void displayInputUris(ArrayList inputUris, HashMap outputUris, + HashMap results) { + + mInputUris = inputUris; + mOutputUris = outputUris != null ? outputUris : new HashMap(inputUris.size()); + + mPendingInputUris = new ArrayList<>(); + + for (Uri uri : inputUris) { + mAdapter.add(uri); + if (results != null && results.containsKey(uri)) { + processResult(uri, results.get(uri)); + } else { + mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); + mPendingInputUris.add(uri); + } + } + + cryptoOperation(); } private String removeEncryptedAppend(String name) { @@ -175,19 +225,6 @@ public class DecryptListFragment } } - private void displayInputUris(ArrayList uris) { - mInputUris = uris; - mOutputUris = new HashMap<>(uris.size()); - for (Uri uri : uris) { - mAdapter.add(uri); - mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); - } - - mPendingInputUris = uris; - - cryptoOperation(); - } - @Override protected void cryptoOperation(CryptoInputParcel cryptoInput) { super.cryptoOperation(cryptoInput, false); @@ -216,9 +253,16 @@ public class DecryptListFragment @Override protected void onCryptoOperationSuccess(DecryptVerifyResult result) { - final Uri uri = mCurrentInputUri; + Uri uri = mCurrentInputUri; mCurrentInputUri = null; + processResult(uri, result); + + cryptoOperation(); + } + + private void processResult(final Uri uri, DecryptVerifyResult result) { + Drawable icon = null; OnClickListener onFileClick = null, onKeyClick = null; @@ -256,7 +300,7 @@ public class DecryptListFragment } Uri outputUri = mOutputUris.get(uri); - Intent intent = new Intent(); + Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, metadata.getMimeType()); activity.startActivity(intent); } @@ -265,8 +309,6 @@ public class DecryptListFragment mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); - cryptoOperation(); - } @Override @@ -380,11 +422,15 @@ public class DecryptListFragment // Depends on inputUri only @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ViewModel viewModel = (ViewModel) o; - return !(mResult != null ? !mResult.equals(viewModel.mResult) - : viewModel.mResult != null); + return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri) + : viewModel.mInputUri != null); } // Depends on inputUri only @@ -439,7 +485,7 @@ public class DecryptListFragment holder.vFilesize.setText(FileHelper.readableFileSize(size)); } - // TODO thumbnail from OpenPgpMetadata + // TODO thumbnail from OpenPgpMetadata? if (model.mIcon != null) { holder.vThumbnail.setImageDrawable(model.mIcon); } else { @@ -485,6 +531,17 @@ public class DecryptListFragment return mDataset.size(); } + public DecryptVerifyResult getItemResult(Uri uri) { + ViewModel model = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(model); + if (pos == -1) { + return null; + } + model = mDataset.get(pos); + + return model.mResult; + } + public void add(Uri uri) { ViewModel newModel = new ViewModel(mContext, uri); mDataset.add(newModel); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java new file mode 100644 index 000000000..fa4081acc --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java @@ -0,0 +1,63 @@ +package org.sufficientlysecure.keychain.util; + + +import java.util.HashMap; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.KeychainApplication; + + +public class ParcelableHashMap implements Parcelable { + + HashMap mInner; + + public ParcelableHashMap(HashMap inner) { + mInner = inner; + } + + protected ParcelableHashMap(@NonNull Parcel in) { + mInner = new HashMap<>(); + ClassLoader loader = KeychainApplication.class.getClassLoader(); + + int num = in.readInt(); + while (num-- > 0) { + K key = in.readParcelable(loader); + V val = in.readParcelable(loader); + mInner.put(key, val); + } + } + + public HashMap getMap() { + return mInner; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mInner.size()); + for (HashMap.Entry entry : mInner.entrySet()) { + parcel.writeParcelable(entry.getKey(), 0); + parcel.writeParcelable(entry.getValue(), 0); + } + } + + public static final Creator CREATOR = new Creator() { + @Override + public ParcelableHashMap createFromParcel(Parcel in) { + return new ParcelableHashMap(in); + } + + @Override + public ParcelableHashMap[] newArray(int size) { + return new ParcelableHashMap[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + +} -- cgit v1.2.3 From ae56f4f90069cfeb7e88f2aa4c858048faf1b154 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 19 Jun 2015 19:45:27 +0200 Subject: handle EXTRA_TEXT in decrypt activity --- .../keychain/ui/DecryptActivity.java | 79 ++++++++++++++++------ 1 file changed, 58 insertions(+), 21 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index aba680807..196cb7f0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import android.app.Activity; @@ -31,6 +33,7 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -79,31 +82,57 @@ public class DecryptActivity extends BaseActivity { String action = intent.getAction(); - // TODO handle ACTION_DECRYPT_FROM_CLIPBOARD - switch (action) { - case Intent.ACTION_SEND: { - // When sending to Keychain Decrypt via share menu - // Binary via content provider (could also be files) - // override uri to get stream from send - action = ACTION_DECRYPT_DATA; - uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); - break; - } + try { + + // TODO handle ACTION_DECRYPT_FROM_CLIPBOARD + switch (action) { + case Intent.ACTION_SEND: { + // When sending to Keychain Decrypt via share menu + // Binary via content provider (could also be files) + // override uri to get stream from send + action = ACTION_DECRYPT_DATA; + + if (intent.hasExtra(Intent.EXTRA_STREAM)) { + uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); + } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { + String text = intent.getStringExtra(Intent.EXTRA_TEXT); + Uri uri = readToTempFile(text); + uris.add(uri); + } + + break; + } + + case Intent.ACTION_SEND_MULTIPLE: { + action = ACTION_DECRYPT_DATA; + + if (intent.hasExtra(Intent.EXTRA_STREAM)) { + uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { + for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) { + Uri uri = readToTempFile(text); + uris.add(uri); + } + } + + break; + } + + case Intent.ACTION_VIEW: + // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) + action = ACTION_DECRYPT_DATA; + + // fallthrough + default: + uris.add(intent.getData()); - case Intent.ACTION_SEND_MULTIPLE: { - action = ACTION_DECRYPT_DATA; - uris.addAll(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)); - break; } - case Intent.ACTION_VIEW: - // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) - action = ACTION_DECRYPT_DATA; - - // fallthrough - default: - uris.add(intent.getData()); + } catch (IOException e) { + Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show(); + finish(); + return; } if (ACTION_DECRYPT_DATA.equals(action)) { @@ -122,6 +151,14 @@ public class DecryptActivity extends BaseActivity { } + public Uri readToTempFile(String text) throws IOException { + Uri tempFile = TemporaryStorageProvider.createFile(this); + OutputStream outStream = getContentResolver().openOutputStream(tempFile); + outStream.write(text.getBytes()); + outStream.close(); + return tempFile; + } + public void displayInputFragment(boolean showOpenDialog) { DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(showOpenDialog); -- cgit v1.2.3 From 36f3887c5fcd53b76343247868fa95e72b2b0044 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 19 Jun 2015 20:09:04 +0200 Subject: handle empty filename in decrypt list --- .../org/sufficientlysecure/keychain/ui/DecryptListFragment.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index d54512076..275212ac8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; @@ -476,7 +477,10 @@ public class DecryptListFragment KeyFormattingUtils.setStatus(mContext, holder, model.mResult); OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); - holder.vFilename.setText(metadata.getFilename()); + + String filename = metadata.getFilename(); + holder.vFilename.setText( + !TextUtils.isEmpty(filename) ? filename : mContext.getString(R.string.filename_unknown)); long size = metadata.getOriginalSize(); if (size == -1 || size == 0) { -- cgit v1.2.3 From b5501eeea6bec74d19bb08abb6c48ea654c0dbd0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 01:35:33 +0200 Subject: working DisplayTextActivity, more input support in DecryptActivity --- .../sufficientlysecure/keychain/pgp/PgpHelper.java | 73 +++++++++++- .../keychain/ui/DecryptActivity.java | 16 ++- .../keychain/ui/DecryptFilesInputFragment.java | 3 - .../keychain/ui/DecryptFragment.java | 58 ++++----- .../keychain/ui/DecryptListFragment.java | 64 +++++++++- .../keychain/ui/DisplayTextActivity.java | 130 ++------------------- .../keychain/ui/DisplayTextFragment.java | 94 ++++++--------- .../ui/EncryptDecryptOverviewFragment.java | 4 +- .../keychain/ui/EncryptFilesFragment.java | 15 +-- 9 files changed, 223 insertions(+), 234 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index d8b86a18c..32718fb4e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -31,6 +32,7 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.security.SecureRandom; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class PgpHelper { @@ -52,9 +54,6 @@ public class PgpHelper { *

* TODO: Does this really help on flash storage? * - * @param context - * @param progressable - * @param file * @throws IOException */ public static void deleteFileSecurely(Context context, Progressable progressable, File file) @@ -78,4 +77,72 @@ public class PgpHelper { raf.close(); file.delete(); } + + /** + * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail + */ + public static String fixPgpMessage(String message) { + // windows newline -> unix newline + message = message.replaceAll("\r\n", "\n"); + // Mac OS before X newline -> unix newline + message = message.replaceAll("\r", "\n"); + + // remove whitespaces before newline + message = message.replaceAll(" +\n", "\n"); + // only two consecutive newlines are allowed + message = message.replaceAll("\n\n+", "\n\n"); + + // replace non breakable spaces + message = message.replaceAll("\\xa0", " "); + + return message; + } + + /** + * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail + */ + public static String fixPgpCleartextSignature(CharSequence input) { + if (!TextUtils.isEmpty(input)) { + String text = input.toString(); + + // windows newline -> unix newline + text = text.replaceAll("\r\n", "\n"); + // Mac OS before X newline -> unix newline + text = text.replaceAll("\r", "\n"); + + return text; + } else { + return null; + } + } + + public static String getPgpContent(CharSequence input) { + // only decrypt if clipboard content is available and a pgp message or cleartext signature + if (!TextUtils.isEmpty(input)) { + Log.dEscaped(Constants.TAG, "input: " + input); + + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); + if (matcher.matches()) { + String text = matcher.group(1); + text = fixPgpMessage(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; + } else { + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); + if (matcher.matches()) { + String text = matcher.group(1); + text = fixPgpCleartextSignature(text); + + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; + } else { + return null; + } + } + } else { + return null; + } + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 196cb7f0a..07e227d55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -32,7 +32,9 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -42,7 +44,8 @@ public class DecryptActivity extends BaseActivity { /* Intents */ public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA; // TODO handle this intent - public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; + // public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; + public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_DATA_CLIPBOARD"; // intern public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN"; @@ -118,6 +121,17 @@ public class DecryptActivity extends BaseActivity { break; } + case ACTION_DECRYPT_FROM_CLIPBOARD: { + action = ACTION_DECRYPT_DATA; + + CharSequence clipboardText = ClipboardReflection.getClipboardText(this); + String text = PgpHelper.getPgpContent(clipboardText); + Uri uri = readToTempFile(text); + uris.add(uri); + + break; + } + case Intent.ACTION_VIEW: // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) action = ACTION_DECRYPT_DATA; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 9a0d95bb4..93066ee32 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -61,9 +61,6 @@ public class DecryptFilesInputFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_files_input_fragment, container, false); - // hide result view for this fragment - getActivity().findViewById(R.id.result_main_layout).setVisibility(View.GONE); - mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 0626326fc..29ccb0907 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -25,15 +25,18 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.View; +import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; @@ -43,24 +46,19 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; -public abstract class DecryptFragment - extends CachingCryptoOperationFragment - implements LoaderManager.LoaderCallbacks { +public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks { public static final int LOADER_ID_UNIFIED = 0; public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result"; @@ -75,11 +73,9 @@ public abstract class DecryptFragment protected TextView mSignatureEmail; protected TextView mSignatureAction; - private LinearLayout mContentLayout; - private LinearLayout mErrorOverlayLayout; - private OpenPgpSignatureResult mSignatureResult; private DecryptVerifyResult mDecryptVerifyResult; + private ViewAnimator mOverlayAnimator; @Override public void onViewCreated(View view, Bundle savedInstanceState) { @@ -98,18 +94,23 @@ public abstract class DecryptFragment mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action); // Overlay - mContentLayout = (LinearLayout) view.findViewById(R.id.decrypt_content); - mErrorOverlayLayout = (LinearLayout) view.findViewById(R.id.decrypt_error_overlay); + mOverlayAnimator = (ViewAnimator) view; Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button); - vErrorOverlayButton.setOnClickListener(new View.OnClickListener() { + vErrorOverlayButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + mOverlayAnimator.setDisplayedChild(0); } }); } + private void showErrorOverlay(boolean overlay) { + int child = overlay ? 1 : 0; + if (mOverlayAnimator.getDisplayedChild() != child) { + mOverlayAnimator.setDisplayedChild(child); + } + } + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -205,9 +206,6 @@ public abstract class DecryptFragment } } - /** - * @return returns false if signature is invalid, key is revoked or expired. - */ protected void loadVerifyResult(DecryptVerifyResult decryptVerifyResult) { mDecryptVerifyResult = decryptVerifyResult; @@ -227,8 +225,7 @@ public abstract class DecryptFragment getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); @@ -330,10 +327,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); - mErrorOverlayLayout.setVisibility(View.VISIBLE); - mContentLayout.setVisibility(View.GONE); - - onVerifyLoaded(false); + onVerifyLoaded(true); } else if (isExpired) { mSignatureText.setText(R.string.decrypt_result_signature_expired_key); @@ -342,8 +336,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); @@ -355,8 +348,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); @@ -367,8 +359,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); @@ -379,8 +370,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); } @@ -438,8 +428,7 @@ public abstract class DecryptFragment } }); - mErrorOverlayLayout.setVisibility(View.GONE); - mContentLayout.setVisibility(View.VISIBLE); + showErrorOverlay(false); onVerifyLoaded(true); @@ -452,8 +441,7 @@ public abstract class DecryptFragment setSignatureLayoutVisibility(View.GONE); - mErrorOverlayLayout.setVisibility(View.VISIBLE); - mContentLayout.setVisibility(View.GONE); + showErrorOverlay(true); onVerifyLoaded(false); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 275212ac8..dd6de9c40 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -18,7 +18,13 @@ package org.sufficientlysecure.keychain.ui; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -262,7 +268,7 @@ public class DecryptListFragment cryptoOperation(); } - private void processResult(final Uri uri, DecryptVerifyResult result) { + private void processResult(final Uri uri, final DecryptVerifyResult result) { Drawable icon = null; OnClickListener onFileClick = null, onKeyClick = null; @@ -301,9 +307,59 @@ public class DecryptListFragment } Uri outputUri = mOutputUris.get(uri); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(outputUri, metadata.getMimeType()); - activity.startActivity(intent); + + if ("text/plain".equals(metadata.getMimeType())) { + + Intent intent = new Intent(activity, DisplayTextActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); + intent.setDataAndType(outputUri, "text/plain"); + + try { + + byte[] decryptedMessage; + { + InputStream in = activity.getContentResolver().openInputStream(outputUri); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[256]; + int read; + while ( (read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + in.close(); + out.close(); + decryptedMessage = out.toByteArray(); + } + + String plaintext; + if (result.getCharset() != null) { + try { + plaintext = new String(decryptedMessage, result.getCharset()); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 + plaintext = new String(decryptedMessage); + } + } else { + plaintext = new String(decryptedMessage); + } + + intent.putExtra(Intent.EXTRA_TEXT, plaintext); + + } catch (IOException e) { + Notify.create(activity, "error", Style.ERROR).show(); + return; + } + + activity.startActivity(intent); + + } else { + Intent intent = new Intent(activity, DisplayTextActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); + intent.setDataAndType(outputUri, metadata.getMimeType()); + activity.startActivity(intent); + } + } }; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index 5c44e345e..aacb55a58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; @@ -43,11 +44,7 @@ public class DisplayTextActivity extends BaseActivity { // TODO make this only display text (maybe we need only the fragment?) /* Intents */ - public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; - public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT; - - // intern - public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD"; + public static final String EXTRA_METADATA = OpenKeychainIntents.DECRYPT_EXTRA_METADATA; @Override public void onCreate(Bundle savedInstanceState) { @@ -70,73 +67,6 @@ public class DisplayTextActivity extends BaseActivity { setContentView(R.layout.decrypt_text_activity); } - /** - * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail - */ - private String fixPgpMessage(String message) { - // windows newline -> unix newline - message = message.replaceAll("\r\n", "\n"); - // Mac OS before X newline -> unix newline - message = message.replaceAll("\r", "\n"); - - // remove whitespaces before newline - message = message.replaceAll(" +\n", "\n"); - // only two consecutive newlines are allowed - message = message.replaceAll("\n\n+", "\n\n"); - - // replace non breakable spaces - message = message.replaceAll("\\xa0", " "); - - return message; - } - - /** - * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail - */ - private String fixPgpCleartextSignature(CharSequence input) { - if (!TextUtils.isEmpty(input)) { - String text = input.toString(); - - // windows newline -> unix newline - text = text.replaceAll("\r\n", "\n"); - // Mac OS before X newline -> unix newline - text = text.replaceAll("\r", "\n"); - - return text; - } else { - return null; - } - } - - private String getPgpContent(CharSequence input) { - // only decrypt if clipboard content is available and a pgp message or cleartext signature - if (!TextUtils.isEmpty(input)) { - Log.dEscaped(Constants.TAG, "input: " + input); - - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); - if (matcher.matches()) { - String text = matcher.group(1); - text = fixPgpMessage(text); - - Log.dEscaped(Constants.TAG, "input fixed: " + text); - return text; - } else { - matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); - if (matcher.matches()) { - String text = matcher.group(1); - text = fixPgpCleartextSignature(text); - - Log.dEscaped(Constants.TAG, "input fixed: " + text); - return text; - } else { - return null; - } - } - } else { - return null; - } - } - /** * Handles all actions with this intent */ @@ -153,53 +83,15 @@ public class DisplayTextActivity extends BaseActivity { return; } - if (Intent.ACTION_SEND.equals(action) && type != null) { - Log.d(Constants.TAG, "ACTION_SEND"); - Log.logDebugBundle(extras, "SEND extras"); - - // When sending to Keychain Decrypt via share menu - if ("text/plain".equals(type)) { - String sharedText = extras.getString(Intent.EXTRA_TEXT); - sharedText = getPgpContent(sharedText); - - if (sharedText != null) { - loadFragment(sharedText); - } else { - Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else { - Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else if (ACTION_DECRYPT_TEXT.equals(action)) { - Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); - - String extraText = extras.getString(EXTRA_TEXT); - extraText = getPgpContent(extraText); + Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); - if (extraText != null) { - loadFragment(extraText); - } else { - Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); - Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); - finish(); - } - } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) { - Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); + DecryptVerifyResult result = extras.getParcelable(EXTRA_METADATA); + String plaintext = extras.getString(Intent.EXTRA_TEXT); - CharSequence clipboardText = ClipboardReflection.getClipboardText(this); - String text = getPgpContent(clipboardText); - - if (text != null) { - loadFragment(text); - } else { - returnInvalidResult(); - } - } else if (ACTION_DECRYPT_TEXT.equals(action)) { - Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); + if (plaintext != null) { + loadFragment(plaintext, result); + } else { + Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); finish(); } @@ -214,9 +106,9 @@ public class DisplayTextActivity extends BaseActivity { finish(); } - private void loadFragment(String ciphertext) { + private void loadFragment(String plaintext, DecryptVerifyResult result) { // Create an instance of the fragment - Fragment frag = DisplayTextFragment.newInstance(ciphertext); + Fragment frag = DisplayTextFragment.newInstance(plaintext, result); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index cd75e2bc3..e252f8e75 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -31,45 +31,32 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ShareHelper; -import java.io.UnsupportedEncodingException; - public class DisplayTextFragment extends DecryptFragment { - public static final String ARG_CIPHERTEXT = "ciphertext"; + public static final String ARG_PLAINTEXT = "ciphertext"; public static final String ARG_SHOW_MENU = "show_menu"; // view private TextView mText; // model - private String mCiphertext; private boolean mShowMenuOptions; + private String mPlaintext; - public static DisplayTextFragment newInstance(String ciphertext) { + public static DisplayTextFragment newInstance(String plaintext, DecryptVerifyResult result) { DisplayTextFragment frag = new DisplayTextFragment(); Bundle args = new Bundle(); - args.putString(ARG_CIPHERTEXT, ciphertext); + args.putString(ARG_PLAINTEXT, plaintext); + args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result); frag.setArguments(args); return frag; } - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); - mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); - - return view; - } - /** * Create Intent Chooser but exclude decrypt activites */ @@ -79,7 +66,7 @@ public class DisplayTextFragment extends DecryptFragment { // we don't want to decrypt the decrypted, no inception ;) String[] blacklist = new String[]{ - Constants.PACKAGE_NAME + ".ui.DecryptTextActivity", + Constants.PACKAGE_NAME + ".ui.DecryptActivity", "org.thialfihar.android.apg.ui.DecryptActivity" }; @@ -104,13 +91,30 @@ public class DisplayTextFragment extends DecryptFragment { setHasOptionsMenu(true); - Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; - mCiphertext = args.getString(ARG_CIPHERTEXT); + Bundle args = getArguments(); mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false); - if (savedInstanceState == null) { - cryptoOperation(); - } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); + mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + Bundle args = getArguments(); + + String plaintext = args.getString(ARG_PLAINTEXT); + DecryptVerifyResult result = args.getParcelable(ARG_DECRYPT_VERIFY_RESULT); + + // display signature result in activity + mText.setText(plaintext); + loadVerifyResult(result); } @@ -118,12 +122,17 @@ public class DisplayTextFragment extends DecryptFragment { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(ARG_CIPHERTEXT, mCiphertext); outState.putBoolean(ARG_SHOW_MENU, mShowMenuOptions); // no need to save the decrypted text, it's in the textview } + @Override + protected void onVerifyLoaded(boolean hideErrorOverlay) { + mShowMenuOptions = hideErrorOverlay; + getActivity().supportInvalidateOptionsMenu(); + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -151,39 +160,4 @@ public class DisplayTextFragment extends DecryptFragment { return true; } - @Override - protected PgpDecryptVerifyInputParcel createOperationInput() { - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes()); - input.setAllowSymmetricDecryption(true); - return input; - } - - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - mShowMenuOptions = hideErrorOverlay; - getActivity().supportInvalidateOptionsMenu(); - } - - @Override - protected void onCryptoOperationSuccess(DecryptVerifyResult result) { - - byte[] decryptedMessage = result.getOutputBytes(); - String displayMessage; - if (result.getCharset() != null) { - try { - displayMessage = new String(decryptedMessage, result.getCharset()); - } catch (UnsupportedEncodingException e) { - // if we can't decode properly, just fall back to utf-8 - displayMessage = new String(decryptedMessage); - } - } else { - displayMessage = new String(decryptedMessage); - } - mText.setText(displayMessage); - - // display signature result in activity - loadVerifyResult(result); - - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index c4007b214..6e5589940 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -83,8 +83,8 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent clipboardDecrypt = new Intent(getActivity(), DisplayTextActivity.class); - clipboardDecrypt.setAction(DisplayTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD); + Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class); + clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD); startActivityForResult(clipboardDecrypt, 0); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 3b972a1ae..7623e5cb8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -17,6 +17,14 @@ package org.sufficientlysecure.keychain.ui; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -59,13 +67,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ShareHelper; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - public class EncryptFilesFragment extends CachingCryptoOperationFragment { -- cgit v1.2.3 From 25beeaceb59f9d536d2d9408fba47f9fc3700627 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 03:22:26 +0200 Subject: more work on decrypt fragment ux --- .../keychain/ui/DecryptListFragment.java | 169 ++++++++++++++------- .../keychain/ui/DisplayTextActivity.java | 36 +---- .../keychain/ui/DisplayTextFragment.java | 22 +-- .../keychain/util/ShareHelper.java | 1 + 4 files changed, 124 insertions(+), 104 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index dd6de9c40..65bcd022d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -18,10 +18,8 @@ package org.sufficientlysecure.keychain.ui; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -32,11 +30,14 @@ import java.util.List; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.LabeledIntent; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Parcelable; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -58,14 +59,15 @@ import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; @@ -122,6 +124,7 @@ public class DecryptListFragment vFilesList.addItemDecoration(new SpacesItemDecoration( FormattingUtils.dpToPx(getActivity(), 4))); vFilesList.setHasFixedSize(true); + // TODO make this a grid, for tablets! vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); vFilesList.setItemAnimator(new DefaultItemAnimator()); @@ -297,74 +300,127 @@ public class DecryptListFragment } if (result.success() && result.getDecryptMetadata() != null) { - final OpenPgpMetadata metadata = result.getDecryptMetadata(); onFileClick = new OnClickListener() { @Override public void onClick(View view) { + displayUri(uri); + } + }; + } + + mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + + } + + public void displayUri(final Uri uri) { + Activity activity = getActivity(); + if (activity == null || mCurrentInputUri != null) { + return; + } + + final Uri outputUri = mOutputUris.get(uri); + final DecryptVerifyResult result = mAdapter.getItemResult(uri); + if (outputUri == null || result == null) { + return; + } + + final OpenPgpMetadata metadata = result.getDecryptMetadata(); + + // text/plain is a special case where we extract the uri content into + // the EXTRA_TEXT extra ourselves, and display a chooser which includes + // OpenKeychain's internal viewer + if ("text/plain".equals(metadata.getMimeType())) { + + // this is a significant i/o operation, use an asynctask + new AsyncTask() { + + @Override + protected Intent doInBackground(Void... params) { + Activity activity = getActivity(); - if (activity == null || mCurrentInputUri != null) { - return; + if (activity == null) { + return null; } - Uri outputUri = mOutputUris.get(uri); - - if ("text/plain".equals(metadata.getMimeType())) { - - Intent intent = new Intent(activity, DisplayTextActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); - intent.setDataAndType(outputUri, "text/plain"); - - try { - - byte[] decryptedMessage; - { - InputStream in = activity.getContentResolver().openInputStream(outputUri); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buf = new byte[256]; - int read; - while ( (read = in.read(buf)) > 0) { - out.write(buf, 0, read); - } - in.close(); - out.close(); - decryptedMessage = out.toByteArray(); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); + intent.setDataAndType(outputUri, "text/plain"); + + try { + + byte[] decryptedMessage; + { + InputStream in = activity.getContentResolver().openInputStream(outputUri); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[256]; + int read; + while ( (read = in.read(buf)) > 0) { + out.write(buf, 0, read); } + in.close(); + out.close(); + decryptedMessage = out.toByteArray(); + } - String plaintext; - if (result.getCharset() != null) { - try { - plaintext = new String(decryptedMessage, result.getCharset()); - } catch (UnsupportedEncodingException e) { - // if we can't decode properly, just fall back to utf-8 - plaintext = new String(decryptedMessage); - } - } else { + String plaintext; + if (result.getCharset() != null) { + try { + plaintext = new String(decryptedMessage, result.getCharset()); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 plaintext = new String(decryptedMessage); } + } else { + plaintext = new String(decryptedMessage); + } - intent.putExtra(Intent.EXTRA_TEXT, plaintext); + intent.putExtra(Intent.EXTRA_TEXT, plaintext); - } catch (IOException e) { - Notify.create(activity, "error", Style.ERROR).show(); - return; - } + } catch (IOException e) { + Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show(); + return null; + } - activity.startActivity(intent); + return intent; + } - } else { - Intent intent = new Intent(activity, DisplayTextActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); - intent.setDataAndType(outputUri, metadata.getMimeType()); - activity.startActivity(intent); + @Override + protected void onPostExecute(Intent intent) { + // for result so we can possibly get a snackbar error from internal viewer + Activity activity = getActivity(); + if (intent == null || activity == null) { + return; } + LabeledIntent internalIntent = new LabeledIntent( + new Intent(intent).setClass(activity, DisplayTextActivity.class), + BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher); + + Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, + new Parcelable[] { internalIntent }); + + activity.startActivity(chooserIntent); } - }; - } - mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + }.execute(); + + + } else { + Intent intent = new Intent(activity, DisplayTextActivity.class); + intent.setAction(Intent.ACTION_VIEW); + + // put output uri as stream, and grant permission to other apps to use it + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(Intent.EXTRA_STREAM, outputUri); + intent.setType(metadata.getMimeType()); + + // put metadata, although this is not likely to be used + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); + + Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); + activity.startActivity(chooserIntent); + } } @@ -535,8 +591,11 @@ public class DecryptListFragment OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); String filename = metadata.getFilename(); - holder.vFilename.setText( - !TextUtils.isEmpty(filename) ? filename : mContext.getString(R.string.filename_unknown)); + if (TextUtils.isEmpty(filename)) { + filename = mContext.getString("text/plain".equals(metadata.getMimeType()) + ? R.string.filename_unknown_text : R.string.filename_unknown); + } + holder.vFilename.setText(filename); long size = metadata.getOriginalSize(); if (size == -1 || size == 0) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index aacb55a58..5f04eb43b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -18,32 +18,23 @@ package org.sufficientlysecure.keychain.ui; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.text.TextUtils; import android.view.View; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; -import java.util.regex.Matcher; - public class DisplayTextActivity extends BaseActivity { - // TODO make this only display text (maybe we need only the fragment?) - - /* Intents */ public static final String EXTRA_METADATA = OpenKeychainIntents.DECRYPT_EXTRA_METADATA; @Override @@ -71,41 +62,24 @@ public class DisplayTextActivity extends BaseActivity { * Handles all actions with this intent */ private void handleActions(Bundle savedInstanceState, Intent intent) { - String action = intent.getAction(); - Bundle extras = intent.getExtras(); - String type = intent.getType(); - - if (extras == null) { - extras = new Bundle(); - } - if (savedInstanceState != null) { return; } Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); - DecryptVerifyResult result = extras.getParcelable(EXTRA_METADATA); - String plaintext = extras.getString(Intent.EXTRA_TEXT); + DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA); + String plaintext = intent.getStringExtra(Intent.EXTRA_TEXT); - if (plaintext != null) { + if (plaintext != null && result != null) { loadFragment(plaintext, result); } else { - Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); + Log.e(Constants.TAG, "Invalid data error!"); Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); finish(); } } - private void returnInvalidResult() { - SingletonResult result = new SingletonResult( - SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_NO_VALID_ENC); - Intent intent = new Intent(); - intent.putExtra(SingletonResult.EXTRA_RESULT, result); - setResult(RESULT_OK, intent); - finish(); - } - private void loadFragment(String plaintext, DecryptVerifyResult result) { // Create an instance of the fragment Fragment frag = DisplayTextFragment.newInstance(plaintext, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index e252f8e75..7b3af48cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -35,15 +35,14 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ShareHelper; public class DisplayTextFragment extends DecryptFragment { - public static final String ARG_PLAINTEXT = "ciphertext"; - public static final String ARG_SHOW_MENU = "show_menu"; + + public static final String ARG_PLAINTEXT = "plaintext"; // view private TextView mText; - // model - private boolean mShowMenuOptions; - private String mPlaintext; + // model (no state to persist though, that's all in arguments!) + private boolean mShowMenuOptions = false; public static DisplayTextFragment newInstance(String plaintext, DecryptVerifyResult result) { DisplayTextFragment frag = new DisplayTextFragment(); @@ -90,10 +89,6 @@ public class DisplayTextFragment extends DecryptFragment { super.onCreate(savedInstanceState); setHasOptionsMenu(true); - - Bundle args = getArguments(); - mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false); - } @Override @@ -118,15 +113,6 @@ public class DisplayTextFragment extends DecryptFragment { } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putBoolean(ARG_SHOW_MENU, mShowMenuOptions); - // no need to save the decrypted text, it's in the textview - - } - @Override protected void onVerifyLoaded(boolean hideErrorOverlay) { mShowMenuOptions = hideErrorOverlay; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java index 120b84a3b..0297d149c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java @@ -91,6 +91,7 @@ public class ShareHelper { // Create chooser with only one Intent in it Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title); // append all other Intents + // TODO this line looks wrong?! chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{})); return chooserIntent; } -- cgit v1.2.3 From f6bc1dcca36cd6e5860a413e41ea7c9896c29137 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 03:34:21 +0200 Subject: extract readTextFromUri into FileHelper --- .../keychain/ui/DecryptListFragment.java | 35 ++------------------ .../keychain/util/FileHelper.java | 37 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 32 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 65bcd022d..1199ebc27 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -18,11 +18,8 @@ package org.sufficientlysecure.keychain.ui; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -346,40 +343,14 @@ public class DecryptListFragment intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); intent.setDataAndType(outputUri, "text/plain"); + String plaintext; try { - - byte[] decryptedMessage; - { - InputStream in = activity.getContentResolver().openInputStream(outputUri); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buf = new byte[256]; - int read; - while ( (read = in.read(buf)) > 0) { - out.write(buf, 0, read); - } - in.close(); - out.close(); - decryptedMessage = out.toByteArray(); - } - - String plaintext; - if (result.getCharset() != null) { - try { - plaintext = new String(decryptedMessage, result.getCharset()); - } catch (UnsupportedEncodingException e) { - // if we can't decode properly, just fall back to utf-8 - plaintext = new String(decryptedMessage); - } - } else { - plaintext = new String(decryptedMessage); - } - - intent.putExtra(Intent.EXTRA_TEXT, plaintext); - + plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset()); } catch (IOException e) { Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show(); return null; } + intent.putExtra(Intent.EXTRA_TEXT, plaintext); return intent; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index 677acb1b8..558ab9a85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -41,7 +41,11 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; public class FileHelper { @@ -234,6 +238,39 @@ public class FileHelper { return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } + public static String readTextFromUri(Context context, Uri outputUri, String charset) + throws IOException { + + byte[] decryptedMessage; + { + InputStream in = context.getContentResolver().openInputStream(outputUri); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[256]; + int read; + while ( (read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + in.close(); + out.close(); + decryptedMessage = out.toByteArray(); + } + + String plaintext; + if (charset != null) { + try { + plaintext = new String(decryptedMessage, charset); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 + plaintext = new String(decryptedMessage); + } + } else { + plaintext = new String(decryptedMessage); + } + + return plaintext; + + } + public static interface FileDialogCallback { public void onFileSelected(File file, boolean checked); } -- cgit v1.2.3 From d11b7b58f1835e1057b17304da71ca9322251681 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 03:34:51 +0200 Subject: allow encrypting to clipboard in EncryptFileFragment --- .../keychain/ui/EncryptFilesFragment.java | 55 +++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 7623e5cb8..93e6f9d24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -49,6 +49,7 @@ import android.widget.TextView; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; @@ -85,7 +86,10 @@ public class EncryptFilesFragment private boolean mEncryptFilenames; private boolean mHiddenRecipients = false; - private boolean mShareAfterEncrypt; + private AfterEncryptAction mAfterEncryptAction; + private enum AfterEncryptAction { + SAVE, SHARE, COPY; + } private ArrayList mOutputUris; @@ -268,12 +272,17 @@ public class EncryptFilesFragment public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.encrypt_save: { - mShareAfterEncrypt = false; + mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(); break; } case R.id.encrypt_share: { - mShareAfterEncrypt = true; + mAfterEncryptAction = AfterEncryptAction.SHARE; + cryptoOperation(); + break; + } + case R.id.encrypt_copy: { + mAfterEncryptAction = AfterEncryptAction.COPY; cryptoOperation(); break; } @@ -376,13 +385,14 @@ public class EncryptFilesFragment protected void onCryptoOperationSuccess(final SignEncryptResult result) { if (mDeleteAfterEncrypt) { + // TODO make behavior coherent here DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList()); deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { @Override public void onDeleted() { - if (mShareAfterEncrypt) { + if (mAfterEncryptAction == AfterEncryptAction.SHARE) { // Share encrypted message/file startActivity(sendWithChooserExcludingEncrypt()); } else { @@ -394,12 +404,24 @@ public class EncryptFilesFragment }); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } else { - if (mShareAfterEncrypt) { - // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt()); - } else { - // Save encrypted file - result.createNotify(getActivity()).show(); + + switch (mAfterEncryptAction) { + + case SHARE: + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt()); + break; + + case COPY: + byte[] resultBytes = result.getResultBytes(); + ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes)); + result.createNotify(getActivity()).show(); + break; + + case SAVE: + // Encrypted file was saved already, just show notification + result.createNotify(getActivity()).show(); + break; } } @@ -408,19 +430,19 @@ public class EncryptFilesFragment // prepares mOutputUris, either directly and returns false, or indirectly // which returns true and will call cryptoOperation after mOutputUris has // been set at a later point. - private boolean prepareOutputStreams(boolean share) { + private boolean prepareOutputStreams() { if (mFilesModels.isEmpty()) { Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR) .show(this); return true; - } else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) { - Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt"); + } else if (mFilesModels.size() > 1 && mAfterEncryptAction != AfterEncryptAction.SHARE) { + Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !afterEncryptAction"); // This should be impossible... return true; } - if (share) { + if (mAfterEncryptAction == AfterEncryptAction.SHARE) { mOutputUris = new ArrayList<>(); int filenameCounter = 1; for (FilesAdapter.ViewModel model : mFilesModels) { @@ -467,7 +489,7 @@ public class EncryptFilesFragment // if this is still null, prepare output streams again if (mOutputUris == null) { // this may interrupt the flow, and call us again from onActivityResult - if (prepareOutputStreams(mShareAfterEncrypt)) { + if (prepareOutputStreams()) { return null; } } @@ -600,7 +622,8 @@ public class EncryptFilesFragment if (resultCode == Activity.RESULT_OK && data != null) { mOutputUris = new ArrayList<>(1); mOutputUris.add(data.getData()); - mShareAfterEncrypt = false; + // make sure this is correct! + mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(); } return; -- cgit v1.2.3 From 3b791f66334178c5fb7682bcef9d59e0dc869245 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 05:03:40 +0200 Subject: add mimetype and streamtype support in TemporaryStorageProvider --- .../provider/TemporaryStorageProvider.java | 56 ++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index 74f7c6513..ca79f2027 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -18,14 +18,17 @@ package org.sufficientlysecure.keychain.provider; +import android.content.ClipDescription; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; +import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; @@ -45,12 +48,20 @@ public class TemporaryStorageProvider extends ContentProvider { private static final String COLUMN_ID = "id"; private static final String COLUMN_NAME = "name"; private static final String COLUMN_TIME = "time"; + private static final String COLUMN_TYPE = "mimetype"; public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY; private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY); - private static final int DB_VERSION = 2; + private static final int DB_VERSION = 3; private static File cacheDir; + public static Uri createFile(Context context, String targetName, String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(COLUMN_NAME, targetName); + contentValues.put(COLUMN_TYPE, mimeType); + return context.getContentResolver().insert(BASE_URI, contentValues); + } + public static Uri createFile(Context context, String targetName) { ContentValues contentValues = new ContentValues(); contentValues.put(COLUMN_NAME, targetName); @@ -78,6 +89,7 @@ public class TemporaryStorageProvider extends ContentProvider { db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + COLUMN_ID + " TEXT PRIMARY KEY, " + COLUMN_NAME + " TEXT, " + + COLUMN_TYPE + " TEXT, " + COLUMN_TIME + " INTEGER" + ");"); } @@ -94,6 +106,8 @@ public class TemporaryStorageProvider extends ContentProvider { COLUMN_NAME + " TEXT, " + COLUMN_TIME + " INTEGER" + ");"); + case 2: + db.execSQL("ALTER TABLE files ADD COLUMN " + COLUMN_TYPE + " TEXT"); } } } @@ -144,11 +158,32 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public String getType(Uri uri) { - // Note: If we can find a files mime type, we can decrypt it to temp storage and open it after - // encryption. The mime type is needed, else UI really sucks and some apps break. + Cursor cursor = db.getReadableDatabase().query(TABLE_FILES, + new String[]{ COLUMN_TYPE }, COLUMN_ID + "=?", + new String[]{ uri.getLastPathSegment() }, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + if (!cursor.isNull(0)) { + return cursor.getString(0); + } + } + } finally { + cursor.close(); + } + } return "*/*"; } + @Override + public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { + String type = getType(uri); + if (ClipDescription.compareMimeTypes(type, mimeTypeFilter)) { + return new String[] { type }; + } + return null; + } + @Override public Uri insert(Uri uri, ContentValues values) { if (!values.containsKey(COLUMN_TIME)) { @@ -183,9 +218,22 @@ public class TemporaryStorageProvider extends ContentProvider { return 0; } + public int setMimeType(Uri uri, String mimetype) { + ContentValues values = new ContentValues(); + values.put(COLUMN_TYPE, mimetype); + return update(uri, values, null, null); + } + @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("Update not supported"); + if (values.size() != 1 || !values.containsKey(COLUMN_TYPE)) { + throw new UnsupportedOperationException("Update supported only for type field!"); + } + if (selection != null || selectionArgs != null) { + throw new UnsupportedOperationException("Update supported only for plain uri!"); + } + return db.getWritableDatabase().update(TABLE_FILES, values, + COLUMN_ID + " = ?", new String[]{ uri.getLastPathSegment() }); } @Override -- cgit v1.2.3 From 09da00d80010412d2afcc75c5ab4a98781f8c937 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 05:08:49 +0200 Subject: drop clipboard backwards compatibility for api level < 11 --- .../compatibility/ClipboardReflection.java | 74 ++++---------------- .../keychain/ui/EncryptFilesFragment.java | 78 ++++++++++++++-------- 2 files changed, 66 insertions(+), 86 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index 2f2838f70..0ac27833c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.compatibility; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import org.sufficientlysecure.keychain.Constants; @@ -28,72 +30,24 @@ public class ClipboardReflection { private static final String clipboardLabel = "Keychain"; - /** - * Wrapper around ClipboardManager based on Android version using Reflection API - * - * @param context - * @param text - */ public static void copyToClipboard(Context context, String text) { - Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); - try { - if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { - Method methodSetText = clipboard.getClass() - .getMethod("setText", CharSequence.class); - methodSetText.invoke(clipboard, text); - } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { - Class classClipData = Class.forName("android.content.ClipData"); - Method methodNewPlainText = classClipData.getMethod("newPlainText", - CharSequence.class, CharSequence.class); - Object clip = methodNewPlainText.invoke(null, clipboardLabel, text); - methodNewPlainText = clipboard.getClass() - .getMethod("setPrimaryClip", classClipData); - methodNewPlainText.invoke(clipboard, clip); - } - } catch (Exception e) { - Log.e(Constants.TAG, "There was an error copying the text to the clipboard", e); - } - } - - /** - * Wrapper around ClipboardManager based on Android version using Reflection API - * - * @param context - */ - public static CharSequence getClipboardText(Context context) { - Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); - try { - if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { - // CharSequence text = clipboard.getText(); - Method methodGetText = clipboard.getClass().getMethod("getText"); - Object text = methodGetText.invoke(clipboard); + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - return (CharSequence) text; - } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { - // ClipData clipData = clipboard.getPrimaryClip(); - Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip"); - Object clipData = methodGetPrimaryClip.invoke(clipboard); + ClipData clip = ClipData.newPlainText(clipboardLabel, text); + clipboard.setPrimaryClip(clip); - if (clipData == null) { - return null; - } - - // ClipData.Item clipDataItem = clipData.getItemAt(0); - Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class); - Object clipDataItem = methodGetItemAt.invoke(clipData, 0); + } - // CharSequence text = clipDataItem.coerceToText(context); - Method methodGetString = clipDataItem.getClass().getMethod("coerceToText", - Context.class); - Object text = methodGetString.invoke(clipDataItem, context); + public static CharSequence getClipboardText(Context context) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - return (CharSequence) text; - } else { - return null; - } - } catch (Exception e) { - Log.e(Constants.TAG, "There was an error getting the text from the clipboard", e); + ClipData clip = clipboard.getPrimaryClip(); + if (clip == null || clip.getItemCount() == 0) { + Log.e(Constants.TAG, "No clipboard data!"); return null; } + + ClipData.Item item = clip.getItemAt(0); + return item.coerceToText(context); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 93e6f9d24..57c82ef9c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Set; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -413,8 +415,20 @@ public class EncryptFilesFragment break; case COPY: - byte[] resultBytes = result.getResultBytes(); - ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes)); + Activity activity = getActivity(); + if (activity == null) { + // it's gone, there's nothing we can do here + return; + } + + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + // ClipData clip = ClipData.newUri(getActivity().getContentResolver(), + // getString(R.string.label_clip_title), mOutputUris.get(0)); + ClipData clip = new ClipData(getString(R.string.label_clip_title), + new String[] { "text/plain" }, + new ClipData.Item(mOutputUris.get(0)) + ); + clipMan.setPrimaryClip(clip); result.createNotify(getActivity()).show(); break; @@ -436,32 +450,44 @@ public class EncryptFilesFragment Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR) .show(this); return true; - } else if (mFilesModels.size() > 1 && mAfterEncryptAction != AfterEncryptAction.SHARE) { - Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !afterEncryptAction"); - // This should be impossible... - return true; } - if (mAfterEncryptAction == AfterEncryptAction.SHARE) { - mOutputUris = new ArrayList<>(); - int filenameCounter = 1; - for (FilesAdapter.ViewModel model : mFilesModels) { - String targetName = - (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) - + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); - mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); - filenameCounter++; - } - return false; - } else { - if (mFilesModels.size() > 1) { - Notify.create(getActivity(), R.string.error_multi_not_supported, - Notify.Style.ERROR).show(this); + switch (mAfterEncryptAction) { + default: + case SHARE: + mOutputUris = new ArrayList<>(); + int filenameCounter = 1; + for (FilesAdapter.ViewModel model : mFilesModels) { + String targetName = (mEncryptFilenames + ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) + + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); + filenameCounter++; + } + return false; + + case SAVE: + if (mFilesModels.size() > 1) { + Notify.create(getActivity(), R.string.error_multi_files, Notify.Style.ERROR).show(this); + return true; + } + showOutputFileDialog(); return true; - } - showOutputFileDialog(); - return true; + + case COPY: + // nothing to do here, but make sure + if (mFilesModels.size() > 1) { + Notify.create(getActivity(), R.string.error_multi_clipboard, Notify.Style.ERROR).show(this); + return true; + } + mOutputUris = new ArrayList<>(); + String targetName = (mEncryptFilenames + ? String.valueOf(1) : FileHelper.getFilename(getActivity(), mFilesModels.get(0).inputUri)) + + Constants.FILE_EXTENSION_ASC; + mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName, "text/plain")); + return false; } + } protected SignEncryptParcel createOperationInput() { @@ -516,7 +542,7 @@ public class EncryptFilesFragment data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); } data.setHiddenRecipients(mHiddenRecipients); - data.setEnableAsciiArmorOutput(mUseArmor); + data.setEnableAsciiArmorOutput(mAfterEncryptAction == AfterEncryptAction.COPY || mUseArmor); data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); @@ -622,7 +648,7 @@ public class EncryptFilesFragment if (resultCode == Activity.RESULT_OK && data != null) { mOutputUris = new ArrayList<>(1); mOutputUris.add(data.getData()); - // make sure this is correct! + // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(); } -- cgit v1.2.3 From 22246afa4b05f4cd2ae0f011b9c2d829321a77ec Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 05:29:22 +0200 Subject: use uris for clipboard data if available in DecryptActivity --- .../keychain/ui/DecryptActivity.java | 28 +++++++++++++++++----- .../keychain/ui/EncryptFilesFragment.java | 4 +--- 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 07e227d55..d14c8555c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -22,6 +22,9 @@ import java.io.OutputStream; import java.util.ArrayList; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -32,9 +35,7 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; -import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -87,7 +88,6 @@ public class DecryptActivity extends BaseActivity { try { - // TODO handle ACTION_DECRYPT_FROM_CLIPBOARD switch (action) { case Intent.ACTION_SEND: { // When sending to Keychain Decrypt via share menu @@ -124,9 +124,25 @@ public class DecryptActivity extends BaseActivity { case ACTION_DECRYPT_FROM_CLIPBOARD: { action = ACTION_DECRYPT_DATA; - CharSequence clipboardText = ClipboardReflection.getClipboardText(this); - String text = PgpHelper.getPgpContent(clipboardText); - Uri uri = readToTempFile(text); + ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = clipMan.getPrimaryClip(); + + // check if data is available as uri + Uri uri = null; + for (int i = 0; i < clip.getItemCount(); i++) { + ClipData.Item item = clip.getItemAt(i); + Uri itemUri = item.getUri(); + if (itemUri != null) { + uri = itemUri; + break; + } + } + + // otherwise, coerce to text (almost always possible) and work from there + if (uri == null) { + String text = clip.getItemAt(0).coerceToText(this).toString(); + uri = readToTempFile(text); + } uris.add(uri); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 57c82ef9c..c14f957f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -51,7 +51,6 @@ import android.widget.TextView; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; @@ -422,9 +421,8 @@ public class EncryptFilesFragment } ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); - // ClipData clip = ClipData.newUri(getActivity().getContentResolver(), - // getString(R.string.label_clip_title), mOutputUris.get(0)); ClipData clip = new ClipData(getString(R.string.label_clip_title), + // make available as application/pgp-encrypted new String[] { "text/plain" }, new ClipData.Item(mOutputUris.get(0)) ); -- cgit v1.2.3 From 41357901613449c90179af74c0806978f7959f45 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 07:29:09 +0200 Subject: fix decrypt view intents, save mimetype in storage provider, and thumbnail loading in decrypt list --- .../provider/TemporaryStorageProvider.java | 25 ++--- .../keychain/ui/DecryptListFragment.java | 102 ++++++++++++--------- .../keychain/ui/DisplayTextActivity.java | 19 +++- 3 files changed, 87 insertions(+), 59 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index ca79f2027..fc3d43eaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -18,6 +18,12 @@ package org.sufficientlysecure.keychain.provider; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.UUID; + import android.content.ClipDescription; import android.content.ContentProvider; import android.content.ContentValues; @@ -29,6 +35,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; @@ -36,11 +43,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.DatabaseUtil; import org.sufficientlysecure.keychain.util.Log; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.UUID; - public class TemporaryStorageProvider extends ContentProvider { private static final String DB_NAME = "tempstorage.db"; @@ -73,6 +75,12 @@ public class TemporaryStorageProvider extends ContentProvider { return context.getContentResolver().insert(BASE_URI, contentValues); } + public static int setMimeType(Context context, Uri uri, String mimetype) { + ContentValues values = new ContentValues(); + values.put(COLUMN_TYPE, mimetype); + return context.getContentResolver().update(uri, values, null, null); + } + public static int cleanUp(Context context) { return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?", new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}); @@ -218,12 +226,6 @@ public class TemporaryStorageProvider extends ContentProvider { return 0; } - public int setMimeType(Uri uri, String mimetype) { - ContentValues values = new ContentValues(); - values.put(COLUMN_TYPE, mimetype); - return update(uri, values, null, null); - } - @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { if (values.size() != 1 || !values.containsKey(COLUMN_TYPE)) { @@ -240,4 +242,5 @@ public class TemporaryStorageProvider extends ContentProvider { public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { return openFileHelper(uri, mode); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 1199ebc27..036fbe8c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -25,10 +25,14 @@ import java.util.HashMap; import java.util.List; import android.app.Activity; +import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.pm.LabeledIntent; import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -270,12 +274,54 @@ public class DecryptListFragment private void processResult(final Uri uri, final DecryptVerifyResult result) { - Drawable icon = null; - OnClickListener onFileClick = null, onKeyClick = null; + new AsyncTask() { + @Override + protected Drawable doInBackground(Void... params) { - if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { - icon = loadIcon(result.getDecryptMetadata().getMimeType()); - } + Context context = getActivity(); + if (result.getDecryptMetadata() == null || context == null) { + return null; + } + + String type = result.getDecryptMetadata().getMimeType(); + Uri outputUri = mOutputUris.get(uri); + if (type == null || outputUri == null) { + return null; + } + + TemporaryStorageProvider.setMimeType(context, outputUri, type); + + if (ClipDescription.compareMimeTypes(type, "image/*")) { + int px = FormattingUtils.dpToPx(context, 48); + Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); + return new BitmapDrawable(context.getResources(), bitmap); + } + + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType(type); + + final List matches = + context.getPackageManager().queryIntentActivities(intent, 0); + //noinspection LoopStatementThatDoesntLoop + for (ResolveInfo match : matches) { + return match.loadIcon(getActivity().getPackageManager()); + } + + return null; + + } + + @Override + protected void onPostExecute(Drawable icon) { + processResult(uri, result, icon); + } + }.execute(); + + } + + private void processResult(final Uri uri, DecryptVerifyResult result, Drawable icon) { + + OnClickListener onFileClick = null, onKeyClick = null; OpenPgpSignatureResult sigResult = result.getSignatureResult(); if (sigResult != null) { @@ -300,7 +346,7 @@ public class DecryptListFragment onFileClick = new OnClickListener() { @Override public void onClick(View view) { - displayUri(uri); + displayWithViewIntent(uri); } }; } @@ -309,7 +355,7 @@ public class DecryptListFragment } - public void displayUri(final Uri uri) { + public void displayWithViewIntent(final Uri uri) { Activity activity = getActivity(); if (activity == null || mCurrentInputUri != null) { return; @@ -340,18 +386,7 @@ public class DecryptListFragment } Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); intent.setDataAndType(outputUri, "text/plain"); - - String plaintext; - try { - plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset()); - } catch (IOException e) { - Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show(); - return null; - } - intent.putExtra(Intent.EXTRA_TEXT, plaintext); - return intent; } @@ -364,7 +399,9 @@ public class DecryptListFragment } LabeledIntent internalIntent = new LabeledIntent( - new Intent(intent).setClass(activity, DisplayTextActivity.class), + new Intent(intent) + .setClass(activity, DisplayTextActivity.class) + .putExtra(DisplayTextActivity.EXTRA_METADATA, result), BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); @@ -376,20 +413,13 @@ public class DecryptListFragment }.execute(); - } else { - Intent intent = new Intent(activity, DisplayTextActivity.class); - intent.setAction(Intent.ACTION_VIEW); - - // put output uri as stream, and grant permission to other apps to use it + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(outputUri, metadata.getMimeType()); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.putExtra(Intent.EXTRA_STREAM, outputUri); - intent.setType(metadata.getMimeType()); - - // put metadata, although this is not likely to be used - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); + chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivity(chooserIntent); } @@ -763,18 +793,4 @@ public class DecryptListFragment } } - private Drawable loadIcon(String mimeType) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(mimeType); - - final List matches = getActivity() - .getPackageManager().queryIntentActivities(intent, 0); - //noinspection LoopStatementThatDoesntLoop - for (ResolveInfo match : matches) { - return match.loadIcon(getActivity().getPackageManager()); - } - return null; - - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index 5f04eb43b..80ab30890 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui; +import java.io.IOException; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -31,6 +33,9 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; public class DisplayTextActivity extends BaseActivity { @@ -66,15 +71,19 @@ public class DisplayTextActivity extends BaseActivity { return; } - Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); - DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA); - String plaintext = intent.getStringExtra(Intent.EXTRA_TEXT); - if (plaintext != null && result != null) { + String plaintext; + try { + plaintext = FileHelper.readTextFromUri(this, intent.getData(), result.getCharset()); + } catch (IOException e) { + Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show(); + return; + } + + if (plaintext != null) { loadFragment(plaintext, result); } else { - Log.e(Constants.TAG, "Invalid data error!"); Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); finish(); } -- cgit v1.2.3 From d472e30b4e52c803d8ef73850674f3652b36aded Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 20:04:33 +0200 Subject: instrument: update symmetric tests --- .../java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java | 1 + .../org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 036fbe8c5..1366e5563 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -387,6 +387,7 @@ public class DecryptListFragment Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, "text/plain"); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); return intent; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 11524aa08..a3cd63d13 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -457,8 +457,8 @@ public class KeyFormattingUtils { } int encColorRes = context.getResources().getColor(encColor); - holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon)); holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN); + holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon)); holder.getEncryptionStatusText().setText(encText); holder.getEncryptionStatusText().setTextColor(encColorRes); } @@ -542,8 +542,8 @@ public class KeyFormattingUtils { } int sigColorRes = context.getResources().getColor(sigColor); - holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); + holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); holder.getSignatureStatusText().setText(sigText); holder.getSignatureStatusText().setTextColor(sigColorRes); -- cgit v1.2.3 From 0be790dcad21dc950e803f71185f78ed2e505985 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 21:34:21 +0200 Subject: fix display of signed-only content --- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 1366e5563..dfe2d07aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -592,14 +591,18 @@ public class DecryptListFragment OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); - String filename = metadata.getFilename(); - if (TextUtils.isEmpty(filename)) { + String filename; + if (metadata == null) { + filename = mContext.getString(R.string.filename_unknown); + } else if (TextUtils.isEmpty(metadata.getFilename())) { filename = mContext.getString("text/plain".equals(metadata.getMimeType()) ? R.string.filename_unknown_text : R.string.filename_unknown); + } else { + filename = metadata.getFilename(); } holder.vFilename.setText(filename); - long size = metadata.getOriginalSize(); + long size = metadata == null ? 0 : metadata.getOriginalSize(); if (size == -1 || size == 0) { holder.vFilesize.setText(""); } else { -- cgit v1.2.3 From 675df57abdbf12fc12a5ef06bd727959680bfe38 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 22:29:18 +0200 Subject: don't show "delete original file" for non-file input uris --- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index dfe2d07aa..0c18835fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -445,11 +445,6 @@ public class DecryptListFragment } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - } - @Override public boolean onMenuItemClick(MenuItem menuItem) { if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { @@ -589,7 +584,7 @@ public class DecryptListFragment KeyFormattingUtils.setStatus(mContext, holder, model.mResult); - OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); + final OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); String filename; if (metadata == null) { @@ -626,6 +621,9 @@ public class DecryptListFragment mMenuClickedModel = model; PopupMenu menu = new PopupMenu(mContext, view); menu.inflate(R.menu.decrypt_item_context_menu); + if (!"file".equals(model.mInputUri.getScheme())) { + menu.getMenu().findItem(R.id.decrypt_delete).setVisible(false); + } menu.setOnMenuItemClickListener(mMenuItemClickListener); menu.setOnDismissListener(new OnDismissListener() { @Override -- cgit v1.2.3 From 2786869e1b4cc81097cc7eb5b4d9e17ee5fc2269 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jun 2015 23:40:42 +0200 Subject: update keychain intents submodule (for travis) --- .../java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index 80ab30890..2742e4565 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.util.Log; public class DisplayTextActivity extends BaseActivity { - public static final String EXTRA_METADATA = OpenKeychainIntents.DECRYPT_EXTRA_METADATA; + public static final String EXTRA_METADATA = "metadata"; @Override public void onCreate(Bundle savedInstanceState) { -- cgit v1.2.3 From fe7d13c85f37d18b69b3ffcda28829a4e0ce9f14 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 03:11:46 +0200 Subject: add extra for initial fragment in MainActivity --- .../keychain/ui/MainActivity.java | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index a0f6d0e1b..ec6fd1bbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -45,13 +45,15 @@ import org.sufficientlysecure.keychain.util.Preferences; public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener { - private static final int ID_KEYS = 1; - private static final int ID_ENCRYPT_DECRYPT = 2; - private static final int ID_APPS = 3; - private static final int ID_SETTINGS = 4; - private static final int ID_HELP = 5; + static final int ID_KEYS = 1; + static final int ID_ENCRYPT_DECRYPT = 2; + static final int ID_APPS = 3; + static final int ID_SETTINGS = 4; + static final int ID_HELP = 5; + // both of these are used for instrumentation testing only public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time"; + public static final String EXTRA_INIT_FRAG = "init_frag"; public Drawer.Result mDrawerResult; private Toolbar mToolbar; @@ -134,8 +136,20 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac } if (savedInstanceState == null) { - // initialize FragmentLayout with KeyListFragment at first + // always initialize keys fragment to the bottom of the backstack onKeysSelected(); + + if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { + // initialize FragmentLayout with KeyListFragment at first + switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { + case ID_ENCRYPT_DECRYPT: + onEnDecryptSelected(); + break; + case ID_APPS: + onAppsSelected(); + break; + } + } } } -- cgit v1.2.3 From 361ad99928589b19b2cc1d73b2f27e7060cd21b2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 03:13:55 +0200 Subject: add failure state to DecryptListFragment --- .../keychain/ui/DecryptListFragment.java | 147 +++++++++++++-------- 1 file changed, 91 insertions(+), 56 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 0c18835fd..1c35a8316 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -577,74 +577,104 @@ public class DecryptListFragment // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); - if (model.hasResult()) { - if (holder.vAnimator.getDisplayedChild() != 1) { - holder.vAnimator.setDisplayedChild(1); - } + if (!model.hasResult()) { + bindItemProgress(holder, model); + return; + } - KeyFormattingUtils.setStatus(mContext, holder, model.mResult); + if (model.mResult.success()) { + bindItemSuccess(holder, model); + } else { + bindItemFailure(holder, model); + } - final OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); + } - String filename; - if (metadata == null) { - filename = mContext.getString(R.string.filename_unknown); - } else if (TextUtils.isEmpty(metadata.getFilename())) { - filename = mContext.getString("text/plain".equals(metadata.getMimeType()) - ? R.string.filename_unknown_text : R.string.filename_unknown); - } else { - filename = metadata.getFilename(); - } - holder.vFilename.setText(filename); + private void bindItemProgress(ViewHolder holder, ViewModel model) { + if (holder.vAnimator.getDisplayedChild() != 0) { + holder.vAnimator.setDisplayedChild(0); + } - long size = metadata == null ? 0 : metadata.getOriginalSize(); - if (size == -1 || size == 0) { - holder.vFilesize.setText(""); - } else { - holder.vFilesize.setText(FileHelper.readableFileSize(size)); - } + holder.vProgress.setProgress(model.mProgress); + holder.vProgress.setMax(model.mMax); + holder.vProgressMsg.setText(model.mProgressMsg); + } - // TODO thumbnail from OpenPgpMetadata? - if (model.mIcon != null) { - holder.vThumbnail.setImageDrawable(model.mIcon); - } else { - holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); - } + private void bindItemSuccess(ViewHolder holder, final ViewModel model) { + if (holder.vAnimator.getDisplayedChild() != 1) { + holder.vAnimator.setDisplayedChild(1); + } - holder.vFile.setOnClickListener(model.mOnFileClickListener); - holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + KeyFormattingUtils.setStatus(mContext, holder, model.mResult); - holder.vContextMenu.setTag(model); - holder.vContextMenu.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - mMenuClickedModel = model; - PopupMenu menu = new PopupMenu(mContext, view); - menu.inflate(R.menu.decrypt_item_context_menu); - if (!"file".equals(model.mInputUri.getScheme())) { - menu.getMenu().findItem(R.id.decrypt_delete).setVisible(false); - } - menu.setOnMenuItemClickListener(mMenuItemClickListener); - menu.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(PopupMenu popupMenu) { - mMenuClickedModel = null; - } - }); - menu.show(); - } - }); + final OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); + + String filename; + if (metadata == null) { + filename = mContext.getString(R.string.filename_unknown); + } else if (TextUtils.isEmpty(metadata.getFilename())) { + filename = mContext.getString("text/plain".equals(metadata.getMimeType()) + ? R.string.filename_unknown_text : R.string.filename_unknown); + } else { + filename = metadata.getFilename(); + } + holder.vFilename.setText(filename); + long size = metadata == null ? 0 : metadata.getOriginalSize(); + if (size == -1 || size == 0) { + holder.vFilesize.setText(""); } else { - if (holder.vAnimator.getDisplayedChild() != 0) { - holder.vAnimator.setDisplayedChild(0); + holder.vFilesize.setText(FileHelper.readableFileSize(size)); + } + + // TODO thumbnail from OpenPgpMetadata? + if (model.mIcon != null) { + holder.vThumbnail.setImageDrawable(model.mIcon); + } else { + holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + + holder.vFile.setOnClickListener(model.mOnFileClickListener); + holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + + holder.vContextMenu.setTag(model); + holder.vContextMenu.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + mMenuClickedModel = model; + PopupMenu menu = new PopupMenu(mContext, view); + menu.inflate(R.menu.decrypt_item_context_menu); + if (!"file".equals(model.mInputUri.getScheme())) { + menu.getMenu().findItem(R.id.decrypt_delete).setVisible(false); + } + menu.setOnMenuItemClickListener(mMenuItemClickListener); + menu.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(PopupMenu popupMenu) { + mMenuClickedModel = null; + } + }); + menu.show(); } + }); + } - holder.vProgress.setProgress(model.mProgress); - holder.vProgress.setMax(model.mMax); - holder.vProgressMsg.setText(model.mProgressMsg); + private void bindItemFailure(ViewHolder holder, final ViewModel model) { + if (holder.vAnimator.getDisplayedChild() != 2) { + holder.vAnimator.setDisplayedChild(2); } + holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId()); + + holder.vErrorViewLog.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(mContext, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); + mContext.startActivity(intent); + } + }); + } // Return the size of your dataset (invoked by the layout manager) @@ -719,9 +749,11 @@ public class DecryptListFragment public TextView vSignatureName; public TextView vSignatureMail; public TextView vSignatureAction; - public View vContextMenu; + public TextView vErrorMsg; + public ImageView vErrorViewLog; + public ViewHolder(View itemView) { super(itemView); @@ -747,6 +779,9 @@ public class DecryptListFragment vContextMenu = itemView.findViewById(R.id.context_menu); + vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg); + vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log); + } @Override -- cgit v1.2.3 From 804a58e779e8bbb5c8c2d53211626dcfd5556ed7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 03:16:32 +0200 Subject: instrument: some updates to asymmetric decrypt tests --- .../org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java | 5 ----- .../org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java | 9 +++++++-- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index 4d23ba9f8..d51b6e0ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -50,14 +50,9 @@ public class EncryptFilesActivity extends EncryptActivity { Intent intent = getIntent(); String action = intent.getAction(); - Bundle extras = intent.getExtras(); String type = intent.getType(); ArrayList uris = new ArrayList<>(); - if (extras == null) { - extras = new Bundle(); - } - if (intent.getData() != null) { uris.add(intent.getData()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index c14f957f7..897770411 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -78,7 +78,7 @@ public class EncryptFilesFragment public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor"; public static final String ARG_URIS = "uris"; - private static final int REQUEST_CODE_INPUT = 0x00007003; + public static final int REQUEST_CODE_INPUT = 0x00007003; private static final int REQUEST_CODE_OUTPUT = 0x00007007; private boolean mUseArmor; @@ -229,8 +229,13 @@ public class EncryptFilesFragment String targetName = (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + Uri inputUri = model.inputUri; + saveDocumentIntent(targetName, inputUri); + } + + private void saveDocumentIntent(String targetName, Uri inputUri) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(model.inputUri.getPath()); + File file = new File(inputUri.getPath()); File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; File targetFile = new File(parentDir, targetName); FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), -- cgit v1.2.3 From b2dec8542177b55a966c878e1c405ce7491651f1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 14:27:18 +0200 Subject: multi-decrypt: use indeterminate progress only --- .../org/sufficientlysecure/keychain/ui/DecryptListFragment.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 1c35a8316..422a12a2e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -42,8 +42,6 @@ import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -597,7 +595,9 @@ public class DecryptListFragment holder.vProgress.setProgress(model.mProgress); holder.vProgress.setMax(model.mMax); - holder.vProgressMsg.setText(model.mProgressMsg); + if (model.mProgressMsg != null) { + holder.vProgressMsg.setText(model.mProgressMsg); + } } private void bindItemSuccess(ViewHolder holder, final ViewModel model) { @@ -627,7 +627,6 @@ public class DecryptListFragment holder.vFilesize.setText(FileHelper.readableFileSize(size)); } - // TODO thumbnail from OpenPgpMetadata? if (model.mIcon != null) { holder.vThumbnail.setImageDrawable(model.mIcon); } else { -- cgit v1.2.3 From 5f91c57b4aec211d0cc0b6e0261191a8cde762e3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 15:44:15 +0200 Subject: don't expose dataset for FileAdapter in encrypt view, small improvements to error handling --- .../keychain/ui/EncryptFilesFragment.java | 60 ++++++++++++---------- .../keychain/ui/EncryptTextFragment.java | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 897770411..d7c6b2049 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -96,7 +96,6 @@ public class EncryptFilesFragment private RecyclerView mSelectedFiles; - ArrayList mFilesModels; FilesAdapter mFilesAdapter; /** @@ -134,8 +133,7 @@ public class EncryptFilesFragment mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity())); mSelectedFiles.setItemAnimator(new DefaultItemAnimator()); - mFilesModels = new ArrayList<>(); - mFilesAdapter = new FilesAdapter(getActivity(), mFilesModels, new View.OnClickListener() { + mFilesAdapter = new FilesAdapter(getActivity(), new View.OnClickListener() { @Override public void onClick(View v) { addInputUri(); @@ -199,8 +197,8 @@ public class EncryptFilesFragment if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT); } else { - FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ? - null : mFilesModels.get(mFilesModels.size() - 1).inputUri, + FileHelper.openFile(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ? + null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri, "*/*", REQUEST_CODE_INPUT); } } @@ -222,10 +220,10 @@ public class EncryptFilesFragment } private void showOutputFileDialog() { - if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) { + if (mFilesAdapter.getModelCount() != 1) { throw new IllegalStateException(); } - FilesAdapter.ViewModel model = mFilesModels.get(0); + FilesAdapter.ViewModel model = mFilesAdapter.getModelItem(0); String targetName = (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); @@ -449,18 +447,12 @@ public class EncryptFilesFragment // been set at a later point. private boolean prepareOutputStreams() { - if (mFilesModels.isEmpty()) { - Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR) - .show(this); - return true; - } - switch (mAfterEncryptAction) { default: case SHARE: mOutputUris = new ArrayList<>(); int filenameCounter = 1; - for (FilesAdapter.ViewModel model : mFilesModels) { + for (FilesAdapter.ViewModel model : mFilesAdapter.mDataset) { String targetName = (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); @@ -470,7 +462,7 @@ public class EncryptFilesFragment return false; case SAVE: - if (mFilesModels.size() > 1) { + if (mFilesAdapter.getModelCount() > 1) { Notify.create(getActivity(), R.string.error_multi_files, Notify.Style.ERROR).show(this); return true; } @@ -479,14 +471,14 @@ public class EncryptFilesFragment case COPY: // nothing to do here, but make sure - if (mFilesModels.size() > 1) { + if (mFilesAdapter.getModelCount() > 1) { Notify.create(getActivity(), R.string.error_multi_clipboard, Notify.Style.ERROR).show(this); return true; } mOutputUris = new ArrayList<>(); String targetName = (mEncryptFilenames - ? String.valueOf(1) : FileHelper.getFilename(getActivity(), mFilesModels.get(0).inputUri)) - + Constants.FILE_EXTENSION_ASC; + ? String.valueOf(1) : FileHelper.getFilename(getActivity(), + mFilesAdapter.getModelItem(0).inputUri)) + Constants.FILE_EXTENSION_ASC; mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName, "text/plain")); return false; } @@ -534,6 +526,11 @@ public class EncryptFilesFragment protected SignEncryptParcel createIncompleteCryptoInput() { + if (mFilesAdapter.getModelCount() == 0) { + Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this); + return null; + } + // fill values for this action SignEncryptParcel data = new SignEncryptParcel(); @@ -556,12 +553,14 @@ public class EncryptFilesFragment long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds(); long signingKeyId = modeFragment.getAsymmetricSigningKeyId(); - boolean gotEncryptionKeys = (encryptionKeyIds != null - && encryptionKeyIds.length > 0); + boolean gotEncryptionKeys = (encryptionKeyIds != null && encryptionKeyIds.length > 0); - if (!gotEncryptionKeys && signingKeyId == 0) { - Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR) - .show(this); + if (!gotEncryptionKeys && signingKeyId != 0) { + Notify.create(getActivity(), R.string.error_detached_signature, Notify.Style.ERROR).show(this); + return null; + } + if (!gotEncryptionKeys) { + Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR).show(this); return null; } @@ -741,9 +740,9 @@ public class EncryptFilesFragment } // Provide a suitable constructor (depends on the kind of dataset) - public FilesAdapter(Activity activity, List myDataset, View.OnClickListener onFooterClickListener) { + public FilesAdapter(Activity activity, View.OnClickListener onFooterClickListener) { mActivity = activity; - mDataset = myDataset; + mDataset = new ArrayList<>(); mFooterOnClickListener = onFooterClickListener; } @@ -797,7 +796,8 @@ public class EncryptFilesFragment // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { - return mDataset.size() + 1; + // one extra for the footer! + return mDataset.size() +1; } @Override @@ -837,6 +837,14 @@ public class EncryptFilesFragment } } + public int getModelCount() { + return mDataset.size(); + } + + public ViewModel getModelItem(int position) { + return mDataset.get(position); + } + public void remove(ViewModel model) { int position = mDataset.indexOf(model); mDataset.remove(position); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index d93b52453..e0629eb73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -241,7 +241,7 @@ public class EncryptTextFragment && encryptionKeyIds.length > 0); if (!gotEncryptionKeys && signingKeyId == Constants.key.none) { - Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR) + Notify.create(getActivity(), R.string.error_no_encryption_or_signature_key, Notify.Style.ERROR) .show(this); return null; } -- cgit v1.2.3 From 8d1dc940c4d1e864aad767e298373b02da55f556 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 23:08:03 +0200 Subject: small fix and some cleanup in ServiceProgressFragment --- .../keychain/service/ServiceProgressHandler.java | 44 ++++++++++++++++------ 1 file changed, 32 insertions(+), 12 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 701bfc053..989b0c4bd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java @@ -98,24 +98,15 @@ public class ServiceProgressHandler extends Handler { public void handleMessage(Message message) { Bundle data = message.getData(); - ProgressDialogFragment progressDialogFragment = - (ProgressDialogFragment) mActivity.getSupportFragmentManager() - .findFragmentByTag("progressDialog"); - - if (progressDialogFragment == null) { - Log.e(Constants.TAG, "Progress has not been updated because mProgressDialogFragment was null!"); - return; - } - MessageStatus status = MessageStatus.fromInt(message.arg1); switch (status) { case OKAY: - progressDialogFragment.dismissAllowingStateLoss(); + dismissAllowingStateLoss(); break; case EXCEPTION: - progressDialogFragment.dismissAllowingStateLoss(); + dismissAllowingStateLoss(); // show error from service if (data.containsKey(DATA_ERROR)) { @@ -147,7 +138,7 @@ public class ServiceProgressHandler extends Handler { break; case PREVENT_CANCEL: - progressDialogFragment.setPreventCancel(true); + setPreventCancel(true); break; default: @@ -156,12 +147,41 @@ public class ServiceProgressHandler extends Handler { } } + private void setPreventCancel(boolean preventCancel) { + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (progressDialogFragment == null) { + return; + } + + progressDialogFragment.setPreventCancel(preventCancel); + } + + protected void dismissAllowingStateLoss() { + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (progressDialogFragment == null) { + return; + } + + progressDialogFragment.dismissAllowingStateLoss(); + } + + protected void onSetProgress(String msg, int progress, int max) { ProgressDialogFragment progressDialogFragment = (ProgressDialogFragment) mActivity.getSupportFragmentManager() .findFragmentByTag("progressDialog"); + if (progressDialogFragment == null) { + return; + } + if (msg != null) { progressDialogFragment.setProgress(msg, progress, max); } else { -- cgit v1.2.3 From 4826e0a8c89e43401a3188d7a4d51cdbfd64c37a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Jun 2015 23:44:03 +0200 Subject: move select file logic into EncryptDecryptOverviewFragment --- .../keychain/ui/DecryptActivity.java | 55 +++----- .../keychain/ui/DecryptFilesInputFragment.java | 145 --------------------- .../ui/EncryptDecryptOverviewFragment.java | 45 +++++-- .../keychain/util/FileHelper.java | 8 +- 4 files changed, 52 insertions(+), 201 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index d14c8555c..4375be740 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -33,7 +34,6 @@ import android.support.v4.app.FragmentTransaction; import android.view.View; import android.widget.Toast; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; @@ -43,13 +43,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class DecryptActivity extends BaseActivity { /* Intents */ - public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA; - // TODO handle this intent - // public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT; - public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_DATA_CLIPBOARD"; - - // intern - public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN"; + public static final String ACTION_DECRYPT_FROM_CLIPBOARD = "DECRYPT_DATA_CLIPBOARD"; @Override public void onCreate(Bundle savedInstanceState) { @@ -86,6 +80,13 @@ public class DecryptActivity extends BaseActivity { String action = intent.getAction(); + if (action == null) { + Toast.makeText(this, "Error: No action specified!", Toast.LENGTH_LONG).show(); + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } + try { switch (action) { @@ -93,8 +94,6 @@ public class DecryptActivity extends BaseActivity { // When sending to Keychain Decrypt via share menu // Binary via content provider (could also be files) // override uri to get stream from send - action = ACTION_DECRYPT_DATA; - if (intent.hasExtra(Intent.EXTRA_STREAM)) { uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { @@ -107,8 +106,6 @@ public class DecryptActivity extends BaseActivity { } case Intent.ACTION_SEND_MULTIPLE: { - action = ACTION_DECRYPT_DATA; - if (intent.hasExtra(Intent.EXTRA_STREAM)) { uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { @@ -122,8 +119,6 @@ public class DecryptActivity extends BaseActivity { } case ACTION_DECRYPT_FROM_CLIPBOARD: { - action = ACTION_DECRYPT_DATA; - ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = clipMan.getPrimaryClip(); @@ -148,11 +143,9 @@ public class DecryptActivity extends BaseActivity { break; } + // for everything else, just work on the intent data + case OpenKeychainIntents.DECRYPT_DATA: case Intent.ACTION_VIEW: - // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) - action = ACTION_DECRYPT_DATA; - - // fallthrough default: uris.add(intent.getData()); @@ -165,19 +158,15 @@ public class DecryptActivity extends BaseActivity { return; } - if (ACTION_DECRYPT_DATA.equals(action)) { - // Definitely need a data uri with the decrypt_data intent - if (uris.isEmpty()) { - Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); - setResult(Activity.RESULT_CANCELED); - finish(); - } - displayListFragment(uris); + // Definitely need a data uri with the decrypt_data intent + if (uris.isEmpty()) { + Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); + setResult(Activity.RESULT_CANCELED); + finish(); return; } - boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); - displayInputFragment(showOpenDialog); + displayListFragment(uris); } @@ -189,16 +178,6 @@ public class DecryptActivity extends BaseActivity { return tempFile; } - public void displayInputFragment(boolean showOpenDialog) { - DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(showOpenDialog); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.decrypt_files_fragment_container, frag) - .commit(); - } - public void displayListFragment(ArrayList inputUris) { DecryptListFragment frag = DecryptListFragment.newInstance(inputUris); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java deleted file mode 100644 index 93066ee32..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import java.util.ArrayList; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.FileHelper; - -public class DecryptFilesInputFragment extends Fragment { - public static final String ARG_URI = "uri"; - public static final String ARG_OPEN_DIRECTLY = "open_directly"; - - private static final int REQUEST_CODE_INPUT = 0x00007003; - - private TextView mFilename; - private View mDecryptButton; - - private Uri mInputUri = null; - - public static DecryptFilesInputFragment newInstance(boolean openDirectly) { - DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); - - Bundle args = new Bundle(); - args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); - - frag.setArguments(args); - - return frag; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_files_input_fragment, container, false); - - mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); - mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); - view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", - REQUEST_CODE_INPUT); - } - } - }); - mDecryptButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - decryptAction(); - } - }); - - return view; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putParcelable(ARG_URI, mInputUri); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - Bundle state = savedInstanceState != null ? savedInstanceState : getArguments(); - setInputUri(state.getParcelable(ARG_URI)); - - // should only come from args - if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); - } - } - } - - private void setInputUri(Uri inputUri) { - if (inputUri == null) { - mInputUri = null; - mFilename.setText(""); - return; - } - - mInputUri = inputUri; - mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); - } - - private void decryptAction() { - if (mInputUri == null) { - Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); - return; - } - - DecryptActivity activity = (DecryptActivity) getActivity(); - - ArrayList uris = new ArrayList<>(); - uris.add(mInputUri); - activity.displayListFragment(uris); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode != REQUEST_CODE_INPUT) { - return; - } - - if (resultCode == Activity.RESULT_OK && data != null) { - setInputUri(data.getData()); - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index 6e5589940..566ad1d67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -18,8 +18,15 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.ArrayList; +import java.util.regex.Matcher; + +import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -29,16 +36,17 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; - -import java.util.regex.Matcher; +import org.sufficientlysecure.keychain.util.FileHelper; public class EncryptDecryptOverviewFragment extends Fragment { View mClipboardIcon; + private static final int REQUEST_CODE_INPUT = 0x00007003; + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -74,9 +82,11 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent filesDecrypt = new Intent(getActivity(), DecryptActivity.class); - filesDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_DATA_OPEN); - startActivity(filesDecrypt); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + FileHelper.openDocument(EncryptDecryptOverviewFragment.this, "*/*", REQUEST_CODE_INPUT); + } else { + FileHelper.openFile(EncryptDecryptOverviewFragment.this, null, "*/*", REQUEST_CODE_INPUT); + } } }); @@ -135,12 +145,23 @@ public class EncryptDecryptOverviewFragment extends Fragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - // if a result has been returned, display a notify - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(getActivity()).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); + if (requestCode != REQUEST_CODE_INPUT) { + return; + } + + if (resultCode == Activity.RESULT_OK && data != null) { + Uri uri = data.getData(); + if (uri == null) { + Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(this); + return; + } + + Intent intent = new Intent(getActivity(), DecryptActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(uri); + startActivity(intent); + } } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index 558ab9a85..e223217ef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -53,7 +53,6 @@ public class FileHelper { /** * Checks if external storage is mounted if file is located on external storage * - * @param file * @return true if storage is mounted */ public static boolean isStorageMounted(String file) { @@ -70,7 +69,6 @@ public class FileHelper { * Opens the preferred installed file manager on Android and shows a toast if no manager is * installed. * - * @param fragment * @param last default selected Uri, not supported by all file managers * @param mimeType can be text/plain for example * @param requestCode requestCode used to identify the result coming back from file manager to @@ -145,7 +143,6 @@ public class FileHelper { /** * Opens the storage browser on Android 4.4 or later for opening a file * - * @param fragment * @param mimeType can be text/plain for example * @param multiple allow file chooser to return multiple files * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your @@ -163,7 +160,6 @@ public class FileHelper { /** * Opens the storage browser on Android 4.4 or later for saving a file * - * @param fragment * @param mimeType can be text/plain for example * @param suggestedName a filename desirable for the file to be saved * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your @@ -271,7 +267,7 @@ public class FileHelper { } - public static interface FileDialogCallback { - public void onFileSelected(File file, boolean checked); + public interface FileDialogCallback { + void onFileSelected(File file, boolean checked); } } -- cgit v1.2.3 From 558cc6befca479d33c20ca58f426bda486b5ee8f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 00:00:19 +0200 Subject: instrument: adapt to new decrypt file dialog, and some minor fixes --- .../java/org/sufficientlysecure/keychain/ui/DecryptFragment.java | 6 +++++- .../org/sufficientlysecure/keychain/ui/DisplayTextActivity.java | 5 ----- .../keychain/ui/EncryptDecryptOverviewFragment.java | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 29ccb0907..c8ae867b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; +import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -152,7 +153,10 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. final ImportKeyResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); - result.createNotify(getActivity()).show(); + Activity activity = getActivity(); + if (result != null && activity != null) { + result.createNotify(activity).show(); + } getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index 2742e4565..be21cdde1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -28,15 +28,10 @@ import android.support.v4.app.Fragment; import android.view.View; import android.widget.Toast; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.Log; public class DisplayTextActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index 566ad1d67..590d02c6f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; import java.util.regex.Matcher; import android.app.Activity; @@ -152,7 +151,7 @@ public class EncryptDecryptOverviewFragment extends Fragment { if (resultCode == Activity.RESULT_OK && data != null) { Uri uri = data.getData(); if (uri == null) { - Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(this); + Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); return; } -- cgit v1.2.3 From 6a5bd6509b088cf8cee3e9ddf9a5eac5a33de98a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 16:58:40 +0200 Subject: implement saving of files --- .../keychain/ui/DecryptListFragment.java | 25 +++++++++++++++-- .../keychain/util/FileHelper.java | 31 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 422a12a2e..0d527926d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -220,8 +221,7 @@ public class DecryptListFragment if (resultCode == Activity.RESULT_OK && data != null) { Uri saveUri = data.getData(); Uri outputUri = mOutputUris.get(mCurrentInputUri); - // TODO save from outputUri to saveUri - + saveFile(saveUri, outputUri); mCurrentInputUri = null; } return; @@ -233,6 +233,21 @@ public class DecryptListFragment } } + private void saveFile(Uri outputUri, Uri saveUri) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + try { + FileHelper.copyUriData(activity, outputUri, saveUri); + Notify.create(activity, R.string.file_saved, Style.ERROR).show(); + } catch (IOException e) { + Log.e(Constants.TAG, "error saving file", e); + Notify.create(activity, R.string.error_saving_file, Style.ERROR).show(); + } + } + @Override protected void cryptoOperation(CryptoInputParcel cryptoInput) { super.cryptoOperation(cryptoInput, false); @@ -448,6 +463,12 @@ public class DecryptListFragment if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { return false; } + + // don't process menu items until all items are done! + if (!mPendingInputUris.isEmpty()) { + return true; + } + Activity activity = getActivity(); if (activity == null) { return false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index e223217ef..4a00f46cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util; import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -41,8 +42,11 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -267,6 +271,33 @@ public class FileHelper { } + public static void copyUriData(Context context, Uri fromUri, Uri toUri) throws IOException { + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + + try { + ContentResolver resolver = context.getContentResolver(); + bis = new BufferedInputStream(resolver.openInputStream(fromUri)); + bos = new BufferedOutputStream(resolver.openOutputStream(toUri)); + byte[] buf = new byte[1024]; + int len; + while ( (len = bis.read(buf)) > 0) { + bos.write(buf, 0, len); + } + } finally { + try { + if (bis != null) { + bis.close(); + } + if (bos != null) { + bos.close(); + } + } catch (IOException e) { + // ignore, it's just stream closin' + } + } + } + public interface FileDialogCallback { void onFileSelected(File file, boolean checked); } -- cgit v1.2.3 From f9dea313004b51c5220a2d0b5e9407aab0604df5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 17:07:59 +0200 Subject: implement deletion of files --- .../keychain/operations/results/CertifyResult.java | 2 +- .../keychain/operations/results/DeleteResult.java | 2 +- .../operations/results/ImportKeyResult.java | 2 +- .../operations/results/OperationResult.java | 2 +- .../keychain/ui/DecryptListFragment.java | 21 +++++++++++++++++---- 5 files changed, 21 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index 0a0e63330..a9f8170d9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java @@ -132,7 +132,7 @@ public class CertifyResult extends InputPendingResult { intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this); activity.startActivity(intent); } - }, R.string.view_log); + }, R.string.snackbar_details); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java index 50f49add2..52ff8bf44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java @@ -124,7 +124,7 @@ public class DeleteResult extends OperationResult { intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this); activity.startActivity(intent); } - }, R.string.view_log); + }, R.string.snackbar_details); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 1438ad698..2a032cef2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -190,7 +190,7 @@ public class ImportKeyResult extends OperationResult { intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this); activity.startActivity(intent); } - }, R.string.view_log); + }, R.string.snackbar_details); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f110b9186..c9e427462 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -253,7 +253,7 @@ public abstract class OperationResult implements Parcelable { intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); activity.startActivity(intent); } - }, R.string.view_log); + }, R.string.snackbar_details); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 0d527926d..549922526 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -491,12 +491,28 @@ public class DecryptListFragment askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); return true; case R.id.decrypt_delete: - Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); + deleteFile(activity, model.mInputUri); return true; } return false; } + private void deleteFile(Activity activity, Uri uri) { + + try { + int deleted = activity.getContentResolver().delete(uri, null, null); + if (deleted > 0) { + Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); + } else { + Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); + } + } catch (Exception e) { + Log.e(Constants.TAG, "exception deleting file", e); + Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); + } + + } + public static class DecryptFilesAdapter extends RecyclerView.Adapter { private Context mContext; private ArrayList mDataset; @@ -664,9 +680,6 @@ public class DecryptListFragment mMenuClickedModel = model; PopupMenu menu = new PopupMenu(mContext, view); menu.inflate(R.menu.decrypt_item_context_menu); - if (!"file".equals(model.mInputUri.getScheme())) { - menu.getMenu().findItem(R.id.decrypt_delete).setVisible(false); - } menu.setOnMenuItemClickListener(mMenuItemClickListener); menu.setOnDismissListener(new OnDismissListener() { @Override -- cgit v1.2.3 From 45a8510bf0d0f3501d7226234a791908cf06d72a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 21:17:28 +0200 Subject: instrument: delete file and inline preference tests --- .../main/java/org/sufficientlysecure/keychain/util/Preferences.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index f4c6f7f94..a5b0088c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -290,4 +290,9 @@ public class Preferences { .commit(); } } + + public void clear() { + mSharedPreferences.edit().clear().commit(); + } + } -- cgit v1.2.3 From 8d141176bd3bf8f67b04580dd7e988d8a846c0f5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 21:56:23 +0200 Subject: fix original file deletion (and instrumentation) --- .../provider/TemporaryStorageProvider.java | 9 +++++--- .../keychain/ui/DecryptListFragment.java | 27 +++++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index fc3d43eaf..b1f1a7d9e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -210,10 +210,13 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - if (uri.getLastPathSegment() != null) { - selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?"); - selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()}); + if (uri == null || uri.getLastPathSegment() == null) { + return 0; } + + selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?"); + selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()}); + Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection, selectionArgs, null, null, null); if (files != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 549922526..092c5f832 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -499,18 +499,33 @@ public class DecryptListFragment private void deleteFile(Activity activity, Uri uri) { - try { - int deleted = activity.getContentResolver().delete(uri, null, null); - if (deleted > 0) { + if ("file".equals(uri.getScheme())) { + File file = new File(uri.getPath()); + if (file.delete()) { Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); } else { Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); } - } catch (Exception e) { - Log.e(Constants.TAG, "exception deleting file", e); - Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); + return; + } + + if ("content".equals(uri.getScheme())) { + try { + int deleted = activity.getContentResolver().delete(uri, null, null); + if (deleted > 0) { + Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); + } else { + Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); + } + } catch (Exception e) { + Log.e(Constants.TAG, "exception deleting file", e); + Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); + } + return; } + Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); + } public static class DecryptFilesAdapter extends RecyclerView.Adapter { -- cgit v1.2.3 From a990044fd9eab711e4de7c74aa453f362456050b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Jun 2015 23:13:19 +0200 Subject: fix: save file --- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 092c5f832..d2bff8336 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -219,9 +219,9 @@ public class DecryptListFragment case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { + Uri decryptedFileUri = mOutputUris.get(mCurrentInputUri); Uri saveUri = data.getData(); - Uri outputUri = mOutputUris.get(mCurrentInputUri); - saveFile(saveUri, outputUri); + saveFile(decryptedFileUri, saveUri); mCurrentInputUri = null; } return; @@ -233,15 +233,15 @@ public class DecryptListFragment } } - private void saveFile(Uri outputUri, Uri saveUri) { + private void saveFile(Uri decryptedFileUri, Uri saveUri) { Activity activity = getActivity(); if (activity == null) { return; } try { - FileHelper.copyUriData(activity, outputUri, saveUri); - Notify.create(activity, R.string.file_saved, Style.ERROR).show(); + FileHelper.copyUriData(activity, decryptedFileUri, saveUri); + Notify.create(activity, R.string.file_saved, Style.OK).show(); } catch (IOException e) { Log.e(Constants.TAG, "error saving file", e); Notify.create(activity, R.string.error_saving_file, Style.ERROR).show(); -- cgit v1.2.3