From 08d63340c92d6c251f082a0d200df8541a42ba61 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Sun, 8 Jun 2014 21:30:11 +0200 Subject: [PATCH 1/3] Modify KeychainIntentService to support I/O with URIs, allow input and output to/from different types (eg. encrypt file and return byte array) --- .../service/KeychainIntentService.java | 277 +++++++----------- .../keychain/ui/DecryptFileFragment.java | 2 +- .../keychain/ui/DecryptMessageFragment.java | 2 +- .../keychain/ui/EncryptFileFragment.java | 2 +- .../keychain/ui/EncryptMessageFragment.java | 2 +- 5 files changed, 109 insertions(+), 176 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 27f41e3d2..67aa8d8c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -107,9 +107,11 @@ public class KeychainIntentService extends IntentService // encrypt, decrypt, import export public static final String TARGET = "target"; + public static final String SOURCE = "source"; // possible targets: - public static final int TARGET_BYTES = 1; - public static final int TARGET_URI = 2; + public static final int IO_BYTES = 1; + public static final int IO_FILE = 2; // This was misleadingly TARGET_URI before! + public static final int IO_URI = 3; // encrypt public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id"; @@ -118,7 +120,9 @@ public class KeychainIntentService extends IntentService public static final String ENCRYPT_COMPRESSION_ID = "compression_id"; public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes"; public static final String ENCRYPT_INPUT_FILE = "input_file"; + public static final String ENCRYPT_INPUT_URI = "input_uri"; public static final String ENCRYPT_OUTPUT_FILE = "output_file"; + public static final String ENCRYPT_OUTPUT_URI = "output_uri"; public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; // decrypt/verify @@ -230,7 +234,7 @@ public class KeychainIntentService extends IntentService if (ACTION_ENCRYPT_SIGN.equals(action)) { try { /* Input */ - int target = data.getInt(TARGET); + int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID); String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); @@ -238,71 +242,8 @@ public class KeychainIntentService extends IntentService boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - InputStream inStream; - long inLength; - InputData inputData; - OutputStream outStream; -// String streamFilename = null; - switch (target) { - case TARGET_BYTES: /* encrypting bytes directly */ - byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES); - - inStream = new ByteArrayInputStream(bytes); - inLength = bytes.length; - - inputData = new InputData(inStream, inLength); - outStream = new ByteArrayOutputStream(); - - break; - case TARGET_URI: /* encrypting file */ - String inputFile = data.getString(ENCRYPT_INPUT_FILE); - String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(inputFile) - || !FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException( - getString(R.string.error_external_storage_not_ready)); - } - - inStream = new FileInputStream(inputFile); - File file = new File(inputFile); - inLength = file.length(); - inputData = new InputData(inStream, inLength); - - outStream = new FileOutputStream(outputFile); - - break; - - // TODO: not used currently -// case TARGET_STREAM: /* Encrypting stream from content uri */ -// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); -// -// // InputStream -// InputStream in = getContentResolver().openInputStream(providerUri); -// inLength = PgpHelper.getLengthOfStream(in); -// inputData = new InputData(in, inLength); -// -// // OutputStream -// try { -// while (true) { -// streamFilename = PgpHelper.generateRandomFilename(32); -// if (streamFilename == null) { -// throw new PgpGeneralException("couldn't generate random file name"); -// } -// openFileInput(streamFilename).close(); -// } -// } catch (FileNotFoundException e) { -// // found a name that isn't used yet -// } -// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); -// -// break; - - default: - throw new PgpGeneralException("No target choosen!"); - - } + InputData inputData = createEncryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); /* Operation */ PgpSignEncrypt.Builder builder = @@ -327,7 +268,7 @@ public class KeychainIntentService extends IntentService PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); // this assumes that the bytes are cleartext (valid for current implementation!) - if (target == TARGET_BYTES) { + if (source == IO_BYTES) { builder.setCleartextInput(true); } @@ -338,24 +279,7 @@ public class KeychainIntentService extends IntentService /* Output */ Bundle resultData = new Bundle(); - - switch (target) { - case TARGET_BYTES: - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - - resultData.putByteArray(RESULT_BYTES, output); - - break; - case TARGET_URI: - // nothing, file was written, just send okay - - break; -// case TARGET_STREAM: -// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); -// resultData.putString(RESULT_URI, uri); -// -// break; - } + finalizeEncryptOutputStream(data, resultData, outStream); OtherHelper.logDebugBundle(resultData, "resultData"); @@ -366,78 +290,10 @@ public class KeychainIntentService extends IntentService } else if (ACTION_DECRYPT_VERIFY.equals(action)) { try { /* Input */ - int target = data.getInt(TARGET); - - byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES); String passphrase = data.getString(DECRYPT_PASSPHRASE); - InputStream inStream; - long inLength; - InputData inputData; - OutputStream outStream; - String streamFilename = null; - switch (target) { - case TARGET_BYTES: /* decrypting bytes directly */ - inStream = new ByteArrayInputStream(bytes); - inLength = bytes.length; - - inputData = new InputData(inStream, inLength); - outStream = new ByteArrayOutputStream(); - - break; - - case TARGET_URI: /* decrypting file */ - String inputFile = data.getString(ENCRYPT_INPUT_FILE); - String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(inputFile) - || !FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException( - getString(R.string.error_external_storage_not_ready)); - } - - // InputStream - inLength = -1; - inStream = new FileInputStream(inputFile); - File file = new File(inputFile); - inLength = file.length(); - inputData = new InputData(inStream, inLength); - - // OutputStream - outStream = new FileOutputStream(outputFile); - - break; - - // TODO: not used, maybe contains code useful for new decrypt method for files? -// case TARGET_STREAM: /* decrypting stream from content uri */ -// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); -// -// // InputStream -// InputStream in = getContentResolver().openInputStream(providerUri); -// inLength = PgpHelper.getLengthOfStream(in); -// inputData = new InputData(in, inLength); -// -// // OutputStream -// try { -// while (true) { -// streamFilename = PgpHelper.generateRandomFilename(32); -// if (streamFilename == null) { -// throw new PgpGeneralException("couldn't generate random file name"); -// } -// openFileInput(streamFilename).close(); -// } -// } catch (FileNotFoundException e) { -// // found a name that isn't used yet -// } -// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); -// -// break; - - default: - throw new PgpGeneralException("No target choosen!"); - - } + InputData inputData = createDecryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); /* Operation */ @@ -468,21 +324,7 @@ public class KeychainIntentService extends IntentService /* Output */ - switch (target) { - case TARGET_BYTES: - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - resultData.putByteArray(RESULT_DECRYPTED_BYTES, output); - break; - case TARGET_URI: - // nothing, file was written, just send okay and verification bundle - - break; -// case TARGET_STREAM: -// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); -// resultData.putString(RESULT_URI, uri); -// -// break; - } + finalizeDecryptOutputStream(data, resultData, outStream); OtherHelper.logDebugBundle(resultData, "resultData"); @@ -915,4 +757,95 @@ public class KeychainIntentService extends IntentService public boolean hasServiceStopped() { return mIsCanceled; } + + private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException { + return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES); + } + + private InputData createEncryptInputData(Bundle data) throws IOException, PgpGeneralException { + return createCryptInputData(data, ENCRYPT_MESSAGE_BYTES); + } + + private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException { + int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); + switch (source) { + case IO_BYTES: /* encrypting bytes directly */ + byte[] bytes = data.getByteArray(bytesName); + return new InputData(new ByteArrayInputStream(bytes), bytes.length); + + case IO_FILE: /* encrypting file */ + String inputFile = data.getString(ENCRYPT_INPUT_FILE); + + // check if storage is ready + if (!FileHelper.isStorageMounted(inputFile)) { + throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + } + + return new InputData(new FileInputStream(inputFile), new File(inputFile).length()); + + case IO_URI: /* encrypting content uri */ + Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI); + + // InputStream + InputStream in = getContentResolver().openInputStream(providerUri); + return new InputData(in, PgpHelper.getLengthOfStream(in)); + + default: + throw new PgpGeneralException("No target choosen!"); + } + } + + private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException { + int target = data.getInt(TARGET); + switch (target) { + case IO_BYTES: + return new ByteArrayOutputStream(); + + case IO_FILE: + String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); + + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + throw new PgpGeneralException( + getString(R.string.error_external_storage_not_ready)); + } + + // OutputStream + return new FileOutputStream(outputFile); + + case IO_URI: + Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI); + + return getContentResolver().openOutputStream(providerUri); + + default: + throw new PgpGeneralException("No target choosen!"); + } + } + + private void finalizeEncryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { + finalizeCryptOutputStream(data, resultData, outStream, RESULT_BYTES); + } + + private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { + finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES); + } + + private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) { + int target = data.getInt(TARGET); + switch (target) { + case IO_BYTES: + byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); + resultData.putByteArray(bytesName, output); + break; + case IO_FILE: + // nothing, file was written, just send okay and verification bundle + + break; + case IO_URI: + // nothing, output was written, just send okay and verification bundle + + break; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index d953e2591..2b43ad3bd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -170,7 +170,7 @@ public class DecryptFileFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + mOutputFilename); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java index d1ad7fbc5..46462f924 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java @@ -129,7 +129,7 @@ public class DecryptMessageFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes()); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index d150abdeb..7df6b2134 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -280,7 +280,7 @@ public class EncryptFileFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); if (mEncryptInterface.isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java index 4c35806e5..8a6103b16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -177,7 +177,7 @@ public class EncryptMessageFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); String message = mMessage.getText().toString(); From f55bc41682c3cfa06d34d6b9679ba7a720066a3f Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 10 Jun 2014 20:57:48 +0200 Subject: [PATCH 2/3] Use Document API on KitKat for file encrypt/decrypt --- .../keychain/Constants.java | 3 + .../keychain/helper/FileHelper.java | 35 ++++++++ .../service/KeychainIntentService.java | 2 +- .../keychain/ui/DecryptFileFragment.java | 79 ++++++++++++++----- .../keychain/ui/EncryptFileFragment.java | 75 +++++++++++++----- .../ui/dialog/FileDialogFragment.java | 56 +++++++++---- 6 files changed, 198 insertions(+), 52 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index c769da421..319ac2873 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain; +import android.os.Build; import android.os.Environment; import org.spongycastle.bcpg.CompressionAlgorithmTags; @@ -48,6 +49,8 @@ public final class Constants { public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; + public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + public static final class Path { public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/OpenKeychain"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java index d7d73cf3d..e0c94b947 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -17,12 +17,14 @@ package org.sufficientlysecure.keychain.helper; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.support.v4.app.Fragment; import android.widget.Toast; @@ -82,6 +84,39 @@ public class FileHelper { } } + + /** + * Opens the storage browser on Android 4.4 or later for opening a file + * @param fragment + * @param last default selected file + * @param mimeType can be text/plain for example + * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void openDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setData(last); + intent.setType(mimeType); + fragment.startActivityForResult(intent, requestCode); + } + + /** + * Opens the storage browser on Android 4.4 or later for saving a file + * @param fragment + * @param last default selected file + * @param mimeType can be text/plain for example + * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void saveDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setData(last); + intent.setType(mimeType); + fragment.startActivityForResult(intent, requestCode); + } + private static Intent buildFileIntent(String filename, String mimeType) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 67aa8d8c1..776419b4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -788,7 +788,7 @@ public class KeychainIntentService extends IntentService // InputStream InputStream in = getContentResolver().openInputStream(providerUri); - return new InputData(in, PgpHelper.getLengthOfStream(in)); + return new InputData(in, 0); default: throw new PgpGeneralException("No target choosen!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index 2b43ad3bd..d3910c11e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -20,10 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.provider.OpenableColumns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -57,7 +60,9 @@ public class DecryptFileFragment extends DecryptFragment { private View mDecryptButton; private String mInputFilename = null; + private Uri mInputUri = null; private String mOutputFilename = null; + private Uri mOutputUri = null; private FileDialogFragment mFileDialog; @@ -74,8 +79,12 @@ public class DecryptFileFragment extends DecryptFragment { mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + if (Constants.KITKAT) { + FileHelper.openDocument(DecryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + } else { + FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", + RESULT_CODE_FILE); + } } }); mDecryptButton.setOnClickListener(new View.OnClickListener() { @@ -98,20 +107,24 @@ public class DecryptFileFragment extends DecryptFragment { } } - private void guessOutputFilename() { - mInputFilename = mFilename.getText().toString(); + private String guessOutputFilename() { File file = new File(mInputFilename); String filename = file.getName(); if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { filename = filename.substring(0, filename.length() - 4); } - mOutputFilename = Constants.Path.APP_DIR + "/" + filename; + return Constants.Path.APP_DIR + "/" + filename; } private void decryptAction() { String currentFilename = mFilename.getText().toString(); if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - guessOutputFilename(); + mInputUri = null; + mInputFilename = mFilename.getText().toString(); + } + + if (mInputUri == null) { + mOutputFilename = guessOutputFilename(); } if (mInputFilename.equals("")) { @@ -119,7 +132,7 @@ public class DecryptFileFragment extends DecryptFragment { return; } - if (mInputFilename.startsWith("file")) { + if (mInputUri == null && mInputFilename.startsWith("file")) { File file = new File(mInputFilename); if (!file.exists() || !file.isFile()) { AppMsg.makeText( @@ -141,7 +154,11 @@ public class DecryptFileFragment extends DecryptFragment { public void handleMessage(Message message) { if (message.what == FileDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { + mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); + } else { + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + } decryptStart(null); } } @@ -170,13 +187,25 @@ public class DecryptFileFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); + + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" + + mOutputUri); - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + if (mInputUri != null) { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + } else { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + } + + if (mOutputUri != null) { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + } else { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + } data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); @@ -232,13 +261,25 @@ public class DecryptFileFragment extends DecryptFragment { switch (requestCode) { case RESULT_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); + if (Constants.KITKAT) { + mInputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mInputFilename = cursor.getString(0); + mFilename.setText(mInputFilename); + } + cursor.close(); + } + } else { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } } } return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index 7df6b2134..a3fb8fbd5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -20,11 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.provider.OpenableColumns; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -73,7 +75,9 @@ public class EncryptFileFragment extends Fragment { // model private String mInputFilename = null; + private Uri mInputUri = null; private String mOutputFilename = null; + private Uri mOutputUri = null; @Override public void onAttach(Activity activity) { @@ -104,8 +108,12 @@ public class EncryptFileFragment extends Fragment { mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + if (Constants.KITKAT) { + FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + } else { + FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", + RESULT_CODE_FILE); + } } }); @@ -178,7 +186,11 @@ public class EncryptFileFragment extends Fragment { public void handleMessage(Message message) { if (message.what == FileDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { + mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); + } else { + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + } encryptStart(); } } @@ -197,17 +209,20 @@ public class EncryptFileFragment extends Fragment { private void encryptClicked() { String currentFilename = mFilename.getText().toString(); if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + mInputUri = null; mInputFilename = mFilename.getText().toString(); } - mOutputFilename = guessOutputFilename(mInputFilename); + if (mInputUri == null) { + mOutputFilename = guessOutputFilename(mInputFilename); + } if (mInputFilename.equals("")) { AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); return; } - if (!mInputFilename.startsWith("content")) { + if (mInputUri == null && !mInputFilename.startsWith("content")) { File file = new File(mInputFilename); if (!file.exists() || !file.isFile()) { AppMsg.makeText( @@ -280,7 +295,25 @@ public class EncryptFileFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); + Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" + + mOutputUri); + + if (mInputUri != null) { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + } else { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + } + + if (mOutputUri != null) { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + } else { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + } if (mEncryptInterface.isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -296,12 +329,6 @@ public class EncryptFileFragment extends Fragment { mEncryptInterface.getEncryptionKeys()); } - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); - - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - boolean useAsciiArmor = mAsciiArmor.isChecked(); data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); @@ -356,13 +383,25 @@ public class EncryptFileFragment extends Fragment { switch (requestCode) { case RESULT_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); + if (Constants.KITKAT) { + mInputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mInputFilename = cursor.getString(0); + mFilename.setText(mInputFilename); + } + cursor.close(); + } + } else { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } } } return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 24f93bed7..10a24ddf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -23,10 +23,13 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.provider.OpenableColumns; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; @@ -50,6 +53,7 @@ public class FileDialogFragment extends DialogFragment { public static final int MESSAGE_OKAY = 1; + public static final String MESSAGE_DATA_URI = "uri"; public static final String MESSAGE_DATA_FILENAME = "filename"; public static final String MESSAGE_DATA_CHECKED = "checked"; @@ -60,6 +64,9 @@ public class FileDialogFragment extends DialogFragment { private CheckBox mCheckBox; private TextView mMessageTextView; + private String mOutputFilename; + private Uri mOutputUri; + private static final int REQUEST_CODE = 0x00007004; /** @@ -92,7 +99,7 @@ public class FileDialogFragment extends DialogFragment { String title = getArguments().getString(ARG_TITLE); String message = getArguments().getString(ARG_MESSAGE); - String defaultFile = getArguments().getString(ARG_DEFAULT_FILE); + mOutputFilename = getArguments().getString(ARG_DEFAULT_FILE); String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT); LayoutInflater inflater = (LayoutInflater) activity @@ -106,15 +113,18 @@ public class FileDialogFragment extends DialogFragment { mMessageTextView.setText(message); mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(defaultFile); + mFilename.setText(mOutputFilename); mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // only .asc or .gpg files // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc // or gpg types! - FileHelper.openFile(FileDialogFragment.this, mFilename.getText().toString(), "*/*", - REQUEST_CODE); + if (Constants.KITKAT) { + FileHelper.saveDocument(FileDialogFragment.this, mOutputUri, "*/*", REQUEST_CODE); + } else { + FileHelper.openFile(FileDialogFragment.this, mOutputFilename, "*/*", REQUEST_CODE); + } } }); @@ -136,13 +146,19 @@ public class FileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - boolean checked = false; - if (mCheckBox.isEnabled()) { - checked = mCheckBox.isChecked(); + String currentFilename = mFilename.getText().toString(); + if (mOutputFilename == null || !mOutputFilename.equals(currentFilename)) { + mOutputUri = null; + mOutputFilename = mFilename.getText().toString(); } + boolean checked = mCheckBox.isEnabled() && mCheckBox.isChecked(); + // return resulting data back to activity Bundle data = new Bundle(); + if (mOutputUri != null) { + data.putParcelable(MESSAGE_DATA_URI, mOutputUri); + } data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString()); data.putBoolean(MESSAGE_DATA_CHECKED, checked); @@ -178,14 +194,26 @@ public class FileDialogFragment extends DialogFragment { switch (requestCode & 0xFFFF) { case REQUEST_CODE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = data.getData().getPath(); - Log.d(Constants.TAG, "path=" + path); + if (Constants.KITKAT) { + mOutputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mOutputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mOutputFilename = cursor.getString(0); + mFilename.setText(mOutputFilename); + } + cursor.close(); + } + } else { + try { + String path = data.getData().getPath(); + Log.d(Constants.TAG, "path=" + path); - // set filename used in export/import dialogs - setFilename(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); + // set filename used in export/import dialogs + setFilename(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); + } } } From e106079b90f7d596a254b223b6e8741fc6d94785 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 10 Jun 2014 21:53:02 +0200 Subject: [PATCH 3/3] Delete using Document API (unsafe) --- .../keychain/ui/DecryptFileFragment.java | 9 +++++-- .../keychain/ui/EncryptFileFragment.java | 15 +++++++++--- .../ui/dialog/DeleteFileDialogFragment.java | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index d3910c11e..170f21c31 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -236,8 +236,13 @@ public class DecryptFileFragment extends DecryptFragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); + DeleteFileDialogFragment deleteFileDialog; + if (mInputUri != null) { + deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + } else { + deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + } deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index a3fb8fbd5..2671e0d40 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -350,8 +350,13 @@ public class EncryptFileFragment extends Fragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); + DeleteFileDialogFragment deleteFileDialog; + if (mInputUri != null) { + deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + } else { + deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + } deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } @@ -359,7 +364,11 @@ public class EncryptFileFragment extends Fragment { // Share encrypted file Intent sendFileIntent = new Intent(Intent.ACTION_SEND); sendFileIntent.setType("*/*"); - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + if (mOutputUri != null) { + sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); + } else { + sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + } startActivity(Intent.createChooser(sendFileIntent, getString(R.string.title_share_file))); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index b42a79993..cae6cf043 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -21,9 +21,11 @@ import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.provider.DocumentsContract; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.widget.Toast; @@ -34,6 +36,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; public class DeleteFileDialogFragment extends DialogFragment { private static final String ARG_DELETE_FILE = "delete_file"; + private static final String ARG_DELETE_URI = "delete_uri"; /** * Creates new instance of this delete file dialog fragment @@ -49,6 +52,20 @@ public class DeleteFileDialogFragment extends DialogFragment { return frag; } + /** + * Creates new instance of this delete file dialog fragment + */ + public static DeleteFileDialogFragment newInstance(Uri deleteUri) { + DeleteFileDialogFragment frag = new DeleteFileDialogFragment(); + Bundle args = new Bundle(); + + args.putParcelable(ARG_DELETE_URI, deleteUri); + + frag.setArguments(args); + + return frag; + } + /** * Creates dialog */ @@ -56,6 +73,7 @@ public class DeleteFileDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); + final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().getParcelable(ARG_DELETE_URI) : null; final String deleteFile = getArguments().getString(ARG_DELETE_FILE); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -71,6 +89,12 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); + if (deleteUri != null) { + // We can not securely delete Documents, so just use usual delete on them + DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri); + return; + } + // Send all information needed to service to edit key in other thread Intent intent = new Intent(activity, KeychainIntentService.class);