diff options
author | Vincent <valodim@mugenguild.com> | 2015-09-18 14:11:49 +0200 |
---|---|---|
committer | Vincent <valodim@mugenguild.com> | 2015-09-18 14:11:49 +0200 |
commit | 2ebcc942d4a4faca97971b387d14c5ea1fcac16f (patch) | |
tree | d5f74e29d7622df408bff138d644a2447f102603 /OpenKeychain/src/main/java/org | |
parent | f4f59abcfd234a6d9c361c101384bf370a7488f9 (diff) | |
parent | 8ad31e32519b42c3ae439baa52716792980c5638 (diff) | |
download | open-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/java/org')
16 files changed, 1009 insertions, 253 deletions
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); |