aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure
diff options
context:
space:
mode:
authorVincent <valodim@mugenguild.com>2015-06-26 16:41:17 +0200
committerVincent <valodim@mugenguild.com>2015-06-26 16:41:17 +0200
commitc3d5160f17718ad19f38b1aacbb966c4b00e7909 (patch)
tree02be170836f92fb81c55b558f6f6b10da12ffa46 /OpenKeychain/src/main/java/org/sufficientlysecure
parent56ef0f320b24f1a40017bd8d0cb8f80c611f2abd (diff)
parentbda15ff92d1c4d8ba6bd43c9d7736adde895bd66 (diff)
downloadopen-keychain-c3d5160f17718ad19f38b1aacbb966c4b00e7909.tar.gz
open-keychain-c3d5160f17718ad19f38b1aacbb966c4b00e7909.tar.bz2
open-keychain-c3d5160f17718ad19f38b1aacbb966c4b00e7909.zip
Merge pull request #1324 from open-keychain/v/multi-decrypt
finished encrypt/decrypt rewrite
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java74
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java69
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java73
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java199
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java225
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java64
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java881
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java228
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java)67
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java)102
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java48
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java197
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java163
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableHashMap.java63
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java1
31 files changed, 1977 insertions, 849 deletions
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/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/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<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
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/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 e3059defb..6a85ce251 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
@@ -40,11 +40,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;
@@ -538,24 +537,33 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
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
@@ -662,9 +670,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
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
@@ -687,12 +692,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
}
- 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);
}
@@ -700,15 +699,26 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
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);
@@ -824,6 +834,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
// 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/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 {
* <p/>
* 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/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
index 2000a6525..b1f1a7d9e 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,24 @@
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;
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.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
@@ -33,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";
@@ -45,18 +50,37 @@ 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);
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 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)});
@@ -73,6 +97,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" +
");");
}
@@ -89,6 +114,8 @@ public class TemporaryStorageProvider extends ContentProvider {
COLUMN_NAME + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
+ case 2:
+ db.execSQL("ALTER TABLE files ADD COLUMN " + COLUMN_TYPE + " TEXT");
}
}
}
@@ -139,12 +166,33 @@ 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)) {
values.put(COLUMN_TIME, System.currentTimeMillis());
@@ -162,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) {
@@ -180,11 +231,19 @@ public class TemporaryStorageProvider extends ContentProvider {
@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
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return openFileHelper(uri, mode);
}
+
}
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..989b0c4bd 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;
@@ -97,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)) {
@@ -128,23 +120,25 @@ 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);
+ int 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;
case PREVENT_CANCEL:
- progressDialogFragment.setPreventCancel(true);
+ setPreventCancel(true);
break;
default:
@@ -152,4 +146,48 @@ public class ServiceProgressHandler extends Handler {
break;
}
}
+
+ 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 {
+ progressDialogFragment.setProgress(progress, max);
+ }
+
+ }
+
}
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<ByteBuffer, byte[]> cachedSessionKeys) {
+ mCryptoData.putAll(cachedSessionKeys);
+ }
+
public Map<ByteBuffer, byte[]> 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/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
new file mode 100644
index 000000000..4375be740
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import java.io.IOException;
+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;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+
+public class DecryptActivity extends BaseActivity {
+
+ /* Intents */
+ public static final String ACTION_DECRYPT_FROM_CLIPBOARD = "DECRYPT_DATA_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_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<Uri> uris = new ArrayList<>();
+
+ 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) {
+ 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
+ if (intent.hasExtra(Intent.EXTRA_STREAM)) {
+ uris.add(intent.<Uri>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: {
+ 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 ACTION_DECRYPT_FROM_CLIPBOARD: {
+ 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;
+ }
+
+ // for everything else, just work on the intent data
+ case OpenKeychainIntents.DECRYPT_DATA:
+ case Intent.ACTION_VIEW:
+ default:
+ uris.add(intent.getData());
+
+ }
+
+
+ } catch (IOException e) {
+ Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ // 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;
+ }
+
+ displayListFragment(uris);
+
+ }
+
+ 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 displayListFragment(ArrayList<Uri> 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/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
deleted file mode 100644
index bc7705233..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.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.KeychainService;
-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.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;
-
- /**
- * 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.<Uri>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;
- }
-
- cryptoOperation();
- }
-
- 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);
- }
- }
-
- @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();
- cryptoOperation();
- }
- return;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
- }
-
- @Override
- protected void onVerifyLoaded(boolean hideErrorOverlay) {
-
- }
-
- @Override
- protected PgpDecryptVerifyInputParcel createOperationInput() {
- return new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri).setAllowSymmetricDecryption(true);
- }
-
- @Override
- protected void onCryptoOperationSuccess(DecryptVerifyResult result) {
-
- // display signature result in activity
- loadVerifyResult(result);
-
- // TODO delete after decrypt not implemented!
-
- }
-
-}
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..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,21 +19,25 @@ 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;
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 +47,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<PgpDecryptVerifyInputParcel, DecryptVerifyResult>
- implements LoaderManager.LoaderCallbacks<Cursor> {
+public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int LOADER_ID_UNIFIED = 0;
public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result";
@@ -75,11 +74,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 +95,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);
@@ -151,7 +153,10 @@ public abstract class DecryptFragment
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);
}
@@ -205,9 +210,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 +229,7 @@ public abstract class DecryptFragment
getLoaderManager().destroyLoader(LOADER_ID_UNIFIED);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -330,10 +331,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 +340,7 @@ public abstract class DecryptFragment
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -355,8 +352,7 @@ public abstract class DecryptFragment
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -367,8 +363,7 @@ public abstract class DecryptFragment
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -379,8 +374,7 @@ public abstract class DecryptFragment
setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId);
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
}
@@ -438,8 +432,7 @@ public abstract class DecryptFragment
}
});
- mErrorOverlayLayout.setVisibility(View.GONE);
- mContentLayout.setVisibility(View.VISIBLE);
+ showErrorOverlay(false);
onVerifyLoaded(true);
@@ -452,8 +445,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
new file mode 100644
index 000000000..d2bff8336
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -0,0 +1,881 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+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;
+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;
+import android.text.TextUtils;
+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.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;
+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;
+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;
+import org.sufficientlysecure.keychain.util.ParcelableHashMap;
+
+
+public class DecryptListFragment
+ extends CryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult>
+ implements OnMenuItemClickListener {
+
+ 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;
+
+ private ArrayList<Uri> mInputUris;
+ private HashMap<Uri, Uri> mOutputUris;
+ private ArrayList<Uri> mPendingInputUris;
+
+ private Uri mCurrentInputUri;
+
+ private DecryptFilesAdapter mAdapter;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static DecryptListFragment newInstance(ArrayList<Uri> uris) {
+ DecryptListFragment frag = new DecryptListFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelableArrayList(ARG_INPUT_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);
+ // TODO make this a grid, for tablets!
+ 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_INPUT_URIS, mInputUris);
+
+ HashMap<Uri,DecryptVerifyResult> 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);
+
+ Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
+
+ ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
+ ParcelableHashMap<Uri,Uri> outputUris = args.getParcelable(ARG_OUTPUT_URIS);
+ ParcelableHashMap<Uri,DecryptVerifyResult> results = args.getParcelable(ARG_RESULTS);
+
+ displayInputUris(inputUris,
+ outputUris != null ? outputUris.getMap() : null,
+ results != null ? results.getMap() : null
+ );
+ }
+
+ private void displayInputUris(ArrayList<Uri> inputUris, HashMap<Uri,Uri> outputUris,
+ HashMap<Uri,DecryptVerifyResult> results) {
+
+ mInputUris = inputUris;
+ mOutputUris = outputUris != null ? outputUris : new HashMap<Uri,Uri>(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) {
+ 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 decryptedFileUri = mOutputUris.get(mCurrentInputUri);
+ Uri saveUri = data.getData();
+ saveFile(decryptedFileUri, saveUri);
+ mCurrentInputUri = null;
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ private void saveFile(Uri decryptedFileUri, Uri saveUri) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ try {
+ 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();
+ }
+ }
+
+ @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) {
+ Uri uri = mCurrentInputUri;
+ mCurrentInputUri = null;
+
+ processResult(uri, result);
+
+ cryptoOperation();
+ }
+
+ private void processResult(final Uri uri, final DecryptVerifyResult result) {
+
+ new AsyncTask<Void, Void, Drawable>() {
+ @Override
+ protected Drawable doInBackground(Void... params) {
+
+ 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<ResolveInfo> 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) {
+ 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) {
+ onFileClick = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ displayWithViewIntent(uri);
+ }
+ };
+ }
+
+ mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick);
+
+ }
+
+ public void displayWithViewIntent(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<Void,Void,Intent>() {
+
+ @Override
+ protected Intent doInBackground(Void... params) {
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, "text/plain");
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return 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)
+ .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));
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ new Parcelable[] { internalIntent });
+
+ activity.startActivity(chooserIntent);
+ }
+
+ }.execute();
+
+ } else {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, metadata.getMimeType());
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
+ chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ activity.startActivity(chooserIntent);
+ }
+
+ }
+
+ @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 boolean onMenuItemClick(MenuItem menuItem) {
+ 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;
+ }
+
+ 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:
+ deleteFile(activity, model.mInputUri);
+ return true;
+ }
+ return false;
+ }
+
+ private void deleteFile(Activity activity, Uri uri) {
+
+ 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();
+ }
+ 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<ViewHolder> {
+ private Context mContext;
+ private ArrayList<ViewModel> 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 !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri)
+ : viewModel.mInputUri != 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()) {
+ bindItemProgress(holder, model);
+ return;
+ }
+
+ if (model.mResult.success()) {
+ bindItemSuccess(holder, model);
+ } else {
+ bindItemFailure(holder, model);
+ }
+
+ }
+
+ private void bindItemProgress(ViewHolder holder, ViewModel model) {
+ if (holder.vAnimator.getDisplayedChild() != 0) {
+ holder.vAnimator.setDisplayedChild(0);
+ }
+
+ holder.vProgress.setProgress(model.mProgress);
+ holder.vProgress.setMax(model.mMax);
+ if (model.mProgressMsg != null) {
+ holder.vProgressMsg.setText(model.mProgressMsg);
+ }
+ }
+
+ private void bindItemSuccess(ViewHolder holder, final ViewModel model) {
+ if (holder.vAnimator.getDisplayedChild() != 1) {
+ holder.vAnimator.setDisplayedChild(1);
+ }
+
+ KeyFormattingUtils.setStatus(mContext, holder, model.mResult);
+
+ 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 {
+ holder.vFilesize.setText(FileHelper.readableFileSize(size));
+ }
+
+ 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();
+ }
+ });
+ }
+
+ 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)
+ @Override
+ public int getItemCount() {
+ 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);
+ 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 TextView vErrorMsg;
+ public ImageView vErrorViewLog;
+
+ 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);
+
+ vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg);
+ vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log);
+
+ }
+
+ @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;
+ }
+ }
+
+}
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 <dominik@dominikschuermann.de>
- * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.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/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
index 81fb6a392..be21cdde1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,27 +18,24 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.io.IOException;
+
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 {
+import org.sufficientlysecure.keychain.util.FileHelper;
- /* Intents */
- public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA;
+public class DisplayTextActivity extends BaseActivity {
- // intern
- public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
+ public static final String EXTRA_METADATA = "metadata";
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -57,51 +55,46 @@ public class DecryptFilesActivity extends BaseActivity {
@Override
protected void initLayout() {
- setContentView(R.layout.decrypt_files_activity);
+ setContentView(R.layout.decrypt_text_activity);
}
/**
* 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;
+ if (savedInstanceState != null) {
+ return;
}
- // No need to initialize fragments if we are being restored
- if (savedInstanceState != null) {
+ DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA);
+
+ 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;
}
- // 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);
+ if (plaintext != null) {
+ loadFragment(plaintext, result);
+ } else {
+ Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
finish();
}
+ }
- boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action);
- DecryptFilesFragment frag = DecryptFilesFragment.newInstance(uri, showOpenDialog);
+ private void loadFragment(String plaintext, DecryptVerifyResult result) {
+ // Create an instance of the fragment
+ Fragment frag = DisplayTextFragment.newInstance(plaintext, result);
// 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)
+ .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/DisplayTextFragment.java
index 051da5d6b..7b3af48cc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
@@ -31,28 +31,25 @@ 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 class DecryptTextFragment extends DecryptFragment {
- public static final String ARG_CIPHERTEXT = "ciphertext";
- public static final String ARG_SHOW_MENU = "show_menu";
+ public static final String ARG_PLAINTEXT = "plaintext";
// view
private TextView mText;
- // model
- private String mCiphertext;
- private boolean mShowMenuOptions;
+ // model (no state to persist though, that's all in arguments!)
+ private boolean mShowMenuOptions = false;
- public static DecryptTextFragment newInstance(String ciphertext) {
- DecryptTextFragment frag = new DecryptTextFragment();
+ 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);
@@ -60,17 +57,6 @@ public class DecryptTextFragment extends DecryptFragment {
}
/**
- * 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) {
@@ -79,7 +65,7 @@ public class DecryptTextFragment 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"
};
@@ -103,28 +89,37 @@ public class DecryptTextFragment extends DecryptFragment {
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 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 onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
+ 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);
- outState.putString(ARG_CIPHERTEXT, mCiphertext);
- outState.putBoolean(ARG_SHOW_MENU, mShowMenuOptions);
- // no need to save the decrypted text, it's in the textview
+ // display signature result in activity
+ mText.setText(plaintext);
+ loadVerifyResult(result);
}
@Override
+ protected void onVerifyLoaded(boolean hideErrorOverlay) {
+ mShowMenuOptions = hideErrorOverlay;
+ getActivity().supportInvalidateOptionsMenu();
+ }
+
+ @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (mShowMenuOptions) {
@@ -151,39 +146,4 @@ public class DecryptTextFragment 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 a6fad8881..590d02c6f 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,14 @@
package org.sufficientlysecure.keychain.ui;
+
+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 +35,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,17 +81,19 @@ 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);
- 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);
+ }
}
});
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(), DecryptActivity.class);
+ clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
startActivityForResult(clipboardDecrypt, 0);
}
});
@@ -135,12 +144,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();
+ 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/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<Uri> 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 d290c57a6..d7c6b2049 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -17,7 +17,17 @@
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.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -59,13 +69,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<SignEncryptParcel, SignEncryptResult> {
@@ -75,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;
@@ -84,13 +87,15 @@ public class EncryptFilesFragment
private boolean mEncryptFilenames;
private boolean mHiddenRecipients = false;
- private boolean mShareAfterEncrypt;
+ private AfterEncryptAction mAfterEncryptAction;
+ private enum AfterEncryptAction {
+ SAVE, SHARE, COPY;
+ }
private ArrayList<Uri> mOutputUris;
private RecyclerView mSelectedFiles;
- ArrayList<FilesAdapter.ViewModel> mFilesModels;
FilesAdapter mFilesAdapter;
/**
@@ -128,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();
@@ -193,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);
}
}
@@ -216,15 +220,20 @@ 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);
+ 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),
@@ -267,12 +276,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;
}
@@ -375,13 +389,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 {
@@ -393,12 +408,35 @@ 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:
+ 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 = new ClipData(getString(R.string.label_clip_title),
+ // make available as application/pgp-encrypted
+ new String[] { "text/plain" },
+ new ClipData.Item(mOutputUris.get(0))
+ );
+ clipMan.setPrimaryClip(clip);
+ result.createNotify(getActivity()).show();
+ break;
+
+ case SAVE:
+ // Encrypted file was saved already, just show notification
+ result.createNotify(getActivity()).show();
+ break;
}
}
@@ -407,38 +445,44 @@ 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) {
-
- 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");
- // This should be impossible...
- return true;
- }
-
- if (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);
+ private boolean prepareOutputStreams() {
+
+ switch (mAfterEncryptAction) {
+ default:
+ case SHARE:
+ mOutputUris = new ArrayList<>();
+ int filenameCounter = 1;
+ 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);
+ mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
+ filenameCounter++;
+ }
+ return false;
+
+ case SAVE:
+ if (mFilesAdapter.getModelCount() > 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 (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(),
+ mFilesAdapter.getModelItem(0).inputUri)) + Constants.FILE_EXTENSION_ASC;
+ mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName, "text/plain"));
+ return false;
}
+
}
protected SignEncryptParcel createOperationInput() {
@@ -466,7 +510,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;
}
}
@@ -482,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();
@@ -493,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);
@@ -504,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;
}
@@ -542,7 +593,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"
};
@@ -599,7 +650,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 at this point
+ mAfterEncryptAction = AfterEncryptAction.SAVE;
cryptoOperation();
}
return;
@@ -688,9 +740,9 @@ public class EncryptFilesFragment
}
// Provide a suitable constructor (depends on the kind of dataset)
- public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
+ public FilesAdapter(Activity activity, View.OnClickListener onFooterClickListener) {
mActivity = activity;
- mDataset = myDataset;
+ mDataset = new ArrayList<>();
mFooterOnClickListener = onFooterClickListener;
}
@@ -744,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
@@ -784,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;
}
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;
+ }
+ }
}
}
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 2be162e95..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 <T extends Parcelable, S extends O
protected abstract T createOperationInput();
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+ cryptoOperation(cryptoInput, true);
+ }
+
+ protected void cryptoOperation() {
+ cryptoOperation(new CryptoInputParcel());
+ }
+
+ protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) {
T operationInput = createOperationInput();
if (operationInput == null) {
@@ -155,24 +163,30 @@ public abstract class CryptoOperationFragment <T extends Parcelable, S extends O
onHandleResult(result);
}
}
+
+ @Override
+ protected void onSetProgress(String msg, int progress, int max) {
+ // allow handling of progress in fragment, or delegate upwards
+ if ( ! onCryptoSetProgress(msg, progress, max)) {
+ super.onSetProgress(msg, progress, max);
+ }
+ }
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
- saveHandler.showProgressDialog(
- getString(R.string.progress_building_key),
- ProgressDialog.STYLE_HORIZONTAL, false);
+ if (showProgress) {
+ saveHandler.showProgressDialog(
+ getString(R.string.progress_building_key),
+ ProgressDialog.STYLE_HORIZONTAL, false);
+ }
getActivity().startService(intent);
}
- protected void cryptoOperation() {
- cryptoOperation(new CryptoInputParcel());
- }
-
protected void onCryptoOperationResult(S result) {
if (result.success()) {
onCryptoOperationSuccess(result);
@@ -213,5 +227,8 @@ public abstract class CryptoOperationFragment <T extends Parcelable, S extends O
}
+ protected boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
}
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..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
@@ -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().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon));
+ 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().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
+ holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon));
+ 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) {
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..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;
@@ -41,6 +42,20 @@ public class ToolableViewAnimator extends ViewAnimator {
private int mInitChild = -1;
+ public ToolableViewAnimator(Context context) {
+ super(context);
+ }
+
+ 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) {
super(context, attrs);
@@ -52,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;
}
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..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,7 +42,14 @@ 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;
import java.text.DecimalFormat;
public class FileHelper {
@@ -49,7 +57,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) {
@@ -66,7 +73,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
@@ -141,7 +147,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
@@ -159,7 +164,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
@@ -234,7 +238,67 @@ public class FileHelper {
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
- public static interface FileDialogCallback {
- public void onFileSelected(File file, boolean checked);
+ 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 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);
}
}
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 <K extends Parcelable, V extends Parcelable> implements Parcelable {
+
+ HashMap<K,V> mInner;
+
+ public ParcelableHashMap(HashMap<K,V> 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<K,V> getMap() {
+ return mInner;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mInner.size());
+ for (HashMap.Entry<K,V> entry : mInner.entrySet()) {
+ parcel.writeParcelable(entry.getKey(), 0);
+ parcel.writeParcelable(entry.getValue(), 0);
+ }
+ }
+
+ public static final Creator<ParcelableHashMap> CREATOR = new Creator<ParcelableHashMap>() {
+ @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;
+ }
+
+}
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();
+ }
+
}
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;
}