Merge pull request #1487 from open-keychain/mime4j
support multipart mime structure in decrypted data
|
@ -27,6 +27,7 @@ python copy OpenKeychain communication grey import_export 24
|
|||
python copy OpenKeychain content grey content_copy 24
|
||||
python copy OpenKeychain content grey content_paste 24
|
||||
python copy OpenKeychain content grey save 24
|
||||
python copy OpenKeychain content black save 24
|
||||
python copy OpenKeychain content grey select_all 24
|
||||
python copy OpenKeychain editor grey mode_edit 24
|
||||
python copy OpenKeychain file grey cloud 24
|
||||
|
@ -37,6 +38,7 @@ python copy OpenKeychain navigation grey close 24
|
|||
python copy OpenKeychain social grey person 24
|
||||
python copy OpenKeychain social grey person_add 24
|
||||
python copy OpenKeychain social grey share 24
|
||||
python copy OpenKeychain social white share 24
|
||||
python copy OpenKeychain communication grey vpn_key 24
|
||||
python copy OpenKeychain navigation grey chevron_left 24
|
||||
python copy OpenKeychain navigation grey chevron_right 24
|
||||
|
@ -44,6 +46,8 @@ python copy OpenKeychain social grey person 48
|
|||
python copy OpenKeychain communication grey email 24
|
||||
python copy OpenKeychain social black share 24
|
||||
python copy OpenKeychain content black content_copy 24
|
||||
python copy OpenKeychain communication black chat 24
|
||||
python copy OpenKeychain navigation black more_vert 24
|
||||
|
||||
# navigation drawer sections
|
||||
python copy OpenKeychain communication black vpn_key 24
|
||||
|
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
// http://www.vogella.com/tutorials/Robolectric/article.html
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.robolectric:robolectric:3.0'
|
||||
testCompile 'org.mockito:mockito-core:1.+'
|
||||
|
||||
// UI testing with Espresso
|
||||
androidTestCompile 'com.android.support.test:runner:0.3'
|
||||
|
@ -57,7 +58,10 @@ dependencies {
|
|||
compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar'
|
||||
compile 'com.nispok:snackbar:2.11.0'
|
||||
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
||||
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
||||
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
||||
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
|
||||
compile 'com.cocosw:bottomsheet:1.1.1@aar'
|
||||
|
||||
// libs as submodules
|
||||
compile project(':extern:openpgp-api-lib:openpgp-api')
|
||||
|
@ -209,9 +213,9 @@ android {
|
|||
htmlOutput file('lint-report.html')
|
||||
}
|
||||
|
||||
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
||||
dexOptions {
|
||||
incremental = true
|
||||
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
||||
preDexLibraries = false
|
||||
jumboMode = true
|
||||
javaMaxHeapSize "2g"
|
||||
|
@ -221,6 +225,9 @@ android {
|
|||
exclude 'LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude '.readme'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ public class SymmetricTextOperationTests {
|
|||
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
|
||||
hasAction(Intent.ACTION_VIEW),
|
||||
hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
|
||||
hasData(allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY))),
|
||||
hasData(allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY))),
|
||||
hasType("text/plain")
|
||||
))
|
||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||
|
|
|
@ -96,7 +96,7 @@ public class ViewKeyAdvShareTest {
|
|||
hasType("text/plain"),
|
||||
hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")),
|
||||
hasExtra(is(Intent.EXTRA_STREAM),
|
||||
allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY)))
|
||||
allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY)))
|
||||
))
|
||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||
onView(withId(R.id.view_key_action_fingerprint_share)).perform(click());
|
||||
|
@ -113,7 +113,7 @@ public class ViewKeyAdvShareTest {
|
|||
hasType("text/plain"),
|
||||
hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")),
|
||||
hasExtra(is(Intent.EXTRA_STREAM),
|
||||
allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY)))
|
||||
allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY)))
|
||||
))
|
||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||
onView(withId(R.id.view_key_action_key_share)).perform(click());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(outputUri, type);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
long size = metadata == null ? 0 : metadata.getOriginalSize();
|
||||
if (size == -1 || size == 0) {
|
||||
holder.vFilesize.setText("");
|
||||
} else {
|
||||
holder.vFilesize.setText(FileHelper.readableFileSize(size));
|
||||
}
|
||||
|
||||
if (model.mIcon != null) {
|
||||
holder.vThumbnail.setImageDrawable(model.mIcon);
|
||||
} else {
|
||||
holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am);
|
||||
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.vFile.setOnClickListener(model.mOnFileClickListener);
|
||||
holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener);
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png
Normal file
After Width: | Height: | Size: 158 B |
Before Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 132 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png
Normal file
After Width: | Height: | Size: 240 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 397 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png
Normal file
After Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 131 B |
After Width: | Height: | Size: 108 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png
Normal file
After Width: | Height: | Size: 193 B |
Before Width: | Height: | Size: 184 B |
After Width: | Height: | Size: 155 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.png
Normal file
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 205 B |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.png
Normal file
After Width: | Height: | Size: 368 B |
After Width: | Height: | Size: 698 B |
After Width: | Height: | Size: 316 B |
After Width: | Height: | Size: 272 B |
After Width: | Height: | Size: 477 B |
After Width: | Height: | Size: 938 B |
|
@ -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>
|
||||
|
||||
|
|
55
OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml
Normal file
|
@ -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>
|
19
OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml
Normal file
|
@ -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>
|
|
@ -7,18 +7,6 @@
|
|||
android:icon="@drawable/ic_view_list_grey_24dp"
|
||||
/>
|
||||
|
||||
<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"
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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.pgp;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
|
||||
import org.sufficientlysecure.keychain.operations.InputDataOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.InputDataResult;
|
||||
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;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
@Config(constants = WorkaroundBuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
|
||||
public class InputDataOperationTest {
|
||||
|
||||
static PrintStream oldShadowStream;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
oldShadowStream = ShadowLog.stream;
|
||||
// ShadowLog.stream = System.out;
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// don't log verbosely here, we're not here to test imports
|
||||
ShadowLog.stream = oldShadowStream;
|
||||
|
||||
// ok NOW log verbosely!
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMimeDecoding () throws Exception {
|
||||
|
||||
String mimeMail =
|
||||
"Content-Type: multipart/mixed; boundary=\"=-26BafqxfXmhVNMbYdoIi\"\n" +
|
||||
"\n" +
|
||||
"--=-26BafqxfXmhVNMbYdoIi\n" +
|
||||
"Content-Type: text/plain\n" +
|
||||
"Content-Transfer-Encoding: quoted-printable\n" +
|
||||
"Content-Disposition: attachment; filename=data.txt\n" +
|
||||
"\n" +
|
||||
"message part 1\n" +
|
||||
"\n" +
|
||||
"--=-26BafqxfXmhVNMbYdoIi\n" +
|
||||
"Content-Type: text/testvalue\n" +
|
||||
"Content-Description: Dummy content description\n" +
|
||||
"\n" +
|
||||
"message part 2.1\n" +
|
||||
"message part 2.2\n" +
|
||||
"\n" +
|
||||
"--=-26BafqxfXmhVNMbYdoIi--";
|
||||
|
||||
|
||||
ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream outStream2 = new ByteArrayOutputStream();
|
||||
ContentResolver mockResolver = mock(ContentResolver.class);
|
||||
|
||||
// fake openOutputStream first and second
|
||||
when(mockResolver.openOutputStream(any(Uri.class), eq("w")))
|
||||
.thenReturn(outStream1, outStream2);
|
||||
|
||||
// fake openInputStream
|
||||
Uri fakeInputUri = Uri.parse("content://fake/1");
|
||||
when(mockResolver.openInputStream(fakeInputUri)).thenReturn(
|
||||
new ByteArrayInputStream(mimeMail.getBytes()));
|
||||
|
||||
Uri fakeOutputUri1 = Uri.parse("content://fake/out/1");
|
||||
Uri fakeOutputUri2 = Uri.parse("content://fake/out/2");
|
||||
when(mockResolver.insert(eq(TemporaryStorageProvider.CONTENT_URI), any(ContentValues.class)))
|
||||
.thenReturn(fakeOutputUri1, fakeOutputUri2);
|
||||
|
||||
// application which returns mockresolver
|
||||
Application spyApplication = spy(RuntimeEnvironment.application);
|
||||
when(spyApplication.getContentResolver()).thenReturn(mockResolver);
|
||||
|
||||
InputDataOperation op = new InputDataOperation(spyApplication,
|
||||
new ProviderHelper(RuntimeEnvironment.application), null);
|
||||
|
||||
InputDataParcel input = new InputDataParcel(fakeInputUri, null);
|
||||
|
||||
InputDataResult result = op.execute(input, new CryptoInputParcel());
|
||||
|
||||
// must be successful, no verification, have two output URIs
|
||||
Assert.assertTrue(result.success());
|
||||
Assert.assertNull(result.mDecryptVerifyResult);
|
||||
|
||||
ArrayList<Uri> outUris = result.getOutputUris();
|
||||
Assert.assertEquals("must have two output URIs", 2, outUris.size());
|
||||
Assert.assertEquals("first uri must be the one we provided", fakeOutputUri1, outUris.get(0));
|
||||
verify(mockResolver).openOutputStream(result.getOutputUris().get(0), "w");
|
||||
Assert.assertEquals("second uri must be the one we provided", fakeOutputUri2, outUris.get(1));
|
||||
verify(mockResolver).openOutputStream(result.getOutputUris().get(1), "w");
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put("name", "data.txt");
|
||||
contentValues.put("mimetype", "text/plain");
|
||||
verify(mockResolver).insert(TemporaryStorageProvider.CONTENT_URI, contentValues);
|
||||
contentValues.put("name", (String) null);
|
||||
contentValues.put("mimetype", "text/testvalue");
|
||||
verify(mockResolver).insert(TemporaryStorageProvider.CONTENT_URI, contentValues);
|
||||
|
||||
// quoted-printable returns windows style line endings for some reason?
|
||||
Assert.assertEquals("first part must have expected content",
|
||||
"message part 1\r\n", new String(outStream1.toByteArray()));
|
||||
Assert.assertEquals("second part must have expected content",
|
||||
"message part 2.1\nmessage part 2.2\n", new String(outStream2.toByteArray()));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -792,9 +792,9 @@ public class PgpEncryptDecryptTest {
|
|||
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes",
|
||||
out.toByteArray(), plaindata);
|
||||
Assert.assertEquals("charset should be read correctly",
|
||||
"iso-2022-jp", result.getCharset());
|
||||
"iso-2022-jp", result.getDecryptionMetadata().getCharset());
|
||||
Assert.assertEquals("decrypted ciphertext should equal plaintext",
|
||||
new String(out.toByteArray(), result.getCharset()), plaintext);
|
||||
new String(out.toByteArray(), result.getDecryptionMetadata().getCharset()), plaintext);
|
||||
Assert.assertEquals("decryptionResult should be RESULT_ENCRYPTED",
|
||||
OpenPgpDecryptionResult.RESULT_ENCRYPTED, result.getDecryptionResult().getResult());
|
||||
Assert.assertEquals("signatureResult should be RESULT_NO_SIGNATURE",
|
||||
|
|
2
extern/openpgp-api-lib
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 13492ba19fcc1767f5589227b8fa0a9c845696d4
|
||||
Subproject commit 0ba25696981a4c4d5aef01e4a1d683c8adf7522a
|