diff options
| author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-06-19 17:07:52 +0200 | 
|---|---|---|
| committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-06-19 17:07:52 +0200 | 
| commit | 313e571a61e22a7152e32366d747114233d25b6e (patch) | |
| tree | d250292332dce208760847f150c4e627607d8028 | |
| parent | 48c96184dcf2837f9be2e5bdb116bedd86098eb3 (diff) | |
| parent | 58706425d62e40c0ecc9164a8fee089ca471e1fe (diff) | |
| download | open-keychain-313e571a61e22a7152e32366d747114233d25b6e.tar.gz open-keychain-313e571a61e22a7152e32366d747114233d25b6e.tar.bz2 open-keychain-313e571a61e22a7152e32366d747114233d25b6e.zip | |
Merge pull request #664 from mar-v-in/improve-file
Add Document/Storage API support
9 files changed, 349 insertions, 232 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 7e2cdcd15..e1514b16f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -104,9 +104,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"; @@ -115,7 +117,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 @@ -214,7 +218,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); @@ -222,71 +226,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 = @@ -311,7 +252,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);                  } @@ -322,24 +263,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"); @@ -350,78 +274,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 */ @@ -452,21 +308,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"); @@ -805,4 +647,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, 0); + +            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 7fdab7bdd..28a465436 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; @@ -58,7 +61,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; @@ -75,8 +80,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() { @@ -99,20 +108,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("")) { @@ -121,7 +134,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( @@ -143,7 +156,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);                  }              } @@ -172,13 +189,25 @@ public class DecryptFileFragment extends DecryptFragment {          intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);          // data -        data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); -          Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" -                + 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); +        } -        data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); -        data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); +        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); @@ -209,8 +238,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");                          }                      } @@ -234,13 +268,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/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..2671e0d40 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.TARGET_URI); +        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); @@ -323,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");                      } @@ -332,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)));                      } @@ -356,13 +392,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); - -                        mFilename.setText(path); -                    } catch (NullPointerException e) { -                        Log.e(Constants.TAG, "Nullpointer while retrieving 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!"); +                        }                      }                  }                  return; 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(); 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 @@ -50,12 +53,27 @@ public class DeleteFileDialogFragment extends DialogFragment {      }      /** +     * 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       */      @Override      public Dialog onCreateDialog(Bundle savedInstanceState) {          final FragmentActivity activity = getActivity(); +        final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().<Uri>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); 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); - -                        // set filename used in export/import dialogs -                        setFilename(path); -                    } catch (NullPointerException e) { -                        Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); +                    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); +                        }                      }                  } | 
