aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main
diff options
context:
space:
mode:
authorVincent <valodim@mugenguild.com>2015-09-18 14:11:49 +0200
committerVincent <valodim@mugenguild.com>2015-09-18 14:11:49 +0200
commit2ebcc942d4a4faca97971b387d14c5ea1fcac16f (patch)
treed5f74e29d7622df408bff138d644a2447f102603 /OpenKeychain/src/main
parentf4f59abcfd234a6d9c361c101384bf370a7488f9 (diff)
parent8ad31e32519b42c3ae439baa52716792980c5638 (diff)
downloadopen-keychain-2ebcc942d4a4faca97971b387d14c5ea1fcac16f.tar.gz
open-keychain-2ebcc942d4a4faca97971b387d14c5ea1fcac16f.tar.bz2
open-keychain-2ebcc942d4a4faca97971b387d14c5ea1fcac16f.zip
Merge pull request #1487 from open-keychain/mime4j
support multipart mime structure in decrypted data
Diffstat (limited to 'OpenKeychain/src/main')
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java374
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java96
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java43
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java81
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java27
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java32
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java517
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java11
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.pngbin0 -> 158 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.pngbin148 -> 0 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.pngbin0 -> 132 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.pngbin0 -> 240 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.pngbin0 -> 397 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.pngbin0 -> 129 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.pngbin131 -> 0 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.pngbin0 -> 108 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.pngbin0 -> 167 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.pngbin0 -> 268 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.pngbin0 -> 193 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.pngbin184 -> 0 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.pngbin0 -> 155 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.pngbin0 -> 264 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.pngbin0 -> 496 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.pngbin0 -> 248 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.pngbin0 -> 205 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.pngbin0 -> 368 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_share_white_24dp.pngbin0 -> 698 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.pngbin0 -> 316 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.pngbin0 -> 272 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/ic_save_black_24dp.pngbin0 -> 477 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.pngbin0 -> 938 bytes
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_list_entry.xml77
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml55
-rw-r--r--OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml19
-rw-r--r--OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml12
-rw-r--r--OpenKeychain/src/main/res/menu/log_display.xml2
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml29
46 files changed, 1133 insertions, 325 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 63e1a5ce7..c966d688a 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -256,7 +256,7 @@
This links to attached asc files in AOSP mail. It is deactivated because of
https://github.com/open-keychain/open-keychain/issues/290
-->
- <!--<data android:mimeType="text/plain" />-->
+ <data android:mimeType="text/plain" />
</intent-filter>
<!-- DECRYPT_DATA with data Uri -->
<intent-filter>
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
new file mode 100644
index 000000000..d9e48af8a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2015 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.operations;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.field.ContentDispositionField;
+import org.apache.james.mime4j.field.DefaultFieldParser;
+import org.apache.james.mime4j.parser.AbstractContentHandler;
+import org.apache.james.mime4j.parser.MimeStreamParser;
+import org.apache.james.mime4j.stream.BodyDescriptor;
+import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.openintents.openpgp.OpenPgpMetadata;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.InputDataResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.InputDataParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+
+
+/** This operation deals with input data, trying to determine its type as it goes.
+ *
+ * We deal with four types of structures:
+ *
+ * - signed/encrypted non-mime data
+ * - signed/encrypted mime data
+ * - encrypted multipart/signed mime data
+ * - multipart/signed mime data (WIP)
+ *
+ */
+public class InputDataOperation extends BaseOperation<InputDataParcel> {
+
+ final private byte[] buf = new byte[256];
+
+ public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ Uri mSignedDataUri;
+ DecryptVerifyResult mSignedDataResult;
+
+ @NonNull
+ @Override
+ public InputDataResult execute(InputDataParcel input, final CryptoInputParcel cryptoInput) {
+
+ final OperationLog log = new OperationLog();
+
+ log.add(LogType.MSG_DATA, 0);
+
+ Uri currentInputUri;
+
+ DecryptVerifyResult decryptResult = null;
+
+ PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput();
+ if (decryptInput != null) {
+
+ log.add(LogType.MSG_DATA_OPENPGP, 1);
+
+ PgpDecryptVerifyOperation op =
+ new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
+
+ decryptInput.setInputUri(input.getInputUri());
+
+ currentInputUri = TemporaryStorageProvider.createFile(mContext);
+ decryptInput.setOutputUri(currentInputUri);
+
+ decryptResult = op.execute(decryptInput, cryptoInput);
+ if (decryptResult.isPending()) {
+ return new InputDataResult(log, decryptResult);
+ }
+ log.addByMerge(decryptResult, 2);
+
+ if (!decryptResult.success()) {
+ log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1);
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log);
+ }
+
+ } else {
+ currentInputUri = input.getInputUri();
+ }
+
+ // If we aren't supposed to attempt mime decode, we are done here
+ if (!input.getMimeDecode()) {
+
+ if (decryptInput == null) {
+ throw new AssertionError("no decryption or mime decoding, this is probably a bug");
+ }
+
+ log.add(LogType.MSG_DATA_SKIP_MIME, 1);
+
+ ArrayList<Uri> uris = new ArrayList<>();
+ uris.add(currentInputUri);
+ ArrayList<OpenPgpMetadata> metadatas = new ArrayList<>();
+ metadatas.add(decryptResult.getDecryptionMetadata());
+
+ log.add(LogType.MSG_DATA_OK, 1);
+ return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, uris, metadatas);
+
+ }
+
+ final MimeStreamParser parser = new MimeStreamParser((MimeConfig) null);
+
+ final ArrayList<Uri> outputUris = new ArrayList<>();
+ final ArrayList<OpenPgpMetadata> metadatas = new ArrayList<>();
+
+ parser.setContentDecoding(true);
+ parser.setRecurse();
+ parser.setContentHandler(new AbstractContentHandler() {
+ private Uri uncheckedSignedDataUri;
+ String mFilename;
+
+ @Override
+ public void startMultipart(BodyDescriptor bd) throws MimeException {
+ if ("signed".equals(bd.getSubType())) {
+ if (mSignedDataUri != null) {
+ // recursive signed data is not supported, and will just be parsed as-is
+ log.add(LogType.MSG_DATA_DETACHED_NESTED, 2);
+ return;
+ }
+ log.add(LogType.MSG_DATA_DETACHED, 2);
+ if (!outputUris.isEmpty()) {
+ // we can't have previous data if we parse a detached signature!
+ log.add(LogType.MSG_DATA_DETACHED_CLEAR, 3);
+ outputUris.clear();
+ metadatas.clear();
+ }
+ // this is signed data, we require the next part raw
+ parser.setRaw();
+ }
+ }
+
+ @Override
+ public void raw(InputStream is) throws MimeException, IOException {
+
+ if (uncheckedSignedDataUri != null) {
+ throw new AssertionError("raw parts must only be received as first part of multipart/signed!");
+ }
+
+ log.add(LogType.MSG_DATA_DETACHED_RAW, 3);
+
+ uncheckedSignedDataUri = TemporaryStorageProvider.createFile(mContext, mFilename, "text/plain");
+ OutputStream out = mContext.getContentResolver().openOutputStream(uncheckedSignedDataUri, "w");
+
+ if (out == null) {
+ throw new IOException("Error getting file for writing!");
+ }
+
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+
+ out.close();
+ // continue to next body part the usual way
+ parser.setFlat();
+
+ }
+
+ @Override
+ public void startHeader() throws MimeException {
+ mFilename = null;
+ }
+
+ @Override
+ public void field(Field field) throws MimeException {
+ field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
+ if (field instanceof ContentDispositionField) {
+ mFilename = ((ContentDispositionField) field).getFilename();
+ }
+ }
+
+ private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
+
+ if (!"application/pgp-signature".equals(bd.getMimeType())) {
+ log.add(LogType.MSG_DATA_DETACHED_UNSUPPORTED, 3);
+ uncheckedSignedDataUri = null;
+ parser.setRecurse();
+ return;
+ }
+
+ log.add(LogType.MSG_DATA_DETACHED_SIG, 3);
+
+ ByteArrayOutputStream detachedSig = new ByteArrayOutputStream();
+
+ int len, totalLength = 0;
+ while ((len = is.read(buf)) > 0) {
+ totalLength += len;
+ detachedSig.write(buf, 0, len);
+ if (totalLength > 4096) {
+ throw new IOException("detached signature is unreasonably large!");
+ }
+ }
+ detachedSig.close();
+
+ PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel();
+ decryptInput.setInputUri(uncheckedSignedDataUri);
+ decryptInput.setDetachedSignature(detachedSig.toByteArray());
+
+ PgpDecryptVerifyOperation op =
+ new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
+ DecryptVerifyResult verifyResult = op.execute(decryptInput, cryptoInput);
+
+ log.addByMerge(verifyResult, 4);
+
+ mSignedDataUri = uncheckedSignedDataUri;
+ mSignedDataResult = verifyResult;
+
+ // reset parser state
+ uncheckedSignedDataUri = null;
+ parser.setRecurse();
+
+ }
+
+ @Override
+ public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
+
+ // if we have signed data waiting, we expect a signature for checking
+ if (uncheckedSignedDataUri != null) {
+ bodySignature(bd, is);
+ return;
+ }
+
+ // we read first, no need to create an output file if nothing was read!
+ int len = is.read(buf);
+ if (len < 0) {
+ return;
+ }
+
+ // If mSignedDataUri is non-null, we already parsed a signature. If mSignedDataResult is non-null
+ // too, we are still in the same parsing stage, so this is trailing data - skip it!
+ if (mSignedDataUri != null && mSignedDataResult != null) {
+ log.add(LogType.MSG_DATA_DETACHED_TRAILING, 2);
+ return;
+ }
+
+ log.add(LogType.MSG_DATA_MIME_PART, 2);
+
+ log.add(LogType.MSG_DATA_MIME_TYPE, 3, bd.getMimeType());
+ if (mFilename != null) {
+ log.add(LogType.MSG_DATA_MIME_FILENAME, 3, mFilename);
+ }
+
+ Uri uri = TemporaryStorageProvider.createFile(mContext, mFilename, bd.getMimeType());
+ OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w");
+
+ if (out == null) {
+ throw new IOException("Error getting file for writing!");
+ }
+
+ int totalLength = 0;
+ do {
+ totalLength += len;
+ out.write(buf, 0, len);
+ } while ((len = is.read(buf)) > 0);
+
+ log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength));
+
+ String charset = bd.getCharset();
+ // the charset defaults to us-ascii, but we want to default to utf-8
+ if ("us-ascii".equals(charset)) {
+ charset = "utf-8";
+ }
+
+ OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, totalLength, charset);
+
+ out.close();
+ outputUris.add(uri);
+ metadatas.add(metadata);
+
+ }
+
+ });
+
+ try {
+
+ log.add(LogType.MSG_DATA_MIME, 1);
+
+ // open current uri for input
+ InputStream in = mContext.getContentResolver().openInputStream(currentInputUri);
+ parser.parse(in);
+
+ if (mSignedDataUri != null) {
+
+ if (decryptResult != null) {
+ decryptResult.setSignatureResult(mSignedDataResult.getSignatureResult());
+ } else {
+ decryptResult = mSignedDataResult;
+ }
+
+ // the actual content is the signed data now (and will be passed verbatim, if parsing fails)
+ currentInputUri = mSignedDataUri;
+ in = mContext.getContentResolver().openInputStream(currentInputUri);
+ // reset signed data result, to indicate to the parser that it is in the inner part
+ mSignedDataResult = null;
+ parser.parse(in);
+
+ }
+
+ // if we found data, return success
+ if (!outputUris.isEmpty()) {
+ log.add(LogType.MSG_DATA_MIME_OK, 2);
+
+ log.add(LogType.MSG_DATA_OK, 1);
+ return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas);
+ }
+
+ // if no mime data parsed, just return the raw data as fallback
+ log.add(LogType.MSG_DATA_MIME_NONE, 2);
+
+ OpenPgpMetadata metadata;
+ if (decryptResult != null) {
+ metadata = decryptResult.getDecryptionMetadata();
+ } else {
+ // if we neither decrypted nor mime-decoded, should this be treated as an error?
+ // either way, we know nothing about the data
+ metadata = new OpenPgpMetadata();
+ }
+
+ outputUris.add(currentInputUri);
+ metadatas.add(metadata);
+
+ log.add(LogType.MSG_DATA_OK, 1);
+ return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas);
+
+ } catch (FileNotFoundException e) {
+ log.add(LogType.MSG_DATA_ERROR_IO, 2);
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log);
+ } catch (IOException e) {
+ e.printStackTrace();
+ log.add(LogType.MSG_DATA_ERROR_IO, 2);
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log);
+ } catch (MimeException e) {
+ e.printStackTrace();
+ log.add(LogType.MSG_DATA_MIME_ERROR, 2);
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log);
+ }
+
+ }
+
+}
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 e8be9fa78..95cf179af 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
@@ -34,9 +34,6 @@ public class DecryptVerifyResult extends InputPendingResult {
OpenPgpSignatureResult mSignatureResult;
OpenPgpDecryptionResult mDecryptionResult;
OpenPgpMetadata mDecryptionMetadata;
- // This holds the charset which was specified in the ascii armor, if specified
- // https://tools.ietf.org/html/rfc4880#page56
- String mCharset;
CryptoInputParcel mCachedCryptoInputParcel;
@@ -96,14 +93,6 @@ public class DecryptVerifyResult extends InputPendingResult {
mDecryptionMetadata = decryptMetadata;
}
- public String getCharset () {
- return mCharset;
- }
-
- public void setCharset(String charset) {
- mCharset = charset;
- }
-
public void setOutputBytes(byte[] outputBytes) {
mOutputBytes = outputBytes;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java
new file mode 100644
index 000000000..56e99ba1b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.operations.results;
+
+
+import java.util.ArrayList;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+import org.openintents.openpgp.OpenPgpMetadata;
+
+
+public class InputDataResult extends InputPendingResult {
+
+ public final ArrayList<Uri> mOutputUris;
+ final public DecryptVerifyResult mDecryptVerifyResult;
+ public final ArrayList<OpenPgpMetadata> mMetadata;
+
+ public InputDataResult(OperationLog log, @NonNull InputPendingResult result) {
+ super(log, result);
+ mOutputUris = null;
+ mDecryptVerifyResult = null;
+ mMetadata = null;
+ }
+
+ public InputDataResult(int result, OperationLog log) {
+ super(result, log);
+ mOutputUris = null;
+ mDecryptVerifyResult = null;
+ mMetadata = null;
+ }
+
+ public InputDataResult(int result, OperationLog log, DecryptVerifyResult decryptResult,
+ @NonNull ArrayList<Uri> outputUris, @NonNull ArrayList<OpenPgpMetadata> metadata) {
+ super(result, log);
+ mDecryptVerifyResult = decryptResult;
+ if (outputUris.size() != metadata.size()) {
+ throw new AssertionError("number of output URIs must match metadata!");
+ }
+ mOutputUris = outputUris;
+ mMetadata = metadata;
+ }
+
+ protected InputDataResult(Parcel in) {
+ super(in);
+ mOutputUris = in.createTypedArrayList(Uri.CREATOR);
+ mDecryptVerifyResult = in.readParcelable(DecryptVerifyResult.class.getClassLoader());
+ mMetadata = in.createTypedArrayList(OpenPgpMetadata.CREATOR);
+ }
+
+ public ArrayList<Uri> getOutputUris() {
+ return mOutputUris;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeTypedList(mOutputUris);
+ dest.writeParcelable(mDecryptVerifyResult, 0);
+ dest.writeTypedList(mMetadata);
+ }
+
+ public static final Creator<InputDataResult> CREATOR = new Creator<InputDataResult>() {
+ @Override
+ public InputDataResult createFromParcel(Parcel in) {
+ return new InputDataResult(in);
+ }
+
+ @Override
+ public InputDataResult[] newArray(int size) {
+ return new InputDataResult[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
index d767382ae..0a8c1f653 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -38,6 +38,15 @@ public class InputPendingResult extends OperationResult {
mCryptoInputParcel = null;
}
+ public InputPendingResult(OperationLog log, InputPendingResult result) {
+ super(RESULT_PENDING, log);
+ if (!result.isPending()) {
+ throw new AssertionError("sub result must be pending!");
+ }
+ mRequiredInput = result.mRequiredInput;
+ mCryptoInputParcel = result.mCryptoInputParcel;
+ }
+
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
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 41691933e..b1dcc9202 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
@@ -126,6 +126,13 @@ public abstract class OperationResult implements Parcelable {
Log.v(Constants.TAG, "log: " + this);
}
+ /** Clones this LogEntryParcel, adding extra indent. Note that the parameter array is NOT cloned! */
+ public LogEntryParcel (LogEntryParcel original, int extraIndent) {
+ mType = original.mType;
+ mParameters = original.mParameters;
+ mIndent = original.mIndent +extraIndent;
+ }
+
public LogEntryParcel(Parcel source) {
mType = LogType.values()[source.readInt()];
mParameters = (Object[]) source.readSerializable();
@@ -818,7 +825,29 @@ public abstract class OperationResult implements Parcelable {
MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR,
R.string.msg_keybase_error_msg_payload_mismatch),
- // export log
+ // InputData Operation
+ MSG_DATA (LogLevel.START, R.string.msg_data),
+ MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp),
+ MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io),
+ MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp),
+ MSG_DATA_DETACHED (LogLevel.INFO, R.string.msg_data_detached),
+ MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear),
+ MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig),
+ MSG_DATA_DETACHED_RAW (LogLevel.DEBUG, R.string.msg_data_detached_raw),
+ MSG_DATA_DETACHED_NESTED(LogLevel.WARN, R.string.msg_data_detached_nested),
+ MSG_DATA_DETACHED_TRAILING (LogLevel.WARN, R.string.msg_data_detached_trailing),
+ MSG_DATA_DETACHED_UNSUPPORTED (LogLevel.WARN, R.string.msg_data_detached_unsupported),
+ MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error),
+ MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename),
+ MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length),
+ MSG_DATA_MIME (LogLevel.DEBUG, R.string.msg_data_mime),
+ MSG_DATA_MIME_OK (LogLevel.INFO, R.string.msg_data_mime_ok),
+ MSG_DATA_MIME_NONE (LogLevel.DEBUG, R.string.msg_data_mime_none),
+ MSG_DATA_MIME_PART (LogLevel.DEBUG, R.string.msg_data_mime_part),
+ MSG_DATA_MIME_TYPE (LogLevel.DEBUG, R.string.msg_data_mime_type),
+ MSG_DATA_OK (LogLevel.OK, R.string.msg_data_ok),
+ MSG_DATA_SKIP_MIME (LogLevel.DEBUG, R.string.msg_data_skip_mime),
+
MSG_LV (LogLevel.START, R.string.msg_lv),
MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match),
MSG_LV_MATCH_ERROR (LogLevel.ERROR, R.string.msg_lv_match_error),
@@ -838,7 +867,8 @@ public abstract class OperationResult implements Parcelable {
MSG_LV_FETCH_ERROR_URL (LogLevel.ERROR, R.string.msg_lv_fetch_error_url),
MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io),
MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format),
- MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing);
+ MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing),
+ ;
public final int mMsgId;
public final LogLevel mLevel;
@@ -896,6 +926,13 @@ public abstract class OperationResult implements Parcelable {
mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters));
}
+ public void addByMerge(OperationResult subResult, int indent) {
+ OperationLog subLog = subResult.getLog();
+ for (LogEntryParcel entry : subLog) {
+ mParcels.add(new LogEntryParcel(entry, indent));
+ }
+ }
+
public SubLogEntryParcel getSubResultIfSingle() {
if (mParcels.size() != 1) {
return null;
@@ -974,7 +1011,7 @@ public abstract class OperationResult implements Parcelable {
for (LogEntryParcel entry : this) {
log.append(entry.getPrintableLogEntry(resources, indent)).append("\n");
}
- return log.toString().substring(0, log.length() -1); // get rid of extra new line
+ return log.toString().substring(0, log.length() - 1); // get rid of extra new line
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
index a6d65688c..3eef7759c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.pgp;
+import java.io.InputStream;
import java.util.HashSet;
import android.net.Uri;
@@ -86,10 +87,20 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
return mInputBytes;
}
+ public PgpDecryptVerifyInputParcel setInputUri(Uri uri) {
+ mInputUri = uri;
+ return this;
+ }
+
Uri getInputUri() {
return mInputUri;
}
+ public PgpDecryptVerifyInputParcel setOutputUri(Uri uri) {
+ mOutputUri = uri;
+ return this;
+ }
+
Uri getOutputUri() {
return mOutputUri;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index dda15f382..007f686e8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -556,12 +556,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
originalFilename,
mimeType,
literalData.getModificationTime().getTime(),
- originalSize == null ? 0 : originalSize);
+ originalSize == null ? 0 : originalSize,
+ charset);
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setCharset(charset);
result.setDecryptionMetadata(metadata);
return result;
}
@@ -607,7 +607,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
metadata = new OpenPgpMetadata(
- originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten);
+ originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
if (signature != null) {
updateProgress(R.string.progress_verifying_signature, 90, 100);
@@ -663,7 +663,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.setCachedCryptoInputParcel(cryptoInput);
result.setSignatureResult(signatureResultBuilder.build());
- result.setCharset(charset);
result.setDecryptionResult(decryptionResultBuilder.build());
result.setDecryptionMetadata(metadata);
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 7e9b24989..67f2c36bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
@@ -67,8 +67,8 @@ public class TemporaryStorageProvider extends ContentProvider {
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);
+ public static final String AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
private static final int DB_VERSION = 3;
private static File cacheDir;
@@ -77,18 +77,18 @@ public class TemporaryStorageProvider extends ContentProvider {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
contentValues.put(COLUMN_TYPE, mimeType);
- return context.getContentResolver().insert(BASE_URI, contentValues);
+ return context.getContentResolver().insert(CONTENT_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);
+ return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static Uri createFile(Context context) {
ContentValues contentValues = new ContentValues();
- return context.getContentResolver().insert(BASE_URI, contentValues);
+ return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static int setMimeType(Context context, Uri uri, String mimetype) {
@@ -98,7 +98,7 @@ public class TemporaryStorageProvider extends ContentProvider {
}
public static int cleanUp(Context context) {
- return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",
+ return context.getContentResolver().delete(CONTENT_URI, COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
}
@@ -163,12 +163,19 @@ public class TemporaryStorageProvider extends ContentProvider {
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
}
+ Log.d(Constants.TAG, "being asked for file " + uri);
+
File file;
try {
file = getFile(uri);
+ if (file.exists()) {
+ Log.e(Constants.TAG, "already exists");
+ }
} catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "file not found!");
return null;
}
+
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
@@ -236,7 +243,7 @@ public class TemporaryStorageProvider extends ContentProvider {
Log.e(Constants.TAG, "File creation failed!");
return null;
}
- return Uri.withAppendedPath(BASE_URI, uuid);
+ return Uri.withAppendedPath(CONTENT_URI, uuid);
}
@Override
@@ -274,6 +281,7 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ Log.d(Constants.TAG, "openFile");
return openFileHelper(uri, mode);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index 57dd068ef..e7709e58e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -628,15 +628,14 @@ public class OpenPgpService extends RemoteService {
}
}
-
+ OpenPgpMetadata metadata = pgpResult.getDecryptionMetadata();
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) {
- OpenPgpMetadata metadata = pgpResult.getDecryptionMetadata();
if (metadata != null) {
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
}
}
- String charset = pgpResult.getCharset();
+ String charset = metadata != null ? metadata.getCharset() : null;
if (charset != null) {
result.putExtra(OpenPgpApi.RESULT_CHARSET, charset);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java
new file mode 100644
index 000000000..6affd3334
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+
+
+public class InputDataParcel implements Parcelable {
+
+ private Uri mInputUri;
+
+ private PgpDecryptVerifyInputParcel mDecryptInput;
+ private boolean mMimeDecode = true; // TODO default to false
+
+ public InputDataParcel(Uri inputUri, PgpDecryptVerifyInputParcel decryptInput) {
+ mInputUri = inputUri;
+ mDecryptInput = decryptInput;
+ }
+
+ InputDataParcel(Parcel source) {
+ // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
+ mInputUri = source.readParcelable(getClass().getClassLoader());
+ mDecryptInput = source.readParcelable(getClass().getClassLoader());
+ mMimeDecode = source.readInt() != 0;
+ }
+
+ public Uri getInputUri() {
+ return mInputUri;
+ }
+
+ public PgpDecryptVerifyInputParcel getDecryptInput() {
+ return mDecryptInput;
+ }
+
+ public boolean getMimeDecode() {
+ return mMimeDecode;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mInputUri, 0);
+ dest.writeParcelable(mDecryptInput, 0);
+ dest.writeInt(mMimeDecode ? 1 : 0);
+ }
+
+ public static final Creator<InputDataParcel> CREATOR = new Creator<InputDataParcel>() {
+ public InputDataParcel createFromParcel(final Parcel source) {
+ return new InputDataParcel(source);
+ }
+
+ public InputDataParcel[] newArray(final int size) {
+ return new InputDataParcel[size];
+ }
+ };
+
+}
+
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index eff27f112..c7ac92eef 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.ExportOperation;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
+import org.sufficientlysecure.keychain.operations.InputDataOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
@@ -108,35 +109,29 @@ public class KeychainService extends Service implements Progressable {
// just for brevity
KeychainService outerThis = KeychainService.this;
if (inputParcel instanceof SignEncryptParcel) {
- op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
- outerThis, mActionCanceled);
+ op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof SaveKeyringParcel) {
- op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof RevokeKeyringParcel) {
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof CertifyActionsParcel) {
- op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof DeleteKeyringParcel) {
op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof PromoteKeyringParcel) {
- op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis),
- outerThis, mActionCanceled);
+ op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ImportKeyringParcel) {
- op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ExportKeyringParcel) {
- op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ConsolidateInputParcel) {
- op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis),
- outerThis);
+ op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof KeybaseVerificationParcel) {
- op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
- outerThis);
+ op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } else if (inputParcel instanceof InputDataParcel) {
+ op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else {
throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
index 881190ae2..5eb9963f5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -82,6 +82,9 @@ public class DecryptActivity extends BaseActivity {
return;
}
+ // depending on the data source, we may or may not be able to delete the original file
+ boolean canDelete = false;
+
try {
switch (action) {
@@ -152,10 +155,21 @@ public class DecryptActivity extends BaseActivity {
}
// for everything else, just work on the intent data
- case OpenKeychainIntents.DECRYPT_DATA:
case Intent.ACTION_VIEW:
+ canDelete = true;
+ case OpenKeychainIntents.DECRYPT_DATA:
default:
- uris.add(intent.getData());
+ Uri uri = intent.getData();
+ if (uri != null) {
+
+ if ("com.android.email.attachmentprovider".equals(uri.getHost())) {
+ Toast.makeText(this, R.string.error_reading_aosp, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ uris.add(intent.getData());
+ }
}
@@ -173,13 +187,17 @@ public class DecryptActivity extends BaseActivity {
return;
}
- displayListFragment(uris);
+ displayListFragment(uris, canDelete);
}
- @Nullable public Uri readToTempFile(String text) throws IOException {
+ @Nullable
+ public Uri readToTempFile(String text) throws IOException {
Uri tempFile = TemporaryStorageProvider.createFile(this);
OutputStream outStream = getContentResolver().openOutputStream(tempFile);
+ if (outStream == null) {
+ return null;
+ }
// clean up ascii armored message, fixing newlines and stuff
String cleanedText = PgpHelper.getPgpContent(text);
@@ -188,14 +206,14 @@ public class DecryptActivity extends BaseActivity {
}
// if cleanup didn't work, just try the raw data
- outStream.write(text.getBytes());
+ outStream.write(cleanedText.getBytes());
outStream.close();
return tempFile;
}
- public void displayListFragment(ArrayList<Uri> inputUris) {
+ public void displayListFragment(ArrayList<Uri> inputUris, boolean canDelete) {
- DecryptListFragment frag = DecryptListFragment.newInstance(inputUris);
+ DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete);
FragmentManager fragMan = getSupportFragmentManager();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index ddaf40010..dcba595e9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -28,6 +28,7 @@ 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;
@@ -36,6 +37,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
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;
@@ -44,26 +46,33 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.webkit.MimeTypeMap;
import android.widget.ImageView;
+import android.widget.LinearLayout;
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.Toast;
import android.widget.ViewAnimator;
+import com.cocosw.bottomsheet.BottomSheet;
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.operations.results.InputDataResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
-// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15)
+import org.sufficientlysecure.keychain.service.InputDataParcel;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
+// this import NEEDS to be above the ViewModel AND SubViewHolder one, or it won't compile! (as of 16.09.15)
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
+import org.sufficientlysecure.keychain.ui.DecryptListFragment.ViewHolder.SubViewHolder;
import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
@@ -76,34 +85,38 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap;
public class DecryptListFragment
- extends QueueingCryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult>
+ extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult>
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_CANCELLED_URIS = "cancelled_uris";
public static final String ARG_RESULTS = "results";
+ public static final String ARG_CAN_DELETE = "can_delete";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
public static final String ARG_CURRENT_URI = "current_uri";
private ArrayList<Uri> mInputUris;
- private HashMap<Uri, Uri> mOutputUris;
+ private HashMap<Uri, InputDataResult> mInputDataResults;
private ArrayList<Uri> mPendingInputUris;
private ArrayList<Uri> mCancelledInputUris;
private Uri mCurrentInputUri;
+ private boolean mCanDelete;
private DecryptFilesAdapter mAdapter;
+ private Uri mCurrentSaveFileUri;
/**
* Creates new instance of this fragment
*/
- public static DecryptListFragment newInstance(ArrayList<Uri> uris) {
+ public static DecryptListFragment newInstance(ArrayList<Uri> uris, boolean canDelete) {
DecryptListFragment frag = new DecryptListFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(ARG_INPUT_URIS, uris);
+ args.putBoolean(ARG_CAN_DELETE, canDelete);
frag.setArguments(args);
return frag;
@@ -129,7 +142,7 @@ public class DecryptListFragment
vFilesList.setLayoutManager(new LinearLayoutManager(getActivity()));
vFilesList.setItemAnimator(new DefaultItemAnimator());
- mAdapter = new DecryptFilesAdapter(getActivity(), this);
+ mAdapter = new DecryptFilesAdapter();
vFilesList.setAdapter(mAdapter);
return view;
@@ -141,21 +154,22 @@ public class DecryptListFragment
outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris);
- HashMap<Uri,DecryptVerifyResult> results = new HashMap<>(mInputUris.size());
+ HashMap<Uri,InputDataResult> results = new HashMap<>(mInputUris.size());
for (Uri uri : mInputUris) {
if (mPendingInputUris.contains(uri)) {
continue;
}
- DecryptVerifyResult result = mAdapter.getItemResult(uri);
+ InputDataResult 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));
+ outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults));
outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri);
+ outState.putBoolean(ARG_CAN_DELETE, mCanDelete);
}
@@ -167,23 +181,22 @@ public class DecryptListFragment
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
- ParcelableHashMap<Uri,Uri> outputUris = args.getParcelable(ARG_OUTPUT_URIS);
- ParcelableHashMap<Uri,DecryptVerifyResult> results = args.getParcelable(ARG_RESULTS);
+ ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS);
Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI);
+ mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
+
displayInputUris(inputUris, currentInputUri, cancelledUris,
- outputUris != null ? outputUris.getMap() : null,
results != null ? results.getMap() : null
);
}
private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri,
- ArrayList<Uri> cancelledUris, HashMap<Uri,Uri> outputUris,
- HashMap<Uri,DecryptVerifyResult> results) {
+ ArrayList<Uri> cancelledUris, HashMap<Uri,InputDataResult> results) {
mInputUris = inputUris;
mCurrentInputUri = currentInputUri;
- mOutputUris = outputUris != null ? outputUris : new HashMap<Uri,Uri>(inputUris.size());
+ mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size());
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
mPendingInputUris = new ArrayList<>();
@@ -206,9 +219,8 @@ public class DecryptListFragment
}
if (results != null && results.containsKey(uri)) {
- processResult(uri, results.get(uri));
+ processResult(uri);
} else {
- mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity()));
mPendingInputUris.add(uri);
}
}
@@ -224,9 +236,8 @@ public class DecryptListFragment
case REQUEST_CODE_OUTPUT: {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
- Uri decryptedFileUri = mOutputUris.get(mCurrentInputUri);
Uri saveUri = data.getData();
- saveFile(decryptedFileUri, saveUri);
+ saveFile(saveUri);
mCurrentInputUri = null;
}
return;
@@ -238,7 +249,37 @@ public class DecryptListFragment
}
}
- private void saveFile(Uri decryptedFileUri, Uri saveUri) {
+ private void saveFileDialog(InputDataResult result, int index) {
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ OpenPgpMetadata metadata = result.mMetadata.get(index);
+ Uri saveUri = Uri.fromFile(activity.getExternalFilesDir(metadata.getMimeType()));
+ mCurrentSaveFileUri = result.getOutputUris().get(index);
+
+ String filename = metadata.getFilename();
+ if (filename == null) {
+ String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType());
+ filename = "decrypted" + (ext != null ? "."+ext : "");
+ }
+
+ FileHelper.saveDocument(this, filename, saveUri, metadata.getMimeType(),
+ R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
+ }
+
+ private void saveFile(Uri saveUri) {
+ if (mCurrentSaveFileUri == null) {
+ return;
+ }
+
+ Uri decryptedFileUri = mCurrentSaveFileUri;
+ mCurrentInputUri = null;
+
+ hideKeyboard();
+
Activity activity = getActivity();
if (activity == null) {
return;
@@ -260,21 +301,27 @@ public class DecryptListFragment
}
@Override
- public void onQueuedOperationError(DecryptVerifyResult result) {
+ public void onQueuedOperationError(InputDataResult result) {
final Uri uri = mCurrentInputUri;
mCurrentInputUri = null;
- mAdapter.addResult(uri, result, null, null, null);
+ Activity activity = getActivity();
+ if (activity != null && "com.fsck.k9.attachmentprovider".equals(uri.getHost())) {
+ Toast.makeText(getActivity(), R.string.error_reading_k9, Toast.LENGTH_LONG).show();
+ }
+
+ mAdapter.addResult(uri, result);
cryptoOperation();
}
@Override
- public void onQueuedOperationSuccess(DecryptVerifyResult result) {
+ public void onQueuedOperationSuccess(InputDataResult result) {
Uri uri = mCurrentInputUri;
mCurrentInputUri = null;
- processResult(uri, result);
+ mInputDataResults.put(uri, result);
+ processResult(uri);
cryptoOperation();
}
@@ -298,39 +345,57 @@ public class DecryptListFragment
}
- private void processResult(final Uri uri, final DecryptVerifyResult result) {
+ HashMap<Uri,Drawable> mIconCache = new HashMap<>();
- new AsyncTask<Void, Void, Drawable>() {
+ private void processResult(final Uri uri) {
+
+ new AsyncTask<Void, Void, Void>() {
@Override
- protected Drawable doInBackground(Void... params) {
+ protected Void doInBackground(Void... params) {
+
+ InputDataResult result = mInputDataResults.get(uri);
Context context = getActivity();
- if (result.getDecryptionMetadata() == null || context == null) {
+ if (context == null) {
return null;
}
- String type = result.getDecryptionMetadata().getMimeType();
- Uri outputUri = mOutputUris.get(uri);
- if (type == null || outputUri == null) {
- return null;
- }
+ for (int i = 0; i < result.getOutputUris().size(); i++) {
- TemporaryStorageProvider.setMimeType(context, outputUri, type);
+ Uri outputUri = result.getOutputUris().get(i);
+ if (mIconCache.containsKey(outputUri)) {
+ continue;
+ }
- 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);
- }
+ OpenPgpMetadata metadata = result.mMetadata.get(i);
+ String type = metadata.getMimeType();
+
+ Drawable icon = null;
+
+ if (ClipDescription.compareMimeTypes(type, "text/plain")) {
+ // noinspection deprecation, this should be called from Context, but not available in minSdk
+ icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp);
+ } else if (ClipDescription.compareMimeTypes(type, "image/*")) {
+ int px = FormattingUtils.dpToPx(context, 48);
+ Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px));
+ icon = new BitmapDrawable(context.getResources(), bitmap);
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(outputUri, type);
+
+ final List<ResolveInfo> matches =
+ context.getPackageManager().queryIntentActivities(intent, 0);
+ // noinspection LoopStatementThatDoesntLoop
+ for (ResolveInfo match : matches) {
+ icon = match.loadIcon(getActivity().getPackageManager());
+ break;
+ }
+ }
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(outputUri, type);
+ if (icon != null) {
+ mIconCache.put(outputUri, icon);
+ }
- final List<ResolveInfo> matches =
- context.getPackageManager().queryIntentActivities(intent, 0);
- //noinspection LoopStatementThatDoesntLoop
- for (ResolveInfo match : matches) {
- return match.loadIcon(getActivity().getPackageManager());
}
return null;
@@ -338,49 +403,14 @@ public class DecryptListFragment
}
@Override
- protected void onPostExecute(Drawable icon) {
- processResult(uri, result, icon);
+ protected void onPostExecute(Void v) {
+ InputDataResult result = mInputDataResults.get(uri);
+ mAdapter.addResult(uri, result);
}
}.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.getResult() != OpenPgpSignatureResult.RESULT_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.getDecryptionMetadata() != null) {
- onFileClick = new OnClickListener() {
- @Override
- public void onClick(View view) {
- displayWithViewIntent(uri, false);
- }
- };
- }
-
- mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick);
-
- }
-
public void retryUri(Uri uri) {
// never interrupt running operations!
@@ -397,19 +427,41 @@ public class DecryptListFragment
}
- public void displayWithViewIntent(final Uri uri, boolean share) {
+ public void displayBottomSheet(final InputDataResult result, final int index) {
+
Activity activity = getActivity();
- if (activity == null || mCurrentInputUri != null) {
+ if (activity == null) {
return;
}
- final Uri outputUri = mOutputUris.get(uri);
- final DecryptVerifyResult result = mAdapter.getItemResult(uri);
- if (outputUri == null || result == null) {
+ new BottomSheet.Builder(activity).sheet(R.menu.decrypt_bottom_sheet).listener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.decrypt_open:
+ displayWithViewIntent(result, index, false, true);
+ break;
+ case R.id.decrypt_share:
+ displayWithViewIntent(result, index, true, true);
+ break;
+ case R.id.decrypt_save:
+ saveFileDialog(result, index);
+ break;
+ }
+ return false;
+ }
+ }).grid().show();
+
+ }
+
+ public void displayWithViewIntent(InputDataResult result, int index, boolean share, boolean forceChooser) {
+ Activity activity = getActivity();
+ if (activity == null) {
return;
}
- final OpenPgpMetadata metadata = result.getDecryptionMetadata();
+ Uri outputUri = result.getOutputUris().get(index);
+ OpenPgpMetadata metadata = result.mMetadata.get(index);
// text/plain is a special case where we extract the uri content into
// the EXTRA_TEXT extra ourselves, and display a chooser which includes
@@ -418,12 +470,14 @@ public class DecryptListFragment
if (share) {
try {
- String plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset());
+ String plaintext = FileHelper.readTextFromUri(activity, outputUri, null);
Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(metadata.getMimeType());
+ intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, plaintext);
- startActivity(intent);
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_share));
+ startActivity(chooserIntent);
} catch (IOException e) {
Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show();
@@ -432,11 +486,34 @@ public class DecryptListFragment
return;
}
- Intent intent = new Intent(activity, DisplayTextActivity.class);
+ Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setDataAndType(outputUri, metadata.getMimeType());
- intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result);
- activity.startActivity(intent);
+ intent.setDataAndType(outputUri, "text/plain");
+
+ if (forceChooser) {
+
+ LabeledIntent internalIntent = new LabeledIntent(
+ new Intent(intent)
+ .setClass(activity, DisplayTextActivity.class)
+ .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult)
+ .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata),
+ BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher);
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ new Parcelable[] { internalIntent });
+
+ startActivity(chooserIntent);
+
+ } else {
+
+ intent.setClass(activity, DisplayTextActivity.class);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult);
+ intent.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata);
+ startActivity(intent);
+
+ }
} else {
@@ -454,13 +531,13 @@ public class DecryptListFragment
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- activity.startActivity(chooserIntent);
+ startActivity(chooserIntent);
}
}
@Override
- public PgpDecryptVerifyInputParcel createOperationInput() {
+ public InputDataParcel createOperationInput() {
if (mCurrentInputUri == null) {
if (mPendingInputUris.isEmpty()) {
@@ -471,11 +548,11 @@ public class DecryptListFragment
mCurrentInputUri = mPendingInputUris.remove(0);
}
- Uri currentOutputUri = mOutputUris.get(mCurrentInputUri);
- Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri);
+ Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri);
- return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri)
+ PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true);
+ return new InputDataParcel(mCurrentInputUri, decryptInput);
}
@@ -496,25 +573,12 @@ public class DecryptListFragment
}
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);
+ intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult);
activity.startActivity(intent);
return true;
- case R.id.decrypt_share:
- displayWithViewIntent(model.mInputUri, true);
- return true;
- case R.id.decrypt_save:
- OpenPgpMetadata metadata = result.getDecryptionMetadata();
- if (metadata == null) {
- return true;
- }
- mCurrentInputUri = model.mInputUri;
- FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(),
- R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
- return true;
case R.id.decrypt_delete:
deleteFile(activity, model.mInputUri);
return true;
@@ -524,6 +588,9 @@ public class DecryptListFragment
private void deleteFile(Activity activity, Uri uri) {
+ // we can only ever delete a file once, if we got this far either it's gone or it will never work
+ mCanDelete = false;
+
if ("file".equals(uri.getScheme())) {
File file = new File(uri.getPath());
if (file.delete()) {
@@ -553,46 +620,29 @@ public class DecryptListFragment
}
- public static class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> {
- private Context mContext;
+ public class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> {
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;
+ InputDataResult mResult;
int mProgress, mMax;
String mProgressMsg;
OnClickListener mCancelled;
- ViewModel(Context context, Uri uri) {
- mContext = context;
+ ViewModel(Uri uri) {
mInputUri = uri;
mProgress = 0;
mMax = 100;
mCancelled = null;
}
- void addResult(DecryptVerifyResult result) {
+ void addResult(InputDataResult result) {
mResult = result;
}
- void addIcon(Drawable icon) {
- mIcon = icon;
- }
-
- void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) {
- mOnFileClickListener = onFileClick;
- mOnKeyClickListener = onKeyClick;
- }
-
boolean hasResult() {
return mResult != null;
}
@@ -636,9 +686,7 @@ public class DecryptListFragment
}
// Provide a suitable constructor (depends on the kind of dataset)
- public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) {
- mContext = context;
- mMenuItemClickListener = menuItemClickListener;
+ public DecryptFilesAdapter() {
mDataset = new ArrayList<>();
}
@@ -701,51 +749,103 @@ public class DecryptListFragment
holder.vAnimator.setDisplayedChild(1);
}
- KeyFormattingUtils.setStatus(mContext, holder, model.mResult);
+ KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult);
- final OpenPgpMetadata metadata = model.mResult.getDecryptionMetadata();
+ int numFiles = model.mResult.getOutputUris().size();
+ holder.resizeFileList(numFiles, LayoutInflater.from(getActivity()));
+ for (int i = 0; i < numFiles; i++) {
- 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);
+ Uri outputUri = model.mResult.getOutputUris().get(i);
+ OpenPgpMetadata metadata = model.mResult.mMetadata.get(i);
+ SubViewHolder fileHolder = holder.mFileHolderList.get(i);
- long size = metadata == null ? 0 : metadata.getOriginalSize();
- if (size == -1 || size == 0) {
- holder.vFilesize.setText("");
- } else {
- holder.vFilesize.setText(FileHelper.readableFileSize(size));
- }
+ String filename;
+ if (metadata == null) {
+ filename = getString(R.string.filename_unknown);
+ } else if (TextUtils.isEmpty(metadata.getFilename())) {
+ filename = getString("text/plain".equals(metadata.getMimeType())
+ ? R.string.filename_unknown_text : R.string.filename_unknown);
+ } else {
+ filename = metadata.getFilename();
+ }
+ fileHolder.vFilename.setText(filename);
+
+ long size = metadata == null ? 0 : metadata.getOriginalSize();
+ if (size == -1 || size == 0) {
+ fileHolder.vFilesize.setText("");
+ } else {
+ fileHolder.vFilesize.setText(FileHelper.readableFileSize(size));
+ }
+
+ if (mIconCache.containsKey(outputUri)) {
+ fileHolder.vThumbnail.setImageDrawable(mIconCache.get(outputUri));
+ } else {
+ fileHolder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am);
+ }
+
+ // save index closure-style :)
+ final int idx = i;
+
+ fileHolder.vFile.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ if (model.mResult.success()) {
+ displayBottomSheet(model.mResult, idx);
+ return true;
+ }
+ return false;
+ }
+ });
+
+ fileHolder.vFile.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (model.mResult.success()) {
+ displayWithViewIntent(model.mResult, idx, false, false);
+ }
+ }
+ });
- 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);
+ OpenPgpSignatureResult sigResult = model.mResult.mDecryptVerifyResult.getSignatureResult();
+ if (sigResult != null) {
+ final long keyId = sigResult.getKeyId();
+ if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) {
+ holder.vSignatureLayout.setOnClickListener(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);
+ }
+ });
+ }
+ }
holder.vContextMenu.setTag(model);
holder.vContextMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
mMenuClickedModel = model;
- PopupMenu menu = new PopupMenu(mContext, view);
+ PopupMenu menu = new PopupMenu(activity, view);
menu.inflate(R.menu.decrypt_item_context_menu);
- menu.setOnMenuItemClickListener(mMenuItemClickListener);
+ menu.setOnMenuItemClickListener(DecryptListFragment.this);
menu.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(PopupMenu popupMenu) {
mMenuClickedModel = null;
}
});
+ menu.getMenu().findItem(R.id.decrypt_delete).setEnabled(mCanDelete);
menu.show();
}
});
@@ -761,9 +861,13 @@ public class DecryptListFragment
holder.vErrorViewLog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- Intent intent = new Intent(mContext, LogDisplayActivity.class);
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ Intent intent = new Intent(activity, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult);
- mContext.startActivity(intent);
+ activity.startActivity(intent);
}
});
@@ -775,8 +879,8 @@ public class DecryptListFragment
return mDataset.size();
}
- public DecryptVerifyResult getItemResult(Uri uri) {
- ViewModel model = new ViewModel(mContext, uri);
+ public InputDataResult getItemResult(Uri uri) {
+ ViewModel model = new ViewModel(uri);
int pos = mDataset.indexOf(model);
if (pos == -1) {
return null;
@@ -787,37 +891,32 @@ public class DecryptListFragment
}
public void add(Uri uri) {
- ViewModel newModel = new ViewModel(mContext, uri);
+ ViewModel newModel = new ViewModel(uri);
mDataset.add(newModel);
notifyItemInserted(mDataset.size());
}
public void setProgress(Uri uri, int progress, int max, String msg) {
- ViewModel newModel = new ViewModel(mContext, uri);
+ ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel);
mDataset.get(pos).setProgress(progress, max, msg);
notifyItemChanged(pos);
}
public void setCancelled(Uri uri, OnClickListener retryListener) {
- ViewModel newModel = new ViewModel(mContext, uri);
+ ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel);
mDataset.get(pos).setCancelled(retryListener);
notifyItemChanged(pos);
}
- public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon,
- OnClickListener onFileClick, OnClickListener onKeyClick) {
+ public void addResult(Uri uri, InputDataResult result) {
- ViewModel model = new ViewModel(mContext, uri);
+ ViewModel model = new ViewModel(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);
}
@@ -834,11 +933,6 @@ public class DecryptListFragment
public ProgressBar vProgress;
public TextView vProgressMsg;
- public View vFile;
- public TextView vFilename;
- public TextView vFilesize;
- public ImageView vThumbnail;
-
public ImageView vEncStatusIcon;
public TextView vEncStatusText;
@@ -855,6 +949,25 @@ public class DecryptListFragment
public ImageView vCancelledRetry;
+ public LinearLayout vFileList;
+
+ public static class SubViewHolder {
+ public View vFile;
+ public TextView vFilename;
+ public TextView vFilesize;
+ public ImageView vThumbnail;
+
+ public SubViewHolder(View itemView) {
+ 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);
+ }
+ }
+
+ public ArrayList<SubViewHolder> mFileHolderList = new ArrayList<>();
+ private int mCurrentFileListSize = 0;
+
public ViewHolder(View itemView) {
super(itemView);
@@ -863,11 +976,6 @@ public class DecryptListFragment
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);
@@ -878,6 +986,12 @@ public class DecryptListFragment
vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email);
vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action);
+ vFileList = (LinearLayout) itemView.findViewById(R.id.file_list);
+ for (int i = 0; i < vFileList.getChildCount(); i++) {
+ mFileHolderList.add(new SubViewHolder(vFileList.getChildAt(i)));
+ mCurrentFileListSize += 1;
+ }
+
vContextMenu = itemView.findViewById(R.id.context_menu);
vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg);
@@ -887,6 +1001,27 @@ public class DecryptListFragment
}
+ public void resizeFileList(int size, LayoutInflater inflater) {
+ int childCount = vFileList.getChildCount();
+ // if we require more children, create them
+ while (childCount < size) {
+ View v = inflater.inflate(R.layout.decrypt_list_file_item, null);
+ vFileList.addView(v);
+ mFileHolderList.add(new SubViewHolder(v));
+ childCount += 1;
+ }
+
+ while (size < mCurrentFileListSize) {
+ mCurrentFileListSize -= 1;
+ vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.GONE);
+ }
+ while (size > mCurrentFileListSize) {
+ vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.VISIBLE);
+ mCurrentFileListSize += 1;
+ }
+
+ }
+
@Override
public ImageView getEncryptionStatusIcon() {
return vEncStatusIcon;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
index 3c8e972b9..4bcca09f1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
@@ -25,9 +25,9 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.view.View;
import android.widget.Toast;
+import org.openintents.openpgp.OpenPgpMetadata;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
@@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.util.FileHelper;
public class DisplayTextActivity extends BaseActivity {
+ public static final String EXTRA_RESULT = "result";
public static final String EXTRA_METADATA = "metadata";
@Override
@@ -60,11 +61,12 @@ public class DisplayTextActivity extends BaseActivity {
return;
}
- DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA);
+ DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_RESULT);
+ OpenPgpMetadata metadata = intent.getParcelableExtra(EXTRA_METADATA);
String plaintext;
try {
- plaintext = FileHelper.readTextFromUri(this, intent.getData(), result.getCharset());
+ plaintext = FileHelper.readTextFromUri(this, intent.getData(), metadata.getCharset());
} catch (IOException e) {
Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show();
return;
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 de90d48fd..88351b6b7 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
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.ui.base;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Parcelable;
@@ -116,14 +117,15 @@ public abstract class CryptoOperationFragment<T extends Parcelable, S extends Op
}
public void hideKeyboard() {
- if (getActivity() == null) {
+ Activity activity = getActivity();
+ if (activity == null) {
return;
}
- InputMethodManager inputManager = (InputMethodManager) getActivity()
+ InputMethodManager inputManager = (InputMethodManager) activity
.getSystemService(Context.INPUT_METHOD_SERVICE);
// check if no view has focus
- View v = getActivity().getCurrentFocus();
+ View v = activity.getCurrentFocus();
if (v == null)
return;
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 284c17e7a..8f5753dae 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
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.ui.util;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.text.Spannable;
@@ -446,7 +447,7 @@ public class KeyFormattingUtils {
}
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
- public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) {
+ public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result) {
if (holder.hasEncrypt()) {
OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
@@ -477,9 +478,9 @@ public class KeyFormattingUtils {
}
}
- int encColorRes = context.getResources().getColor(encColor);
+ int encColorRes = resources.getColor(encColor);
holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN);
- holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon));
+ holder.getEncryptionStatusIcon().setImageDrawable(resources.getDrawable(encIcon));
holder.getEncryptionStatusText().setText(encText);
holder.getEncryptionStatusText().setTextColor(encColorRes);
}
@@ -577,9 +578,9 @@ public class KeyFormattingUtils {
}
- int sigColorRes = context.getResources().getColor(sigColor);
+ int sigColorRes = resources.getColor(sigColor);
holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
- holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon));
+ holder.getSignatureStatusIcon().setImageDrawable(resources.getDrawable(sigIcon));
holder.getSignatureStatusText().setText(sigText);
holder.getSignatureStatusText().setTextColor(sigColorRes);
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png
new file mode 100644
index 000000000..fa4d2c14d
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index bb6aef1d0..000000000
--- a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png
new file mode 100644
index 000000000..22acc5500
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png
new file mode 100644
index 000000000..b959dc4a8
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..b09a6926d
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png
new file mode 100644
index 000000000..d239cfa09
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index 01d681697..000000000
--- a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png
new file mode 100644
index 000000000..0e4f2f6ea
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.png
new file mode 100644
index 000000000..663479b73
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..e944fd70c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png
new file mode 100644
index 000000000..e9e92e5f8
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index 930ca8d95..000000000
--- a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png
new file mode 100644
index 000000000..9f10aa275
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png
new file mode 100644
index 000000000..eca2d92ec
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..22a8783e7
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.png
new file mode 100644
index 000000000..9102f25df
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png
new file mode 100644
index 000000000..94d5ab98c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.png
new file mode 100644
index 000000000..871291b4e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..a35b3cd14
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png
new file mode 100644
index 000000000..55d42e284
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png
new file mode 100644
index 000000000..4642a3b66
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_save_black_24dp.png
new file mode 100644
index 000000000..ba001835a
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_save_black_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..e351c7beb
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml
index 048595dd8..7869b9a8a 100644
--- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml
+++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml
@@ -24,7 +24,7 @@
android:outAnimation="@anim/fade_out"
android:id="@+id/view_animator"
android:measureAllChildren="false"
- custom:initialView="0"
+ custom:initialView="1"
android:minHeight="?listPreferredItemHeightSmall"
android:animateLayoutChanges="true"
>
@@ -78,14 +78,25 @@
<TextView
android:id="@+id/result_encryption_text"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text=""
tools:text="Encryption status text" />
+
+ <ImageView
+ android:id="@+id/context_menu"
+ android:scaleType="center"
+ android:layout_width="36dip"
+ android:layout_height="48dip"
+ android:clickable="true"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:src="@drawable/ic_more_vert_black_24dp" />
+
</LinearLayout>
<LinearLayout
@@ -121,7 +132,9 @@
android:layout_height="wrap_content"
android:clickable="true"
android:background="?android:selectableItemBackground"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ style="?listPreferredItemHeight"
+ >
<LinearLayout
android:layout_width="0dp"
@@ -184,62 +197,10 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:id="@+id/file"
- android:clickable="true"
- android:background="?android:selectableItemBackground"
- >
+ android:id="@+id/file_list"
+ android:orientation="vertical">
- <ImageView
- android:id="@+id/thumbnail"
- android:layout_gravity="center_vertical"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="center"
- android:padding="6dp"
- android:src="@drawable/ic_doc_generic_am" />
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
- android:layout_weight="1">
-
- <TextView
- android:id="@+id/filename"
- android:maxLines="1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:ellipsize="end"
- android:text=""
- tools:text="filename.jpg" />
-
- <TextView
- android:id="@+id/filesize"
- android:maxLines="1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="?android:attr/textColorTertiary"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textSize="12sp"
- android:ellipsize="end"
- android:text=""
- tools:text="14kb" />
-
- </LinearLayout>
-
- <ImageView
- android:id="@+id/context_menu"
- android:scaleType="center"
- android:layout_width="36dip"
- android:layout_height="48dip"
- android:clickable="true"
- android:background="?android:selectableItemBackground"
- android:src="@drawable/ic_menu_moreoverflow_normal_holo_light" />
+ <include layout="@layout/decrypt_list_file_item" />
</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml b/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml
new file mode 100644
index 000000000..f13a43fd6
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/file"
+ android:clickable="true"
+ android:background="?android:selectableItemBackground"
+ android:minHeight="?listPreferredItemHeight"
+ >
+
+ <ImageView
+ android:id="@+id/thumbnail"
+ android:layout_gravity="center_vertical"
+ android:layout_width="36dp"
+ android:layout_height="36dp"
+ android:scaleType="center"
+ android:padding="6dp"
+ android:src="@drawable/ic_doc_generic_am" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/filename"
+ android:maxLines="1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="end"
+ android:text=""
+ tools:text="filename.jpg" />
+
+ <TextView
+ android:id="@+id/filesize"
+ android:maxLines="1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textSize="12sp"
+ android:ellipsize="end"
+ android:text=""
+ tools:text="14kb" />
+
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml b/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml
new file mode 100644
index 000000000..11b79bd5f
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/decrypt_open"
+ android:title="Open with…"
+ android:icon="@drawable/ic_apps_black_24dp" />
+
+ <item
+ android:id="@+id/decrypt_share"
+ android:title="@string/btn_share_decrypted_text"
+ android:icon="@drawable/ic_share_black_24dp" />
+
+ <item
+ android:id="@+id/decrypt_save"
+ android:title="@string/btn_save"
+ android:icon="@drawable/ic_save_black_24dp" />
+
+</menu>
diff --git a/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml b/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml
index ab526c4a5..65b8f210d 100644
--- a/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml
+++ b/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml
@@ -8,18 +8,6 @@
/>
<item
- android:id="@+id/decrypt_share"
- android:title="@string/btn_share_decrypted_text"
- android:icon="@drawable/ic_share_grey_24dp"
- />
-
- <item
- android:id="@+id/decrypt_save"
- android:title="@string/btn_save_file"
- android:icon="@drawable/ic_action_encrypt_file_24dp"
- />
-
- <item
android:id="@+id/decrypt_delete"
android:title="@string/btn_delete_original"
android:icon="@drawable/ic_delete_grey_24dp"
diff --git a/OpenKeychain/src/main/res/menu/log_display.xml b/OpenKeychain/src/main/res/menu/log_display.xml
index a7f7e286d..a32153359 100644
--- a/OpenKeychain/src/main/res/menu/log_display.xml
+++ b/OpenKeychain/src/main/res/menu/log_display.xml
@@ -4,7 +4,7 @@
<item
android:id="@+id/menu_log_display_export_log"
- android:icon="@drawable/ic_share_black_24dp"
+ android:icon="@drawable/ic_share_white_24dp"
android:title="@string/menu_share_log"
app:showAsAction="ifRoom|withText" />
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 7c8296648..f12be176e 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -1355,6 +1355,27 @@
<string name="msg_lv_fetch_error_format">"Format error!"</string>
<string name="msg_lv_fetch_error_nothing">"Resource not found!"</string>
+ <string name="msg_data">"Processing input data"</string>
+ <string name="msg_data_openpgp">"Attempting to process OpenPGP data"</string>
+ <string name="msg_data_detached">"Encountered detached signature"</string>
+ <string name="msg_data_detached_clear">"Clearing earlier, unsigned data!"</string>
+ <string name="msg_data_detached_sig">"Processing detached signature"</string>
+ <string name="msg_data_detached_raw">"Processing signed data"</string>
+ <string name="msg_data_detached_nested">"Skipping nested signed data!"</string>
+ <string name="msg_data_detached_trailing">"Skipping trailing data after signed part!"</string>
+ <string name="msg_data_detached_unsupported">"Unsupported type of detached signature!"</string>
+ <string name="msg_data_error_io">"Error reading input data!"</string>
+ <string name="msg_data_error_openpgp">"Error processing OpenPGP data!"</string>
+ <string name="msg_data_mime_error">"Error parsing MIME data!"</string>
+ <string name="msg_data_mime_filename">"Filename: '%s'"</string>
+ <string name="msg_data_mime_length">"Content-Length: %s"</string>
+ <string name="msg_data_mime">"Parsing MIME data structure"</string>
+ <string name="msg_data_mime_ok">"Finished parsing</string>
+ <string name="msg_data_mime_none">"No MIME structure found"</string>
+ <string name="msg_data_mime_part">"Processing MIME part"</string>
+ <string name="msg_data_mime_type">"Content-Type: %s"</string>
+ <string name="msg_data_ok">"Data processing successful"</string>
+ <string name="msg_data_skip_mime">"Skipping MIME parsing"</string>
<string name="msg_acc_saved">"Account saved"</string>
@@ -1380,6 +1401,11 @@
<string name="msg_keybase_error_specific">"%s"</string>
<string name="msg_keybase_error_msg_payload_mismatch">"Decrypted proof post does not match expected value"</string>
+ <!-- Messages for Mime parsing operation -->
+ <string name="msg_mime_parsing_start">"Parsing the MIME structure"</string>
+ <string name="msg_mime_parsing_error">"MIME parsing failed"</string>
+ <string name="msg_mime_parsing_success">"MIME parsing successfully!"</string>
+
<!-- PassphraseCache -->
<string name="passp_cache_notif_click_to_clear">"Touch to clear passwords."</string>
<plurals name="passp_cache_notif_n_keys">
@@ -1521,9 +1547,12 @@
<string name="error_loading_keys">"Error loading keys!"</string>
<string name="error_empty_log">"(error, empty log)"</string>
<string name="error_reading_text">"Could not read input to decrypt!"</string>
+ <string name="error_reading_aosp">"Failed reading data, this is a bug in the Android E-Mail client! (Issue #290)"</string>
+ <string name="error_reading_k9">"Received incomplete data, try pressing 'Download complete message' in K-9 Mail!"</string>
<string name="filename_unknown">Unknown filename (click to open)</string>
<string name="filename_unknown_text">Text (click to show)</string>
<string name="intent_show">Show Signed/Encrypted Content</string>
+ <string name="intent_share">Share Signed/Encrypted Content</string>
<string name="view_internal">"View in OpenKeychain"</string>
<string name="error_preparing_data">"Error preparing data!"</string>
<string name="label_clip_title">"Encrypted Data"</string>