From 79fb23b095fba273d77066204ee44d2b8d1edf8d Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Sun, 22 Jun 2014 16:31:28 +0200 Subject: Improve file more, Part 1 - Use Uris where it makes sense, Use File class to clarify it's a file (and not whatever else a string could be) - Show sdcard in side menu in storage API #665 - Propose filename with gpg ending when storing it using the storage API #665 - Don't show output dialog on Android 4.4 #665 - Only show filename on Android < 4.4 #665 TODO: - File deletion for Android < 4.4 - Testing (especially with Android < 4.4) - Batch-encryption - UI - Temporary content provider (see #665 discussion) --- .../org/sufficientlysecure/keychain/Constants.java | 7 +- .../keychain/KeychainApplication.java | 5 +- .../keychain/helper/ExportHelper.java | 63 +++---- .../keychain/helper/FileHelper.java | 149 +++++++++-------- .../keychain/service/KeychainIntentService.java | 21 ++- .../keychain/ui/DecryptActivity.java | 21 +-- .../keychain/ui/DecryptFileFragment.java | 171 ++++++------------- .../keychain/ui/EncryptActivity.java | 29 +--- .../keychain/ui/EncryptFileFragment.java | 181 ++++++--------------- .../keychain/ui/ImportKeysFileFragment.java | 2 +- .../keychain/ui/KeyListFragment.java | 8 +- .../keychain/ui/ViewKeyActivity.java | 4 +- .../ui/dialog/DeleteFileDialogFragment.java | 38 ++--- .../keychain/ui/dialog/FileDialogFragment.java | 87 ++++------ OpenKeychain/src/main/res/values/strings.xml | 1 + 15 files changed, 291 insertions(+), 496 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 319ac2873..8074ad2ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -27,6 +27,8 @@ import org.sufficientlysecure.keychain.ui.DecryptActivity; import org.sufficientlysecure.keychain.ui.EncryptActivity; import org.sufficientlysecure.keychain.ui.KeyListActivity; +import java.io.File; + public final class Constants { public static final boolean DEBUG = BuildConfig.DEBUG; @@ -52,9 +54,8 @@ public final class Constants { 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"; - public static final String APP_DIR_FILE = APP_DIR + "/export.asc"; + public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); + public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc"); } public static final class Pref { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 5d6a62f9c..d28c4d63c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -26,11 +26,9 @@ import android.graphics.drawable.Drawable; import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; -import java.io.File; import java.security.Provider; import java.security.Security; @@ -70,8 +68,7 @@ public class KeychainApplication extends Application { // Create APG directory on sdcard if not existing if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - File dir = new File(Constants.Path.APP_DIR); - if (!dir.exists() && !dir.mkdirs()) { + if (!Constants.Path.APP_DIR.exists() && !Constants.Path.APP_DIR.mkdirs()) { // ignore this for now, it's not crucial // that the directory doesn't exist at this point } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 16ef28311..ae9438148 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -30,7 +30,6 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; @@ -39,9 +38,10 @@ import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import java.io.File; + public class ExportHelper { - protected FileDialogFragment mFileDialog; - protected String mExportFilename; + protected File mExportFile; ActionBarActivity mActivity; @@ -68,47 +68,30 @@ public class ExportHelper { /** * Show dialog where to export keys */ - public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename, + public void showExportKeysDialog(final long[] masterKeyIds, final File exportFile, final boolean showSecretCheckbox) { - mExportFilename = exportFilename; - - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - - exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); - } - } - }; + mExportFile = exportFile; - // Create a new Messenger for the communication back - final Messenger messenger = new Messenger(returnHandler); - - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - String title = null; - if (masterKeyIds == null) { - // export all keys - title = mActivity.getString(R.string.title_export_keys); - } else { - // export only key specified at data uri - title = mActivity.getString(R.string.title_export_key); - } - - String message = mActivity.getString(R.string.specify_file_to_export_to); - String checkMsg = showSecretCheckbox ? - mActivity.getString(R.string.also_export_secret_keys) : null; + String title = null; + if (masterKeyIds == null) { + // export all keys + title = mActivity.getString(R.string.title_export_keys); + } else { + // export only key specified at data uri + title = mActivity.getString(R.string.title_export_key); + } - mFileDialog = FileDialogFragment.newInstance(messenger, title, message, - exportFilename, checkMsg); + String message = mActivity.getString(R.string.specify_file_to_export_to); + String checkMsg = showSecretCheckbox ? + mActivity.getString(R.string.also_export_secret_keys) : null; - mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog"); + FileHelper.saveFile(new FileHelper.FileDialogCallback() { + @Override + public void onFileSelected(File file, boolean checked) { + mExportFile = file; + exportKeys(masterKeyIds, checked); } - }); + }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg); } /** @@ -125,7 +108,7 @@ public class ExportHelper { // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); + data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFile.getAbsolutePath()); data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret); if (masterKeyIds == null) { 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 e0c94b947..2898c7030 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -26,12 +26,19 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.provider.OpenableColumns; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.widget.Toast; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; + +import java.io.File; public class FileHelper { @@ -55,25 +62,18 @@ public class FileHelper { * Opens the preferred installed file manager on Android and shows a toast if no manager is * installed. * - * @param activity - * @param filename default selected file, not supported by all file managers + * @param fragment + * @param last default selected Uri, not supported by all file managers * @param mimeType can be text/plain for example * @param requestCode requestCode used to identify the result coming back from file manager to * onActivityResult() in your activity */ - public static void openFile(Activity activity, String filename, String mimeType, int requestCode) { - Intent intent = buildFileIntent(filename, mimeType); - - try { - activity.startActivityForResult(intent, requestCode); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(activity, R.string.no_filemanager_installed, Toast.LENGTH_SHORT).show(); - } - } + public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); - public static void openFile(Fragment fragment, String filename, String mimeType, int requestCode) { - Intent intent = buildFileIntent(filename, mimeType); + intent.setData(last); + intent.setType(mimeType); try { fragment.startActivityForResult(intent, requestCode); @@ -84,19 +84,62 @@ public class FileHelper { } } + public static void saveFile(final FileDialogCallback callback, final FragmentManager fragmentManager, + final String title, final String message, final File defaultFile, + final String checkMsg) { + // Message is received after file is selected + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + callback.onFileSelected( + new File(message.getData().getString(FileDialogFragment.MESSAGE_DATA_FILE)), + message.getData().getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); + } + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + @Override + public void run() { + FileDialogFragment fileDialog = FileDialogFragment.newInstance(messenger, title, message, + defaultFile, checkMsg); + + fileDialog.show(fragmentManager, "fileDialog"); + } + }); + } + + public static void saveFile(Fragment fragment, String title, String message, File defaultFile, int requestCode) { + saveFile(fragment, title, message, defaultFile, requestCode, null); + } + + public static void saveFile(final Fragment fragment, String title, String message, File defaultFile, + final int requestCode, String checkMsg) { + saveFile(new FileDialogCallback() { + @Override + public void onFileSelected(File file, boolean checked) { + Intent intent = new Intent(); + intent.setData(Uri.fromFile(file)); + fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); + } + }, fragment.getActivity().getSupportFragmentManager(), title, message, defaultFile, checkMsg); + } + /** * 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) { + public static void openDocument(Fragment fragment, 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); } @@ -104,66 +147,42 @@ public class FileHelper { /** * 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 suggestedName a filename desirable for the file to be saved * @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) { + public static void saveDocument(Fragment fragment, String mimeType, String suggestedName, int requestCode) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setData(last); intent.setType(mimeType); + intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works + intent.putExtra(Intent.EXTRA_TITLE, suggestedName); 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); - - intent.setData(Uri.parse("file://" + filename)); - intent.setType(mimeType); - - return intent; - } + public static String getFilename(Context context, Uri uri) { + String filename = null; + try { + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); - /** - * Get a file path from a Uri. - *

- * from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/ - * afilechooser/utils/FileUtils.java - * - * @param context - * @param uri - * @return - * @author paulburke - */ - public static String getPath(Context context, Uri uri) { - Log.d(Constants.TAG + " File -", - "Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() - + ", Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme: " - + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: " - + uri.getPathSegments().toString()); - - if ("content".equalsIgnoreCase(uri.getScheme())) { - String[] projection = {"_data"}; - Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndexOrThrow("_data"); - return cursor.getString(columnIndex); - } - } catch (Exception e) { - // Eat it - } finally { - if (cursor != null) { - cursor.close(); + if (cursor != null) { + if (cursor.moveToNext()) { + filename = cursor.getString(0); } + cursor.close(); } - } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); + } catch (Exception ignored) { + // This happens in rare cases (eg: document deleted since selection) and should not cause a failure } + if (filename == null) { + String[] split = uri.toString().split("/"); + filename = split[split.length - 1]; + } + return filename; + } - return null; + public static interface FileDialogCallback { + public void onFileSelected(File file, boolean checked); } } 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 e1514b16f..a2676b1a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -139,6 +139,7 @@ public class KeychainIntentService extends IntentService // export key public static final String EXPORT_OUTPUT_STREAM = "export_output_stream"; public static final String EXPORT_FILENAME = "export_filename"; + public static final String EXPORT_URI = "export_uri"; public static final String EXPORT_SECRET = "export_secret"; public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; @@ -393,13 +394,16 @@ public class KeychainIntentService extends IntentService boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); String outputFile = data.getString(EXPORT_FILENAME); + Uri outputUri = data.getParcelable(EXPORT_URI); // If not exporting all keys get the masterKeyIds of the keys to export from the intent boolean exportAll = data.getBoolean(EXPORT_ALL); - // check if storage is ready - if (!FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + if (outputFile != null) { + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + } } ArrayList publicMasterKeyIds = new ArrayList(); @@ -431,12 +435,19 @@ public class KeychainIntentService extends IntentService } } + OutputStream outStream; + if (outputFile != null) { + outStream = new FileOutputStream(outputFile); + } else { + outStream = getContentResolver().openOutputStream(outputUri); + } + PgpImportExport pgpImportExport = new PgpImportExport(this, this, this); Bundle resultData = pgpImportExport .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds, - new FileOutputStream(outputFile)); + outStream); - if (mIsCanceled) { + if (mIsCanceled && outputFile != null) { boolean isDeleted = new File(outputFile).delete(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 5b21be6e4..33659f3e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -23,11 +23,9 @@ import android.net.Uri; import android.os.Bundle; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; @@ -114,7 +112,7 @@ public class DecryptActivity extends DrawerActivity { } else { // Binary via content provider (could also be files) // override uri to get stream from send - uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); action = ACTION_DECRYPT; } } else if (Intent.ACTION_VIEW.equals(action)) { @@ -155,21 +153,8 @@ public class DecryptActivity extends DrawerActivity { } } } else if (ACTION_DECRYPT.equals(action) && uri != null) { - // get file path from uri - String path = FileHelper.getPath(this, uri); - - if (path != null) { - mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path); - mSwitchToTab = PAGER_TAB_FILE; - } else { - Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported. " + - "Please use the Remote Service API!"); - Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) - .show(); - // end activity - finish(); - } + mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri); + mSwitchToTab = PAGER_TAB_FILE; } else { Log.e(Constants.TAG, "Include the extra 'text' or an Uri with setData() in your Intent!"); 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 28a465436..3df3b24b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -20,21 +20,17 @@ 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; import android.widget.CheckBox; -import android.widget.EditText; +import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; -import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -44,29 +40,26 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.util.Notify; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; import java.io.File; public class DecryptFileFragment extends DecryptFragment { - public static final String ARG_FILENAME = "filename"; + public static final String ARG_URI = "uri"; - private static final int RESULT_CODE_FILE = 0x00007003; + private static final int REQUEST_CODE_INPUT = 0x00007003; + private static final int REQUEST_CODE_OUTPUT = 0x00007007; // view - private EditText mFilename; + private TextView mFilename; private CheckBox mDeleteAfter; private BootstrapButton mBrowse; private View mDecryptButton; - private String mInputFilename = null; + // model private Uri mInputUri = null; - private String mOutputFilename = null; private Uri mOutputUri = null; - private FileDialogFragment mFileDialog; - /** * Inflate the layout for this fragment */ @@ -74,17 +67,17 @@ public class DecryptFileFragment extends DecryptFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false); - mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename); + mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename); mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse); mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (Constants.KITKAT) { - FileHelper.openDocument(DecryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + FileHelper.openFile(DecryptFileFragment.this, mInputUri, "*/*", + REQUEST_CODE_INPUT); } } }); @@ -102,78 +95,48 @@ public class DecryptFileFragment extends DecryptFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - String filename = getArguments().getString(ARG_FILENAME); - if (filename != null) { - mFilename.setText(filename); - } + setInputUri(getArguments().getParcelable(ARG_URI)); } - 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); + private void setInputUri(Uri inputUri) { + if (inputUri == null) { + mInputUri = null; + mFilename.setText(""); + return; } - return Constants.Path.APP_DIR + "/" + filename; + + mInputUri = inputUri; + mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); } private void decryptAction() { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - mInputUri = null; - mInputFilename = mFilename.getText().toString(); - } - if (mInputUri == null) { - mOutputFilename = guessOutputFilename(); - } - - if (mInputFilename.equals("")) { //AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); return; } - if (mInputUri == null && mInputFilename.startsWith("file")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - AppMsg.makeText( - getActivity(), - getString(R.string.error_message, - getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) - .show(); - return; - } - } - askForOutputFilename(); } - private void askForOutputFilename() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { - mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); - } else { - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - } - decryptStart(null); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null); + private String removeEncryptedAppend(String name) { + if (name.endsWith(".asc") || name.endsWith(".gpg") || name.endsWith(".pgp")) { + return name.substring(0, name.length() - 4); + } + return name; + } - mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + private void askForOutputFilename() { + String targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); + if (!Constants.KITKAT) { + File file = new File(mInputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, targetName); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT); + } } @Override @@ -189,25 +152,13 @@ public class DecryptFileFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" - + mOutputUri); + Log.d(Constants.TAG, "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.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); - 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.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); @@ -238,14 +189,9 @@ public class DecryptFileFragment extends DecryptFragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog; - if (mInputUri != null) { - deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - } else { - deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - } + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + setInputUri(null); } } } @@ -266,28 +212,17 @@ public class DecryptFileFragment extends DecryptFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case RESULT_CODE_FILE: { + case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { - 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!"); - } - } + setInputUri(data.getData()); + } + return; + } + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mOutputUri = data.getData(); + decryptStart(null); } return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 39d4a09bc..e93a63cc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -23,11 +23,9 @@ import android.net.Uri; import android.os.Bundle; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; @@ -98,11 +96,7 @@ public class EncryptActivity extends DrawerActivity implements @Override public boolean isModeSymmetric() { - if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) { - return true; - } else { - return false; - } + return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem(); } @Override @@ -201,7 +195,7 @@ public class EncryptActivity extends DrawerActivity implements } } else { // Files via content provider, override uri and action - uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); action = ACTION_ENCRYPT; } } @@ -232,23 +226,8 @@ public class EncryptActivity extends DrawerActivity implements mSwitchToContent = PAGER_CONTENT_MESSAGE; } else if (ACTION_ENCRYPT.equals(action) && uri != null) { // encrypt file based on Uri - - // get file path from uri - String path = FileHelper.getPath(this, uri); - - if (path != null) { - mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path); - mSwitchToContent = PAGER_CONTENT_FILE; - } else { - Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported " + - "by Intents. Please use the Remote Service API!" - ); - Toast.makeText(this, R.string.error_only_files_are_supported, - Toast.LENGTH_LONG).show(); - // end activity - finish(); - } + mFileFragmentBundle.putParcelable(EncryptFileFragment.ARG_URI, uri); + mSwitchToContent = PAGER_CONTENT_FILE; } else { Log.e(Constants.TAG, "Include the extra 'text' or an Uri with setData() in your Intent!"); 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 2671e0d40..4da76bdfb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -20,21 +20,19 @@ 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; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.CheckBox; -import android.widget.EditText; import android.widget.Spinner; +import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; import com.devspark.appmsg.AppMsg; @@ -47,7 +45,6 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Log; @@ -55,28 +52,25 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.File; public class EncryptFileFragment extends Fragment { - public static final String ARG_FILENAME = "filename"; + public static final String ARG_URI = "uri"; public static final String ARG_ASCII_ARMOR = "ascii_armor"; - private static final int RESULT_CODE_FILE = 0x00007003; + private static final int REQUEST_CODE_INPUT = 0x00007003; + private static final int REQUEST_CODE_OUTPUT = 0x00007007; private EncryptActivityInterface mEncryptInterface; // view private CheckBox mAsciiArmor = null; private Spinner mFileCompression = null; - private EditText mFilename = null; + private TextView mFilename = null; private CheckBox mDeleteAfter = null; private CheckBox mShareAfter = null; private BootstrapButton mBrowse = null; private View mEncryptFile; - private FileDialogFragment mFileDialog; - // model - private String mInputFilename = null; private Uri mInputUri = null; - private String mOutputFilename = null; private Uri mOutputUri = null; @Override @@ -104,15 +98,15 @@ public class EncryptFileFragment extends Fragment { } }); - mFilename = (EditText) view.findViewById(R.id.filename); + mFilename = (TextView) view.findViewById(R.id.filename); mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (Constants.KITKAT) { - FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + FileHelper.openDocument(EncryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + FileHelper.openFile(EncryptFileFragment.this, mInputUri, "*/*", + REQUEST_CODE_INPUT); } } }); @@ -154,86 +148,45 @@ public class EncryptFileFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - String filename = getArguments().getString(ARG_FILENAME); - if (filename != null) { - mFilename.setText(filename); - } + setInputUri(getArguments().getParcelable(ARG_URI)); boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR); if (asciiArmor) { - mAsciiArmor.setChecked(asciiArmor); + mAsciiArmor.setChecked(true); } } - /** - * Guess output filename based on input path - * - * @param path - * @return Suggestion for output filename - */ - private String guessOutputFilename(String path) { - // output in the same directory but with additional ending - File file = new File(path); - String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); - String outputFilename = file.getParent() + File.separator + file.getName() + ending; + private void setInputUri(Uri inputUri) { + if (inputUri == null) { + mInputUri = null; + mFilename.setText(""); + return; + } - return outputFilename; + mInputUri = inputUri; + mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); } private void showOutputFileDialog() { - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { - mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); - } else { - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - } - encryptStart(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_encrypt_to_file), - getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); - - mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + if (!Constants.KITKAT) { + File file = new File(mInputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + String targetName = FileHelper.getFilename( + getActivity(), mInputUri) + (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); + File targetFile = new File(parentDir, targetName); + FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), + getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), mInputUri) + + (mAsciiArmor.isChecked() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT); + } } private void encryptClicked() { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - mInputUri = null; - mInputFilename = mFilename.getText().toString(); - } - if (mInputUri == null) { - mOutputFilename = guessOutputFilename(mInputFilename); - } - - if (mInputFilename.equals("")) { AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); return; } - if (mInputUri == null && !mInputFilename.startsWith("content")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - AppMsg.makeText( - getActivity(), - getString(R.string.error_message, - getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) - .show(); - return; - } - } - if (mEncryptInterface.isModeSymmetric()) { // symmetric encryption @@ -287,6 +240,10 @@ public class EncryptFileFragment extends Fragment { } private void encryptStart() { + if (mInputUri == null || mOutputUri == null) { + throw new IllegalStateException("Something went terribly wrong if this happens!"); + } + // Send all information needed to service to edit key in other thread Intent intent = new Intent(getActivity(), KeychainIntentService.class); @@ -295,25 +252,13 @@ public class EncryptFileFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" - + mOutputUri); + Log.d(Constants.TAG, "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.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); - 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.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); if (mEncryptInterface.isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -350,25 +295,16 @@ public class EncryptFileFragment extends Fragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog; - if (mInputUri != null) { - deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - } else { - deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - } + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + setInputUri(null); } if (mShareAfter.isChecked()) { // Share encrypted file Intent sendFileIntent = new Intent(Intent.ACTION_SEND); sendFileIntent.setType("*/*"); - if (mOutputUri != null) { - sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); - } else { - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); - } + sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); startActivity(Intent.createChooser(sendFileIntent, getString(R.string.title_share_file))); } @@ -390,28 +326,17 @@ public class EncryptFileFragment extends Fragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case RESULT_CODE_FILE: { + case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { - 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!"); - } - } + setInputUri(data.getData()); + } + return; + } + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mOutputUri = data.getData(); + encryptStart(); } return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 60e5324c5..f24cb379e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -66,7 +66,7 @@ public class ImportKeysFileFragment extends Fragment { // open .asc or .gpg files // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! - FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/", + FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), "*/*", REQUEST_CODE_FILE); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 5eb8ecb8d..48ad13425 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -181,8 +181,8 @@ public class KeyListFragment extends LoaderFragment case R.id.menu_key_list_multi_export: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog( - ids, Constants.Path.APP_DIR_FILE, mAdapter.isAnySecretSelected()); + mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, + mAdapter.isAnySecretSelected()); break; } case R.id.menu_key_list_multi_select_all: { @@ -205,7 +205,7 @@ public class KeyListFragment extends LoaderFragment public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { if (checked) { - mAdapter.setNewSelection(position, checked); + mAdapter.setNewSelection(position, true); } else { mAdapter.removeSelection(position); } @@ -452,7 +452,7 @@ public class KeyListFragment extends LoaderFragment ItemViewHolder holder = new ItemViewHolder(); holder.mMainUserId = (TextView) view.findViewById(R.id.mainUserId); holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - holder.mStatusDivider = (View) view.findViewById(R.id.status_divider); + holder.mStatusDivider = view.findViewById(R.id.status_divider); holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout); holder.mButton = (ImageButton) view.findViewById(R.id.edit); holder.mRevoked = (TextView) view.findViewById(R.id.revoked); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 1912b6e7d..6dc6990e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -312,8 +311,7 @@ public class ViewKeyActivity extends ActionBarActivity implements exportHelper.showExportKeysDialog( new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, - Constants.Path.APP_DIR_FILE, - ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1) + Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1) ); } 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 cae6cf043..f9111d885 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 @@ -18,40 +18,20 @@ package org.sufficientlysecure.keychain.ui.dialog; 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; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.helper.FileHelper; 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 - */ - public static DeleteFileDialogFragment newInstance(String deleteFile) { - DeleteFileDialogFragment frag = new DeleteFileDialogFragment(); - Bundle args = new Bundle(); - - args.putString(ARG_DELETE_FILE, deleteFile); - - frag.setArguments(args); - - return frag; - } - /** * Creates new instance of this delete file dialog fragment */ @@ -74,14 +54,14 @@ public class DeleteFileDialogFragment extends DialogFragment { 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); + String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); alert.setTitle(R.string.warning); - alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile)); + alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFilename)); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @@ -89,12 +69,14 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - if (deleteUri != null) { + if (Constants.KITKAT) { // We can not securely delete Documents, so just use usual delete on them - DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri); - return; + if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) return; } + // TODO!!! We can't delete files from Uri without trying to find it's real path + + /* // Send all information needed to service to edit key in other thread Intent intent = new Intent(activity, KeychainIntentService.class); @@ -102,7 +84,6 @@ public class DeleteFileDialogFragment extends DialogFragment { Bundle data = new Bundle(); intent.setAction(KeychainIntentService.ACTION_DELETE_FILE_SECURELY); - data.putString(KeychainIntentService.DELETE_FILE, deleteFile); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance( @@ -134,6 +115,7 @@ public class DeleteFileDialogFragment extends DialogFragment { // start service with intent activity.startService(intent); + */ } }); alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 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 10a24ddf0..c02c37055 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,13 +23,11 @@ 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; @@ -39,11 +37,17 @@ import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import java.io.File; + +/** + * This is a file chooser dialog no longer used with KitKat + */ public class FileDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_TITLE = "title"; @@ -53,8 +57,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_FILE = "file"; public static final String MESSAGE_DATA_CHECKED = "checked"; private Messenger mMessenger; @@ -64,8 +67,7 @@ public class FileDialogFragment extends DialogFragment { private CheckBox mCheckBox; private TextView mMessageTextView; - private String mOutputFilename; - private Uri mOutputUri; + private File mFile; private static final int REQUEST_CODE = 0x00007004; @@ -73,14 +75,14 @@ public class FileDialogFragment extends DialogFragment { * Creates new instance of this file dialog fragment */ public static FileDialogFragment newInstance(Messenger messenger, String title, String message, - String defaultFile, String checkboxText) { + File defaultFile, String checkboxText) { FileDialogFragment frag = new FileDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); args.putString(ARG_TITLE, title); args.putString(ARG_MESSAGE, message); - args.putString(ARG_DEFAULT_FILE, defaultFile); + args.putString(ARG_DEFAULT_FILE, defaultFile.getAbsolutePath()); args.putString(ARG_CHECKBOX_TEXT, checkboxText); frag.setArguments(args); @@ -99,7 +101,11 @@ public class FileDialogFragment extends DialogFragment { String title = getArguments().getString(ARG_TITLE); String message = getArguments().getString(ARG_MESSAGE); - mOutputFilename = getArguments().getString(ARG_DEFAULT_FILE); + mFile = new File(getArguments().getString(ARG_DEFAULT_FILE)); + if (!mFile.isAbsolute()) { + // We use OK dir by default + mFile = new File(Constants.Path.APP_DIR.getAbsolutePath(), mFile.getName()); + } String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT); LayoutInflater inflater = (LayoutInflater) activity @@ -113,18 +119,14 @@ public class FileDialogFragment extends DialogFragment { mMessageTextView.setText(message); mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(mOutputFilename); + mFilename.setText(mFile.getName()); 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! - if (Constants.KITKAT) { - FileHelper.saveDocument(FileDialogFragment.this, mOutputUri, "*/*", REQUEST_CODE); - } else { - FileHelper.openFile(FileDialogFragment.this, mOutputFilename, "*/*", REQUEST_CODE); - } + FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE); } }); @@ -147,19 +149,23 @@ public class FileDialogFragment extends DialogFragment { dismiss(); String currentFilename = mFilename.getText().toString(); - if (mOutputFilename == null || !mOutputFilename.equals(currentFilename)) { - mOutputUri = null; - mOutputFilename = mFilename.getText().toString(); + if (currentFilename == null || currentFilename.isEmpty()) { + // No file is like pressing cancel, UI: maybe disable positive button in this case? + return; + } + + if (mFile == null || currentFilename.startsWith("/")) { + mFile = new File(currentFilename); + } else if (!mFile.getName().equals(currentFilename)) { + // We update our File object if user changed name! + mFile = new File(mFile.getParentFile(), currentFilename); } 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.putString(MESSAGE_DATA_FILE, mFile.getAbsolutePath()); data.putBoolean(MESSAGE_DATA_CHECKED, checked); sendMessageToHandler(MESSAGE_OKAY, data); @@ -176,44 +182,17 @@ public class FileDialogFragment extends DialogFragment { return alert.show(); } - /** - * Updates filename in dialog, normally called in onActivityResult in activity using the - * FileDialog - */ - private void setFilename(String filename) { - AlertDialog dialog = (AlertDialog) getDialog(); - EditText filenameEditText = (EditText) dialog.findViewById(R.id.input); - - if (filenameEditText != null) { - filenameEditText.setText(filename); - } - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode & 0xFFFF) { case REQUEST_CODE: { if (resultCode == Activity.RESULT_OK && data != null) { - 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(); - } + File file = new File(data.getData().getPath()); + if (file.getParentFile().exists()) { + mFile = file; + mFilename.setText(mFile.getName()); } 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); - } + AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index e24ac6925..5600aa06e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -195,6 +195,7 @@ Wrong passphrase. Set a passphrase first. No compatible file manager installed. + The file manager does not support saving. The passphrases didn\'t match. Please enter a passphrase. Symmetric encryption. -- cgit v1.2.3 From 50e72b196fa8bc97edc63198a9ad73c24770b9df Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 1 Jul 2014 01:26:50 +0200 Subject: Missing import after merge --- .../org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java | 1 + 1 file changed, 1 insertion(+) 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 15d50d7ed..80341aeaa 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 @@ -35,6 +35,7 @@ import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; +import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; -- cgit v1.2.3 From 35647734104471a6d35bc26a77682dd5531dd5e3 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 1 Jul 2014 14:50:15 +0200 Subject: Add temporary file storage as discussed in #665 Writable from OpenKeychain, readable worldwide. Should be used to write shared files to it by first creating the file using TemporaryStorageProvider.createFile and then write to the Uri returned. --- OpenKeychain/src/main/AndroidManifest.xml | 9 ++ .../org/sufficientlysecure/keychain/Constants.java | 2 + .../keychain/KeychainApplication.java | 3 + .../provider/TemporaryStorageProvider.java | 151 +++++++++++++++++++++ .../keychain/util/DatabaseUtil.java | 36 +++++ 5 files changed, 201 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 3ce200008..6a67ac9bf 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -49,6 +49,9 @@ android:name="android.hardware.touchscreen" android:required="false" /> + + + @@ -484,6 +487,12 @@ android:resource="@xml/custom_pgp_contacts_structure"/> + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index eeb9fa389..956019605 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -53,6 +53,8 @@ public final class Constants { public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + public static int TEMPFILE_TTL = 24*60*60*1000; // 1 day + public static final class Path { public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index be9c1e405..125573b53 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -28,6 +28,7 @@ import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.TlsHelper; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; @@ -85,6 +86,8 @@ public class KeychainApplication extends Application { Preferences.getPreferences(this).updateKeyServers(); TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer"); + + TemporaryStorageProvider.cleanUp(this); } public static void setupAccountAsNeeded(Context context) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java new file mode 100644 index 000000000..9e745215d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -0,0 +1,151 @@ +package org.sufficientlysecure.keychain.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.DatabaseUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class TemporaryStorageProvider extends ContentProvider { + + private static final String DB_NAME = "tempstorage.db"; + private static final String TABLE_FILES = "files"; + private static final String COLUMN_ID = "id"; + private static final String COLUMN_NAME = "name"; + private static final String COLUMN_TIME = "time"; + private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); + private static final int DB_VERSION = 1; + + public static Uri createFile(Context context, String targetName) { + ContentValues contentValues = new ContentValues(); + contentValues.put(COLUMN_NAME, targetName); + return context.getContentResolver().insert(BASE_URI, contentValues); + } + + public static int cleanUp(Context context) { + return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?", + new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}); + } + + private class TemporaryStorageDatabase extends SQLiteOpenHelper { + + public TemporaryStorageDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_NAME + " TEXT, " + + COLUMN_TIME + " INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + } + + private TemporaryStorageDatabase db; + + private File getFile(Uri uri) throws FileNotFoundException { + try { + return getFile(Integer.parseInt(uri.getLastPathSegment())); + } catch (NumberFormatException e) { + throw new FileNotFoundException(); + } + } + + private File getFile(int id) { + return new File(getContext().getCacheDir(), "temp/" + id); + } + + @Override + public boolean onCreate() { + db = new TemporaryStorageDatabase(getContext()); + return new File(getContext().getCacheDir(), "temp").mkdirs(); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + File file; + try { + file = getFile(uri); + } catch (FileNotFoundException e) { + 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) { + if (fileName.moveToNext()) { + MatrixCursor cursor = + new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"}); + cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath()); + fileName.close(); + return cursor; + } + fileName.close(); + } + return null; + } + + @Override + public String getType(Uri uri) { + return "*/*"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + if (!values.containsKey(COLUMN_TIME)) { + values.put(COLUMN_TIME, System.currentTimeMillis()); + } + int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); + try { + getFile(insert).createNewFile(); + } catch (IOException e) { + return null; + } + return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + if (uri.getLastPathSegment() != null) { + selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?"); + selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()}); + } + Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection, + selectionArgs, null, null, null); + if (files != null) { + while (files.moveToNext()) { + getFile(files.getInt(0)).delete(); + } + files.close(); + return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); + } + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Update not supported"); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + return openFileHelper(uri, mode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java new file mode 100644 index 000000000..c18e5cabd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java @@ -0,0 +1,36 @@ +package org.sufficientlysecure.keychain.util; + +import android.text.TextUtils; + +/** + * Shamelessly copied from android.database.DatabaseUtils + */ +public class DatabaseUtil { + /** + * Concatenates two SQL WHERE clauses, handling empty or null values. + */ + public static String concatenateWhere(String a, String b) { + if (TextUtils.isEmpty(a)) { + return b; + } + if (TextUtils.isEmpty(b)) { + return a; + } + + return "(" + a + ") AND (" + b + ")"; + } + + /** + * Appends one set of selection args to another. This is useful when adding a selection + * argument to a user provided set. + */ + public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) { + if (originalValues == null || originalValues.length == 0) { + return newValues; + } + String[] result = new String[originalValues.length + newValues.length ]; + System.arraycopy(originalValues, 0, result, 0, originalValues.length); + System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); + return result; + } +} -- cgit v1.2.3 From 93eae114eac566a9ed2da6275c147b66ca480305 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 2 Jul 2014 00:34:21 +0200 Subject: Encrypt/Decrypt UI work --- .../keychain/provider/CachedPublicKeyRing.java | 14 +-- .../keychain/ui/DecryptFileFragment.java | 5 +- .../keychain/ui/EncryptActivity.java | 11 ++ .../keychain/ui/EncryptActivityInterface.java | 1 + .../keychain/ui/EncryptAsymmetricFragment.java | 24 +++- .../keychain/ui/EncryptFileFragment.java | 48 ++++++-- .../keychain/ui/EncryptMessageFragment.java | 17 ++- .../ui/dialog/DeleteFileDialogFragment.java | 59 +++------- .../src/main/res/layout/decrypt_file_fragment.xml | 53 +++++---- .../res/layout/encrypt_content_adv_settings.xml | 13 --- .../src/main/res/layout/encrypt_file_fragment.xml | 121 +++++++++++++-------- OpenKeychain/src/main/res/values/strings.xml | 2 + 12 files changed, 216 insertions(+), 152 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 48d40430a..52ca71679 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -62,7 +62,7 @@ public class CachedPublicKeyRing extends KeyRing { public String getPrimaryUserId() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING); return (String) data; } catch(ProviderHelper.NotFoundException e) { @@ -73,7 +73,7 @@ public class CachedPublicKeyRing extends KeyRing { public boolean isRevoked() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.IS_REVOKED, ProviderHelper.FIELD_TYPE_INTEGER); return (Long) data > 0; } catch(ProviderHelper.NotFoundException e) { @@ -84,7 +84,7 @@ public class CachedPublicKeyRing extends KeyRing { public boolean canCertify() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.CAN_CERTIFY, ProviderHelper.FIELD_TYPE_INTEGER); return (Long) data > 0; } catch(ProviderHelper.NotFoundException e) { @@ -106,7 +106,7 @@ public class CachedPublicKeyRing extends KeyRing { public boolean hasEncrypt() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.HAS_ENCRYPT, ProviderHelper.FIELD_TYPE_INTEGER); return (Long) data > 0; } catch(ProviderHelper.NotFoundException e) { @@ -128,7 +128,7 @@ public class CachedPublicKeyRing extends KeyRing { public boolean hasSign() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.HAS_SIGN, ProviderHelper.FIELD_TYPE_INTEGER); return (Long) data > 0; } catch(ProviderHelper.NotFoundException e) { @@ -139,7 +139,7 @@ public class CachedPublicKeyRing extends KeyRing { public int getVerified() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.VERIFIED, ProviderHelper.FIELD_TYPE_INTEGER); return (Integer) data; } catch(ProviderHelper.NotFoundException e) { @@ -150,7 +150,7 @@ public class CachedPublicKeyRing extends KeyRing { public boolean hasAnySecret() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.HAS_ANY_SECRET, ProviderHelper.FIELD_TYPE_INTEGER); return (Long) data > 0; } catch(ProviderHelper.NotFoundException e) { 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 430f85b6f..520ef7567 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -29,7 +29,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; -import android.widget.ImageButton; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -52,7 +51,6 @@ public class DecryptFileFragment extends DecryptFragment { // view private TextView mFilename; private CheckBox mDeleteAfter; - private ImageButton mBrowse; private View mDecryptButton; // model @@ -67,10 +65,9 @@ public class DecryptFileFragment extends DecryptFragment { View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false); mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename); - mBrowse = (ImageButton) view.findViewById(R.id.decrypt_file_browse); mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); - mBrowse.setOnClickListener(new View.OnClickListener() { + view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (Constants.KITKAT) { FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index e93a63cc8..c77fe9ab8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -70,6 +70,7 @@ public class EncryptActivity extends DrawerActivity implements // model used by message and file fragments private long mEncryptionKeyIds[] = null; + private String mEncryptionUserIds[] = null; private long mSigningKeyId = Constants.key.none; private String mPassphrase; private String mPassphraseAgain; @@ -84,6 +85,11 @@ public class EncryptActivity extends DrawerActivity implements mEncryptionKeyIds = encryptionKeyIds; } + @Override + public void onEncryptionUserSelected(String[] encryptionUserIds) { + mEncryptionUserIds = encryptionUserIds; + } + @Override public void onPassphraseUpdate(String passphrase) { mPassphrase = passphrase; @@ -109,6 +115,11 @@ public class EncryptActivity extends DrawerActivity implements return mEncryptionKeyIds; } + @Override + public String[] getEncryptionUsers() { + return mEncryptionUserIds; + } + @Override public String getPassphrase() { return mPassphrase; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java index 0786b3a16..ca2ee3b55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -23,6 +23,7 @@ public interface EncryptActivityInterface { public long getSignatureKey(); public long[] getEncryptionKeys(); + public String[] getEncryptionUsers(); public String getPassphrase(); public String getPassphraseAgain(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index 51963e963..be845f05e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -59,12 +59,15 @@ public class EncryptAsymmetricFragment extends Fragment { // model private long mSecretKeyId = Constants.key.none; private long mEncryptionKeyIds[] = null; + private String mEncryptionUserIds[] = null; // Container Activity must implement this interface public interface OnAsymmetricKeySelection { public void onSigningKeySelected(long signingKeyId); public void onEncryptionKeysSelected(long[] encryptionKeyIds); + + public void onEncryptionUserSelected(String[] encryptionUserIds); } @Override @@ -91,6 +94,13 @@ public class EncryptAsymmetricFragment extends Fragment { updateView(); } + private void setEncryptionUserIds(String[] encryptionUserIds) { + mEncryptionUserIds = encryptionUserIds; + // update key selection in EncryptActivity + mKeySelectionListener.onEncryptionUserSelected(encryptionUserIds); + updateView(); + } + /** * Inflate the layout for this fragment */ @@ -159,12 +169,14 @@ public class EncryptAsymmetricFragment extends Fragment { if (preselectedEncryptionKeyIds != null) { Vector goodIds = new Vector(); - for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { + Vector goodUserIds = new Vector(); + for (long preselectedId : preselectedEncryptionKeyIds) { try { - long id = providerHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( - preselectedEncryptionKeyIds[i]) - ).getMasterKeyId(); + CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId)); + long id = ring.getMasterKeyId(); + ring.getSplitPrimaryUserId(); + goodUserIds.add(ring.getPrimaryUserId()); goodIds.add(id); } catch (PgpGeneralException e) { Log.e(Constants.TAG, "key not found!", e); @@ -176,6 +188,7 @@ public class EncryptAsymmetricFragment extends Fragment { keyIds[i] = goodIds.get(i); } setEncryptionKeyIds(keyIds); + setEncryptionUserIds(goodUserIds.toArray(new String[goodUserIds.size()])); } } } @@ -249,6 +262,7 @@ public class EncryptAsymmetricFragment extends Fragment { Bundle bundle = data.getExtras(); setEncryptionKeyIds(bundle .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS)); + setEncryptionUserIds(bundle.getStringArray(SelectPublicKeyActivity.RESULT_EXTRA_USER_IDS)); } break; } 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 2fabeb82c..3111e5c3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -33,12 +33,13 @@ import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.Spinner; import android.widget.TextView; -import android.widget.ImageButton; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.service.KeychainIntentService; @@ -50,6 +51,8 @@ import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Log; import java.io.File; +import java.util.HashSet; +import java.util.Set; public class EncryptFileFragment extends Fragment { public static final String ARG_URI = "uri"; @@ -65,8 +68,7 @@ public class EncryptFileFragment extends Fragment { private Spinner mFileCompression = null; private TextView mFilename = null; private CheckBox mDeleteAfter = null; - private CheckBox mShareAfter = null; - private ImageButton mBrowse = null; + private View mShareFile; private View mEncryptFile; // model @@ -94,13 +96,19 @@ public class EncryptFileFragment extends Fragment { mEncryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - encryptClicked(); + encryptClicked(false); + } + }); + mShareFile = view.findViewById(R.id.action_encrypt_share); + mShareFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(true); } }); mFilename = (TextView) view.findViewById(R.id.filename); - mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { + view.findViewById(R.id.btn_browse).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (Constants.KITKAT) { FileHelper.openDocument(EncryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); @@ -136,7 +144,6 @@ public class EncryptFileFragment extends Fragment { } mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption); - mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption); mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor); mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor()); @@ -181,7 +188,7 @@ public class EncryptFileFragment extends Fragment { } } - private void encryptClicked() { + private void encryptClicked(boolean share) { if (mInputUri == null) { AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); return; @@ -236,10 +243,17 @@ public class EncryptFileFragment extends Fragment { } } - showOutputFileDialog(); + if (share) { + String targetName = FileHelper.getFilename(getActivity(), mInputUri) + + (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); + mOutputUri = TemporaryStorageProvider.createFile(getActivity(), targetName); + encryptStart(true); + } else { + showOutputFileDialog(); + } } - private void encryptStart() { + private void encryptStart(final boolean share) { if (mInputUri == null || mOutputUri == null) { throw new IllegalStateException("Something went terribly wrong if this happens!"); } @@ -300,11 +314,21 @@ public class EncryptFileFragment extends Fragment { setInputUri(null); } - if (mShareAfter.isChecked()) { + if (share) { // Share encrypted file Intent sendFileIntent = new Intent(Intent.ACTION_SEND); sendFileIntent.setType("*/*"); sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); + if (!mEncryptInterface.isModeSymmetric() && mEncryptInterface.getEncryptionUsers() != null) { + Set users = new HashSet(); + for (String user : mEncryptInterface.getEncryptionUsers()) { + String[] userId = KeyRing.splitUserId(user); + if (userId[1] != null) { + users.add(userId[1]); + } + } + sendFileIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); + } startActivity(Intent.createChooser(sendFileIntent, getString(R.string.title_share_file))); } @@ -336,7 +360,7 @@ public class EncryptFileFragment extends Fragment { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { mOutputUri = data.getData(); - encryptStart(); + encryptStart(false); } 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 8a6103b16..e4f63089f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -36,12 +36,17 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class EncryptMessageFragment extends Fragment { public static final String ARG_TEXT = "text"; @@ -235,7 +240,17 @@ public class EncryptMessageFragment extends Fragment { // Type is set to text/plain so that encrypted messages can // be sent with Whatsapp, Hangouts, SMS etc... sendIntent.setType("text/plain"); - + Log.d(Constants.TAG, "encrypt to:" + Arrays.toString(mEncryptInterface.getEncryptionUsers())); + if (!mEncryptInterface.isModeSymmetric() && mEncryptInterface.getEncryptionUsers() != null) { + Set users = new HashSet(); + for (String user : mEncryptInterface.getEncryptionUsers()) { + String[] userId = KeyRing.splitUserId(user); + if (userId[1] != null) { + users.add(userId[1]); + } + } + sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); + } sendIntent.putExtra(Intent.EXTRA_TEXT, output); startActivity(Intent.createChooser(sendIntent, getString(R.string.title_share_with))); 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 f9111d885..27ce4faee 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 @@ -25,6 +25,7 @@ import android.provider.DocumentsContract; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; @@ -53,8 +54,8 @@ 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; - String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); + final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI); + final String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -69,53 +70,23 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); + // We can not securely delete Uris, so just use usual delete on them if (Constants.KITKAT) { - // We can not securely delete Documents, so just use usual delete on them - if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) return; - } - - // TODO!!! We can't delete files from Uri without trying to find it's real path - - /* - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(activity, KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - - intent.setAction(KeychainIntentService.ACTION_DELETE_FILE_SECURELY); - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance( - getString(R.string.progress_deleting_securely), - ProgressDialog.STYLE_HORIZONTAL, - false, - null); - - // Message is received after deleting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = - new KeychainIntentServiceHandler(activity, deletingDialog) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Toast.makeText(activity, R.string.file_delete_successful, - Toast.LENGTH_SHORT).show(); - } + if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; } - }; + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; + } - // show progress dialog - deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog"); + Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, deleteFilename), Toast.LENGTH_SHORT).show(); - // start service with intent - activity.startService(intent); - */ + // TODO: We can't delete that file... + // If possible we should find out if deletion is possible before even showing the option to do so. } }); alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { diff --git a/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml index 098aaaea1..6ff827894 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml @@ -26,32 +26,41 @@ android:orientation="vertical"> - - + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" - + android:clickable="true" + style="@style/SelectableItem"> + + + + + + - - - - - - - - - + android:layout_height="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + + android:id="@+id/btn_browse" + android:clickable="true" + style="@style/SelectableItem"> + + + + + + - + + + + + + + + + + + android:layout_above="@+id/action_encrypt_share"/> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 25cdfb541..6823c3c57 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -55,6 +55,7 @@ Decrypt and verify message From Clipboard Encrypt and save file + Encrypt and share file Save Cancel Delete @@ -105,6 +106,7 @@ Sign Message File + File: No Passphrase Passphrase Again -- cgit v1.2.3 From 51a4b0466ba1e1c1c72d9d8112c28628d1efc84b Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 3 Jul 2014 00:34:41 +0200 Subject: Add support for multiple input/output URIs to KeychainIntentService --- .../keychain/service/KeychainIntentService.java | 91 +++++++++++++--------- 1 file changed, 56 insertions(+), 35 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 a2676b1a4..d87f98775 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -109,6 +109,9 @@ public class KeychainIntentService extends IntentService 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; + public static final int IO_URIS = 4; + + public static final String SELECTED_URI = "selected_uri"; // encrypt public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id"; @@ -118,8 +121,10 @@ public class KeychainIntentService extends IntentService 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_INPUT_URIS = "input_uris"; public static final String ENCRYPT_OUTPUT_FILE = "output_file"; public static final String ENCRYPT_OUTPUT_URI = "output_uri"; + public static final String ENCRYPT_OUTPUT_URIS = "output_uris"; public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; // decrypt/verify @@ -220,6 +225,7 @@ public class KeychainIntentService extends IntentService try { /* Input */ int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); + Bundle resultData = new Bundle(); long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID); String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); @@ -227,44 +233,48 @@ 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); - InputData inputData = createEncryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); - - /* Operation */ - PgpSignEncrypt.Builder builder = - new PgpSignEncrypt.Builder( - new ProviderHelper(this), - PgpHelper.getFullVersion(this), - inputData, outStream); - builder.setProgressable(this); + int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1; + for (int i = 0; i < urisCount; i++) { + data.putInt(SELECTED_URI, i); + InputData inputData = createEncryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); + + /* Operation */ + PgpSignEncrypt.Builder builder = + new PgpSignEncrypt.Builder( + new ProviderHelper(this), + PgpHelper.getFullVersion(this), + inputData, outStream); + builder.setProgressable(this); + + builder.setEnableAsciiArmorOutput(useAsciiArmor) + .setCompressionId(compressionId) + .setSymmetricEncryptionAlgorithm( + Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) + .setSignatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) + .setEncryptionMasterKeyIds(encryptionKeyIds) + .setSymmetricPassphrase(symmetricPassphrase) + .setSignatureMasterKeyId(signatureKeyId) + .setEncryptToSigner(true) + .setSignatureHashAlgorithm( + Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .setSignaturePassphrase( + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); + + // this assumes that the bytes are cleartext (valid for current implementation!) + if (source == IO_BYTES) { + builder.setCleartextInput(true); + } - builder.setEnableAsciiArmorOutput(useAsciiArmor) - .setCompressionId(compressionId) - .setSymmetricEncryptionAlgorithm( - Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) - .setSignatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) - .setEncryptionMasterKeyIds(encryptionKeyIds) - .setSymmetricPassphrase(symmetricPassphrase) - .setSignatureMasterKeyId(signatureKeyId) - .setEncryptToSigner(true) - .setSignatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .setSignaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); - - // this assumes that the bytes are cleartext (valid for current implementation!) - if (source == IO_BYTES) { - builder.setCleartextInput(true); - } + builder.build().execute(); - builder.build().execute(); + outStream.close(); - outStream.close(); + /* Output */ - /* Output */ + finalizeEncryptOutputStream(data, resultData, outStream); - Bundle resultData = new Bundle(); - finalizeEncryptOutputStream(data, resultData, outStream); + } OtherHelper.logDebugBundle(resultData, "resultData"); @@ -688,8 +698,13 @@ public class KeychainIntentService extends IntentService Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI); // InputStream - InputStream in = getContentResolver().openInputStream(providerUri); - return new InputData(in, 0); + return new InputData(getContentResolver().openInputStream(providerUri), 0); + + case IO_URIS: + providerUri = data.getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI)); + + // InputStream + return new InputData(getContentResolver().openInputStream(providerUri), 0); default: throw new PgpGeneralException("No target choosen!"); @@ -719,6 +734,11 @@ public class KeychainIntentService extends IntentService return getContentResolver().openOutputStream(providerUri); + case IO_URIS: + providerUri = data.getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI)); + + return getContentResolver().openOutputStream(providerUri); + default: throw new PgpGeneralException("No target choosen!"); } @@ -744,6 +764,7 @@ public class KeychainIntentService extends IntentService break; case IO_URI: + case IO_URIS: // nothing, output was written, just send okay and verification bundle break; -- cgit v1.2.3 From 1b0666e9de5caea14997a3e638a6209b45c97d60 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Sun, 6 Jul 2014 02:10:35 +0200 Subject: Many changes to file ... and still incomplete - Multi file - Reworked UI --- .gitmodules | 3 + OpenKeychain/build.gradle | 2 +- OpenKeychain/src/main/AndroidManifest.xml | 1 + .../keychain/helper/ContactHelper.java | 14 ++ .../keychain/helper/FileHelper.java | 41 +++++ .../keychain/provider/CachedPublicKeyRing.java | 62 ++++++- .../keychain/provider/ProviderHelper.java | 4 + .../keychain/ui/EncryptActivity.java | 77 +++++++- .../keychain/ui/EncryptActivityInterface.java | 2 + .../keychain/ui/EncryptAsymmetricFragment.java | 105 +++++++---- .../keychain/ui/EncryptFileFragment.java | 190 +++++++++++++------ .../ui/widget/EncryptKeyCompletionView.java | 204 +++++++++++++++++++++ .../ui/widget/NoSwipeWrapContentViewPager.java | 44 +++++ .../res/drawable-hdpi/attachment_bg_holo.9.png | Bin 0 -> 282 bytes .../main/res/drawable-hdpi/ic_doc_generic_am.png | Bin 0 -> 694 bytes .../src/main/res/drawable-hdpi/ic_generic_man.png | Bin 0 -> 2375 bytes .../res/drawable-mdpi/attachment_bg_holo.9.png | Bin 0 -> 204 bytes .../main/res/drawable-mdpi/ic_doc_generic_am.png | Bin 0 -> 561 bytes .../src/main/res/drawable-mdpi/ic_generic_man.png | Bin 0 -> 1657 bytes .../res/drawable-xhdpi/attachment_bg_holo.9.png | Bin 0 -> 344 bytes .../main/res/drawable-xhdpi/ic_doc_generic_am.png | Bin 0 -> 831 bytes .../src/main/res/drawable-xhdpi/ic_generic_man.png | Bin 0 -> 3149 bytes .../res/drawable-xxhdpi/attachment_bg_holo.9.png | Bin 0 -> 1316 bytes .../main/res/drawable-xxhdpi/ic_doc_generic_am.png | Bin 0 -> 585 bytes .../main/res/drawable-xxhdpi/ic_generic_man.png | Bin 0 -> 3607 bytes .../src/main/res/layout/encrypt_activity.xml | 13 +- .../res/layout/encrypt_asymmetric_fragment.xml | 64 +------ .../src/main/res/layout/encrypt_content.xml | 16 +- .../res/layout/encrypt_content_adv_settings.xml | 26 --- .../src/main/res/layout/encrypt_file_fragment.xml | 150 ++++++--------- .../main/res/layout/encrypt_symmetric_fragment.xml | 74 ++++---- .../src/main/res/layout/file_list_entry.xml | 60 ++++++ .../src/main/res/layout/file_list_entry_add.xml | 21 +++ .../src/main/res/layout/recipient_box_entry.xml | 24 +++ .../res/layout/recipient_selection_list_entry.xml | 42 +++++ .../src/main/res/menu/encrypt_activity.xml | 7 + OpenKeychain/src/main/res/values/strings.xml | 1 + extern/TokenAutoComplete | 1 + 38 files changed, 902 insertions(+), 346 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png create mode 100644 OpenKeychain/src/main/res/layout/file_list_entry.xml create mode 100644 OpenKeychain/src/main/res/layout/file_list_entry_add.xml create mode 100644 OpenKeychain/src/main/res/layout/recipient_box_entry.xml create mode 100644 OpenKeychain/src/main/res/layout/recipient_selection_list_entry.xml create mode 100644 OpenKeychain/src/main/res/menu/encrypt_activity.xml create mode 160000 extern/TokenAutoComplete diff --git a/.gitmodules b/.gitmodules index b7b0e1173..b01f9a0ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "OpenKeychain/src/test/resources/extern/OpenPGP-Haskell"] path = OpenKeychain/src/test/resources/extern/OpenPGP-Haskell url = https://github.com/singpolyma/OpenPGP-Haskell.git +[submodule "extern/TokenAutoComplete"] + path = extern/TokenAutoComplete + url = https://github.com/open-keychain/TokenAutoComplete diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index f419141b4..18a801ce0 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -20,7 +20,7 @@ dependencies { compile project(':extern:SuperToasts:supertoasts') compile project(':extern:minidns') compile project(':extern:KeybaseLib:Lib') - + compile project(':extern:TokenAutoComplete:library') // Unit tests are run with Robolectric testCompile 'junit:junit:4.11' diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 6a67ac9bf..bed47a44a 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -155,6 +155,7 @@ + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index e639824ec..1b9ef57b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -21,6 +21,8 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.content.*; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; @@ -33,6 +35,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; +import java.io.InputStream; import java.util.*; public class ContactHelper { @@ -232,6 +235,17 @@ public class ContactHelper { return null; } + public static Bitmap photoFromFingerprint(ContentResolver contentResolver, String fingerprint) { + int rawContactId = findRawContactId(contentResolver, fingerprint); + if (rawContactId == -1) return null; + Uri rawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); + Uri contactUri = ContactsContract.RawContacts.getContactLookupUri(contentResolver, rawContactUri); + InputStream photoInputStream = + ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri); + if (photoInputStream == null) return null; + return BitmapFactory.decodeStream(photoInputStream); + } + /** * Write the current Keychain to the contact db */ 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 2898c7030..e42c7987b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -23,22 +23,28 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.provider.DocumentsContract; import android.provider.OpenableColumns; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import java.io.File; +import java.text.DecimalFormat; public class FileHelper { @@ -182,6 +188,41 @@ public class FileHelper { return filename; } + public static long getFileSize(Context context, Uri uri) { + long size = -1; + try { + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null); + + if (cursor != null) { + if (cursor.moveToNext()) { + size = cursor.getLong(0); + } + cursor.close(); + } + } catch (Exception ignored) { + // This happens in rare cases (eg: document deleted since selection) and should not cause a failure + } + return size; + } + + /** + * Retrieve thumbnail of file, document api feature and thus KitKat only + */ + public static Bitmap getThumbnail(Context context, Uri uri, Point size) { + if (Constants.KITKAT) { + return DocumentsContract.getDocumentThumbnail(context.getContentResolver(), uri, size, null); + } else { + return null; + } + } + + public static String readableFileSize(long size) { + if(size <= 0) return "0"; + final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; + int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); + return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + public static interface FileDialogCallback { public void onFileSelected(File file, boolean checked); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 52ca71679..bc7221d13 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -1,5 +1,6 @@ package org.sufficientlysecure.keychain.provider; +import android.database.Cursor; import android.net.Uri; import org.sufficientlysecure.keychain.Constants; @@ -33,6 +34,7 @@ public class CachedPublicKeyRing extends KeyRing { mUri = uri; } + @Override public long getMasterKeyId() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -59,6 +61,17 @@ public class CachedPublicKeyRing extends KeyRing { return getMasterKeyId(); } + public byte[] getFingerprint() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + return (byte[]) data; + } catch (ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + @Override public String getPrimaryUserId() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -70,6 +83,7 @@ public class CachedPublicKeyRing extends KeyRing { } } + @Override public boolean isRevoked() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -81,6 +95,7 @@ public class CachedPublicKeyRing extends KeyRing { } } + @Override public boolean canCertify() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -92,17 +107,28 @@ public class CachedPublicKeyRing extends KeyRing { } } + @Override public long getEncryptId() throws PgpGeneralException { try { - Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, - ProviderHelper.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(ProviderHelper.NotFoundException e) { + Cursor subkeys = getSubkeys(); + if (subkeys != null) { + try { + while (subkeys.moveToNext()) { + if (subkeys.getInt(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.CAN_ENCRYPT)) != 0) { + return subkeys.getLong(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.KEY_ID)); + } + } + } finally { + subkeys.close(); + } + } + } catch(Exception e) { throw new PgpGeneralException(e); } + throw new PgpGeneralException("No encrypt key found"); } + @Override public boolean hasEncrypt() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -114,17 +140,28 @@ public class CachedPublicKeyRing extends KeyRing { } } + @Override public long getSignId() throws PgpGeneralException { try { - Object data = mProviderHelper.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, - ProviderHelper.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(ProviderHelper.NotFoundException e) { + Cursor subkeys = getSubkeys(); + if (subkeys != null) { + try { + while (subkeys.moveToNext()) { + if (subkeys.getInt(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.CAN_SIGN)) != 0) { + return subkeys.getLong(subkeys.getColumnIndexOrThrow(KeychainContract.Keys.KEY_ID)); + } + } + } finally { + subkeys.close(); + } + } + } catch(Exception e) { throw new PgpGeneralException(e); } + throw new PgpGeneralException("No sign key found"); } + @Override public boolean hasSign() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -136,6 +173,7 @@ public class CachedPublicKeyRing extends KeyRing { } } + @Override public int getVerified() throws PgpGeneralException { try { Object data = mProviderHelper.getGenericData(mUri, @@ -156,6 +194,10 @@ public class CachedPublicKeyRing extends KeyRing { } catch(ProviderHelper.NotFoundException e) { throw new PgpGeneralException(e); } + } + private Cursor getSubkeys() throws PgpGeneralException { + Uri keysUri = KeychainContract.Keys.buildKeysUri(Long.toString(extractOrGetMasterKeyId())); + return mProviderHelper.getContentResolver().query(keysUri, null, null, null, null); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 28495d51d..4bf3a38a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -1034,4 +1034,8 @@ public class ProviderHelper { } } } + + public ContentResolver getContentResolver() { + return mContentResolver; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index c77fe9ab8..5542cccd1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -21,14 +21,21 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; +import java.util.ArrayList; + public class EncryptActivity extends DrawerActivity implements EncryptSymmetricFragment.OnSymmetricKeySelection, EncryptAsymmetricFragment.OnAsymmetricKeySelection, @@ -49,7 +56,7 @@ public class EncryptActivity extends DrawerActivity implements // view ViewPager mViewPagerMode; - PagerTabStrip mPagerTabStripMode; + //PagerTabStrip mPagerTabStripMode; PagerTabStripAdapter mTabsAdapterMode; ViewPager mViewPagerContent; PagerTabStrip mPagerTabStripContent; @@ -74,6 +81,9 @@ public class EncryptActivity extends DrawerActivity implements private long mSigningKeyId = Constants.key.none; private String mPassphrase; private String mPassphraseAgain; + private int mCurrentMode = PAGER_MODE_ASYMMETRIC; + private boolean mUseArmor; + private boolean mDeleteAfterEncrypt = false; @Override public void onSigningKeySelected(long signingKeyId) { @@ -102,7 +112,7 @@ public class EncryptActivity extends DrawerActivity implements @Override public boolean isModeSymmetric() { - return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem(); + return PAGER_MODE_SYMMETRIC == mCurrentMode; } @Override @@ -130,10 +140,19 @@ public class EncryptActivity extends DrawerActivity implements return mPassphraseAgain; } + @Override + public boolean isUseArmor() { + return mUseArmor; + } + + @Override + public boolean isDeleteAfterEncrypt() { + return mDeleteAfterEncrypt; + } private void initView() { mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode); - mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); + //mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content); mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content); @@ -172,6 +191,37 @@ public class EncryptActivity extends DrawerActivity implements mTabsAdapterContent.addTab(EncryptFileFragment.class, mFileFragmentBundle, getString(R.string.label_file)); mViewPagerContent.setCurrentItem(mSwitchToContent); + + mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.encrypt_activity, menu); + menu.findItem(R.id.check_use_armor).setChecked(mUseArmor); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.isCheckable()) { + item.setChecked(!item.isChecked()); + } + switch (item.getItemId()) { + case R.id.check_use_symmetric: + mSwitchToMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC; + mViewPagerMode.setCurrentItem(mSwitchToMode); + break; + case R.id.check_use_armor: + mUseArmor = item.isChecked(); + break; + case R.id.check_delete_after_encrypt: + mDeleteAfterEncrypt = item.isChecked(); + break; + default: + return super.onOptionsItemSelected(item); + } + return true; } /** @@ -183,12 +233,16 @@ public class EncryptActivity extends DrawerActivity implements String action = intent.getAction(); Bundle extras = intent.getExtras(); String type = intent.getType(); - Uri uri = intent.getData(); + ArrayList uris = new ArrayList(); if (extras == null) { extras = new Bundle(); } + if (intent.getData() != null) { + uris.add(intent.getData()); + } + /* * Android's Action */ @@ -206,14 +260,19 @@ public class EncryptActivity extends DrawerActivity implements } } else { // Files via content provider, override uri and action - uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + uris.clear(); + uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); action = ACTION_ENCRYPT; } } + if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { + uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + action = ACTION_ENCRYPT; + } + if (extras.containsKey(EXTRA_ASCII_ARMOR)) { - boolean requestAsciiArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true); - mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, requestAsciiArmor); + mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true); } String textData = extras.getString(EXTRA_TEXT); @@ -235,9 +294,9 @@ public class EncryptActivity extends DrawerActivity implements // encrypt text based on given extra mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData); mSwitchToContent = PAGER_CONTENT_MESSAGE; - } else if (ACTION_ENCRYPT.equals(action) && uri != null) { + } else if (ACTION_ENCRYPT.equals(action) && uris != null && !uris.isEmpty()) { // encrypt file based on Uri - mFileFragmentBundle.putParcelable(EncryptFileFragment.ARG_URI, uri); + mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris); mSwitchToContent = PAGER_CONTENT_FILE; } else { Log.e(Constants.TAG, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java index ca2ee3b55..6d649c32e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -28,4 +28,6 @@ public interface EncryptActivityInterface { public String getPassphrase(); public String getPassphraseAgain(); + boolean isUseArmor(); + boolean isDeleteAfterEncrypt(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index be845f05e..dc9cfe72e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -19,25 +19,30 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.TextView; -import android.widget.Button; +import android.widget.*; +import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.util.Log; -import java.util.Vector; +import java.util.*; public class EncryptAsymmetricFragment extends Fragment { public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id"; @@ -51,10 +56,8 @@ public class EncryptAsymmetricFragment extends Fragment { OnAsymmetricKeySelection mKeySelectionListener; // view - private Button mSelectKeysButton; private CheckBox mSign; - private TextView mMainUserId; - private TextView mMainUserIdRest; + private EncryptKeyCompletionView mEncryptKeyView; // model private long mSecretKeyId = Constants.key.none; @@ -108,15 +111,7 @@ public class EncryptAsymmetricFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); - mSelectKeysButton = (Button) view.findViewById(R.id.btn_selectEncryptKeys); mSign = (CheckBox) view.findViewById(R.id.sign); - mMainUserId = (TextView) view.findViewById(R.id.mainUserId); - mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mSelectKeysButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - selectPublicKeys(); - } - }); mSign.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { CheckBox checkBox = (CheckBox) v; @@ -127,6 +122,7 @@ public class EncryptAsymmetricFragment extends Fragment { } } }); + mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); return view; } @@ -140,6 +136,40 @@ public class EncryptAsymmetricFragment extends Fragment { mProviderHelper = new ProviderHelper(getActivity()); + getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks() { + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new CursorLoader(getActivity(), KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + new String[]{KeyRings.HAS_ENCRYPT, KeyRings.KEY_ID, KeyRings.USER_ID, KeyRings.FINGERPRINT}, + null, null, null); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + mEncryptKeyView.fromCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + mEncryptKeyView.fromCursor(null); + } + }); + mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() { + @Override + public void onTokenAdded(Object token) { + if (token instanceof EncryptKeyCompletionView.EncryptionKey) { + updateEncryptionKeys(); + } + } + + @Override + public void onTokenRemoved(Object token) { + if (token instanceof EncryptKeyCompletionView.EncryptionKey) { + updateEncryptionKeys(); + } + } + }); + // preselect keys given by arguments (given by Intent to EncryptActivity) preselectKeys(signatureKeyId, encryptionKeyIds, mProviderHelper); } @@ -168,44 +198,31 @@ public class EncryptAsymmetricFragment extends Fragment { } if (preselectedEncryptionKeyIds != null) { - Vector goodIds = new Vector(); - Vector goodUserIds = new Vector(); for (long preselectedId : preselectedEncryptionKeyIds) { try { CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId)); - long id = ring.getMasterKeyId(); - ring.getSplitPrimaryUserId(); - goodUserIds.add(ring.getPrimaryUserId()); - goodIds.add(id); + mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring)); } catch (PgpGeneralException e) { Log.e(Constants.TAG, "key not found!", e); } } - if (goodIds.size() > 0) { - long[] keyIds = new long[goodIds.size()]; - for (int i = 0; i < goodIds.size(); ++i) { - keyIds[i] = goodIds.get(i); - } - setEncryptionKeyIds(keyIds); - setEncryptionUserIds(goodUserIds.toArray(new String[goodUserIds.size()])); - } + updateEncryptionKeys(); } } private void updateView() { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { + /*if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); } else { mSelectKeysButton.setText(getResources().getQuantityString( R.plurals.select_keys_button, mEncryptionKeyIds.length, mEncryptionKeyIds.length)); - } + }*/ + /* if (mSecretKeyId == Constants.key.none) { mSign.setChecked(false); - mMainUserId.setText(""); - mMainUserIdRest.setText(""); } else { // See if we can get a user_id from a unified query String[] userId; @@ -216,7 +233,7 @@ public class EncryptAsymmetricFragment extends Fragment { userId = null; } if (userId != null && userId[0] != null) { - mMainUserId.setText(String.format("%#16x", Long.parseLong(userId[0]))); + mMainUserId.setText(userId[0]); } else { mMainUserId.setText(getResources().getString(R.string.user_id_no_name)); } @@ -227,6 +244,26 @@ public class EncryptAsymmetricFragment extends Fragment { } mSign.setChecked(true); } + */ + } + + private void updateEncryptionKeys() { + List objects = mEncryptKeyView.getObjects(); + List keyIds = new ArrayList(); + List userIds = new ArrayList(); + for (Object object : objects) { + if (object instanceof EncryptKeyCompletionView.EncryptionKey) { + keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId()); + userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId()); + } + } + long[] keyIdsArr = new long[keyIds.size()]; + Iterator iterator = keyIds.iterator(); + for (int i = 0; i < keyIds.size(); i++) { + keyIdsArr[i] = iterator.next(); + } + setEncryptionKeyIds(keyIdsArr); + setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); } private void selectPublicKeys() { 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 3111e5c3e..350bc03aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -20,6 +20,8 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -29,15 +31,13 @@ import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.Spinner; -import android.widget.TextView; +import android.widget.*; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.helper.FileHelper; @@ -45,18 +45,18 @@ import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Log; import java.io.File; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; public class EncryptFileFragment extends Fragment { - public static final String ARG_URI = "uri"; - public static final String ARG_ASCII_ARMOR = "ascii_armor"; + public static final String ARG_URIS = "uris"; private static final int REQUEST_CODE_INPUT = 0x00007003; private static final int REQUEST_CODE_OUTPUT = 0x00007007; @@ -64,16 +64,14 @@ public class EncryptFileFragment extends Fragment { private EncryptActivityInterface mEncryptInterface; // view - private CheckBox mAsciiArmor = null; private Spinner mFileCompression = null; - private TextView mFilename = null; - private CheckBox mDeleteAfter = null; private View mShareFile; private View mEncryptFile; + private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter(); // model - private Uri mInputUri = null; - private Uri mOutputUri = null; + private ArrayList mInputUri = new ArrayList(); + private ArrayList mOutputUri = new ArrayList(); @Override public void onAttach(Activity activity) { @@ -107,17 +105,33 @@ public class EncryptFileFragment extends Fragment { } }); - mFilename = (TextView) view.findViewById(R.id.filename); - view.findViewById(R.id.btn_browse).setOnClickListener(new View.OnClickListener() { + //mFilename = (TextView) view.findViewById(R.id.filename); + //view.findViewById(R.id.btn_browse).setOnClickListener(new View.OnClickListener() { + // public void onClick(View v) { + // if (Constants.KITKAT) { + // FileHelper.openDocument(EncryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); + // } else { + // FileHelper.openFile(EncryptFileFragment.this, + // mInputUri.isEmpty() ? null : mInputUri.get(mInputUri.size() - 1), "*/*", REQUEST_CODE_INPUT); + // } + // } + //}); + + View addFile = inflater.inflate(R.layout.file_list_entry_add, null); + addFile.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { if (Constants.KITKAT) { FileHelper.openDocument(EncryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(EncryptFileFragment.this, mInputUri, "*/*", - REQUEST_CODE_INPUT); + FileHelper.openFile(EncryptFileFragment.this, + mInputUri.isEmpty() ? null : mInputUri.get(mInputUri.size() - 1), "*/*", REQUEST_CODE_INPUT); } } }); + ListView listView = (ListView) view.findViewById(R.id.selected_files_list); + listView.addFooterView(addFile); + listView.setAdapter(mAdapter); mFileCompression = (Spinner) view.findViewById(R.id.fileCompression); Choice[] choices = new Choice[]{ @@ -143,11 +157,6 @@ public class EncryptFileFragment extends Fragment { } } - mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption); - - mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor); - mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor()); - return view; } @@ -155,43 +164,57 @@ public class EncryptFileFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - setInputUri(getArguments().getParcelable(ARG_URI)); - boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR); - if (asciiArmor) { - mAsciiArmor.setChecked(true); + addInputUris(getArguments().getParcelableArrayList(ARG_URIS)); + } + + private void addInputUris(List uris) { + if (uris != null) { + for (Uri uri : uris) { + addInputUri(uri); + } } } - private void setInputUri(Uri inputUri) { + private void addInputUri(Uri inputUri) { if (inputUri == null) { - mInputUri = null; - mFilename.setText(""); return; } - mInputUri = inputUri; - mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); + mInputUri.add(inputUri); + mAdapter.notifyDataSetChanged(); + } + + private void delInputUri(int position) { + mInputUri.remove(position); + mAdapter.notifyDataSetChanged(); } private void showOutputFileDialog() { + if (mInputUri.size() > 1 || mInputUri.isEmpty()) { + throw new IllegalStateException(); + } + Uri inputUri = mInputUri.get(0); if (!Constants.KITKAT) { - File file = new File(mInputUri.getPath()); + File file = new File(inputUri.getPath()); File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - String targetName = FileHelper.getFilename( - getActivity(), mInputUri) + (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); + String targetName = FileHelper.getFilename(getActivity(), inputUri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"); File targetFile = new File(parentDir, targetName); FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT); } else { - FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), mInputUri) + - (mAsciiArmor.isChecked() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT); + FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), inputUri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT); } } private void encryptClicked(boolean share) { - if (mInputUri == null) { + if (mInputUri.isEmpty()) { AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); return; + } else if (mInputUri.size() > 1 && !share) { + AppMsg.makeText(getActivity(), "TODO", AppMsg.STYLE_ALERT).show(); // TODO + return; } if (mEncryptInterface.isModeSymmetric()) { @@ -244,17 +267,20 @@ public class EncryptFileFragment extends Fragment { } if (share) { - String targetName = FileHelper.getFilename(getActivity(), mInputUri) + - (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); - mOutputUri = TemporaryStorageProvider.createFile(getActivity(), targetName); + mOutputUri.clear(); + for (Uri uri : mInputUri) { + String targetName = FileHelper.getFilename(getActivity(), uri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"); + mOutputUri.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); + } encryptStart(true); - } else { + } else if (mInputUri.size() == 1) { showOutputFileDialog(); } } private void encryptStart(final boolean share) { - if (mInputUri == null || mOutputUri == null) { + if (mInputUri == null || mOutputUri == null || mInputUri.size() != mOutputUri.size()) { throw new IllegalStateException("Something went terribly wrong if this happens!"); } @@ -268,11 +294,11 @@ public class EncryptFileFragment extends Fragment { Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS); + data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUri); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS); + data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUri); if (mEncryptInterface.isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -288,8 +314,7 @@ public class EncryptFileFragment extends Fragment { mEncryptInterface.getEncryptionKeys()); } - boolean useAsciiArmor = mAsciiArmor.isChecked(); - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mEncryptInterface.isUseArmor()); int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); @@ -307,18 +332,25 @@ public class EncryptFileFragment extends Fragment { AppMsg.makeText(getActivity(), R.string.encrypt_sign_successful, AppMsg.STYLE_INFO).show(); - if (mDeleteAfter.isChecked()) { + if (mEncryptInterface.isDeleteAfterEncrypt()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + /*DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); - setInputUri(null); + setInputUri(null);*/ } if (share) { // Share encrypted file - Intent sendFileIntent = new Intent(Intent.ACTION_SEND); - sendFileIntent.setType("*/*"); - sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); + Intent sendFileIntent; + if (mOutputUri.size() == 1) { + sendFileIntent = new Intent(Intent.ACTION_SEND); + sendFileIntent.setType("*/*"); + sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri.get(0)); + } else { + sendFileIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + sendFileIntent.setType("*/*"); + sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); + } if (!mEncryptInterface.isModeSymmetric() && mEncryptInterface.getEncryptionUsers() != null) { Set users = new HashSet(); for (String user : mEncryptInterface.getEncryptionUsers()) { @@ -352,14 +384,14 @@ public class EncryptFileFragment extends Fragment { switch (requestCode) { case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { - setInputUri(data.getData()); + addInputUri(data.getData()); } return; } case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { - mOutputUri = data.getData(); + mOutputUri.add(data.getData()); encryptStart(false); } return; @@ -372,4 +404,52 @@ public class EncryptFileFragment extends Fragment { } } } + + private class SelectedFilesAdapter extends BaseAdapter { + @Override + public int getCount() { + return mInputUri.size(); + } + + @Override + public Object getItem(int position) { + return mInputUri.get(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).hashCode(); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null); + } else { + view = convertView; + } + ((TextView) view.findViewById(R.id.filename)).setText(FileHelper.getFilename(getActivity(), mInputUri.get(position))); + long size = FileHelper.getFileSize(getActivity(), mInputUri.get(position)); + if (size == -1) { + ((TextView) view.findViewById(R.id.filesize)).setText(""); + } else { + ((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size)); + } + view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + delInputUri(position); + } + }); + int px = OtherHelper.dpToPx(getActivity(), 48); + Bitmap bitmap = FileHelper.getThumbnail(getActivity(), mInputUri.get(position), new Point(px, px)); + if (bitmap != null) { + ((ImageView) view.findViewById(R.id.thumbnail)).setImageBitmap(bitmap); + } else { + ((ImageView) view.findViewById(R.id.thumbnail)).setImageResource(R.drawable.ic_doc_generic_am); + } + return view; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java new file mode 100644 index 000000000..4566e37fd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -0,0 +1,204 @@ +package org.sufficientlysecure.keychain.ui.widget; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.tokenautocomplete.FilteredArrayAdapter; +import com.tokenautocomplete.TokenCompleteTextView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class EncryptKeyCompletionView extends TokenCompleteTextView { + public EncryptKeyCompletionView(Context context) { + super(context); + initView(); + } + + public EncryptKeyCompletionView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public EncryptKeyCompletionView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initView(); + } + + private void initView() { + fromCursor(null); + setPrefix(getContext().getString(R.string.label_to) + ": "); + allowDuplicates(false); + } + + private EncryptKeyAdapter mAdapter; + + @Override + protected View getViewForObject(Object object) { + if (object instanceof EncryptionKey) { + LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + View view = l.inflate(R.layout.recipient_box_entry, null); + ((TextView) view.findViewById(android.R.id.text1)).setText(((EncryptionKey) object).getPrimary()); + setImageByKey((ImageView) view.findViewById(android.R.id.icon), (EncryptionKey) object); + return view; + } + return null; + } + + private void setImageByKey(ImageView view, EncryptionKey key) { + Bitmap photo = ContactHelper.photoFromFingerprint(getContext().getContentResolver(), key.getFingerprint()); + + if (photo != null) { + view.setImageBitmap(photo); + } else { + view.setImageResource(R.drawable.ic_generic_man); + } + } + + @Override + protected Object defaultObject(String completionText) { + // TODO: We could try to automagically download the key if it's unknown but a key id + /*if (completionText.startsWith("0x")) { + + }*/ + return null; + } + + public void fromCursor(Cursor cursor) { + if (cursor == null) { + setAdapter(new EncryptKeyAdapter(Collections.emptyList())); + return; + } + ArrayList keys = new ArrayList(); + cursor.moveToFirst(); + while (cursor.moveToNext()) { + try { + if (cursor.getInt(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT)) != 0) { + EncryptionKey key = new EncryptionKey(cursor); + keys.add(key); + } + } catch (Exception e) { + Log.w(Constants.TAG, e); + return; + } + } + setAdapter(new EncryptKeyAdapter(keys)); + } + + public class EncryptionKey { + private String mUserId; + private long mKeyId; + private String mFingerprint; + + public EncryptionKey(String userId, long keyId, String fingerprint) { + this.mUserId = userId; + this.mKeyId = keyId; + this.mFingerprint = fingerprint; + } + + public EncryptionKey(Cursor cursor) { + this(cursor.getString(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.USER_ID)), + cursor.getLong(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.KEY_ID)), + PgpKeyHelper.convertFingerprintToHex( + cursor.getBlob(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT)))); + + } + + public EncryptionKey(CachedPublicKeyRing ring) throws PgpGeneralException { + this(ring.getPrimaryUserId(), ring.extractOrGetMasterKeyId(), + PgpKeyHelper.convertFingerprintToHex(ring.getFingerprint())); + } + + public String getUserId() { + return mUserId; + } + + public String getFingerprint() { + return mFingerprint; + } + + public String getPrimary() { + String[] userId = KeyRing.splitUserId(mUserId); + if (userId[0] != null && userId[2] != null) { + return userId[0] + " (" + userId[2] + ")"; + } else if (userId[0] != null) { + return userId[0]; + } else { + return userId[1]; + } + } + + public String getSecondary() { + String[] userId = KeyRing.splitUserId(mUserId); + if (userId[0] != null) { + return userId[1] + " (" + getKeyIdHexShort() + ")"; + } else { + return getKeyIdHex(); + } + } + + public long getKeyId() { + return mKeyId; + } + + public String getKeyIdHex() { + return PgpKeyHelper.convertKeyIdToHex(mKeyId); + } + + public String getKeyIdHexShort() { + return PgpKeyHelper.convertKeyIdToHexShort(mKeyId); + } + + @Override + public String toString() { + return Long.toString(mKeyId); + } + } + + private class EncryptKeyAdapter extends FilteredArrayAdapter { + + public EncryptKeyAdapter(List objs) { + super(EncryptKeyCompletionView.this.getContext(), 0, 0, objs); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + View view; + if (convertView != null) { + view = convertView; + } else { + view = l.inflate(R.layout.recipient_selection_list_entry, null); + } + ((TextView) view.findViewById(android.R.id.title)).setText(getItem(position).getPrimary()); + ((TextView) view.findViewById(android.R.id.text1)).setText(getItem(position).getSecondary()); + setImageByKey((ImageView) view.findViewById(android.R.id.icon), getItem(position)); + return view; + } + + @Override + protected boolean keepObject(EncryptionKey obj, String mask) { + String m = mask.toLowerCase(); + return obj.getUserId().toLowerCase().contains(m) || + obj.getKeyIdHex().contains(m) || + obj.getKeyIdHexShort().startsWith(m); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java new file mode 100644 index 000000000..08f071fb2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java @@ -0,0 +1,44 @@ +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class NoSwipeWrapContentViewPager extends android.support.v4.view.ViewPager { + public NoSwipeWrapContentViewPager(Context context) { + super(context); + } + + public NoSwipeWrapContentViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int height = 0; + for(int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int h = child.getMeasuredHeight(); + if(h > height) height = h; + } + + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + // Never allow swiping to switch between pages + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Never allow swiping to switch between pages + return false; + } +} diff --git a/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..3cbdd667b Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..55b9b7d3c Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png new file mode 100644 index 000000000..b6b3129f5 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..bb9214b6a Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..a1bd14eaf Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png new file mode 100644 index 000000000..f763dd259 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..1f4b25d21 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..e05c4b48d Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png new file mode 100644 index 000000000..212293db0 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..13855a2b1 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..c09886632 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png new file mode 100644 index 000000000..130c670c9 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/layout/encrypt_activity.xml b/OpenKeychain/src/main/res/layout/encrypt_activity.xml index 65c2ee8fd..839bddc75 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_activity.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_activity.xml @@ -1,10 +1,11 @@ - + diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index cde92b477..284fdd9ce 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -9,69 +9,15 @@ android:paddingRight="16dp" android:paddingLeft="16dp"> - - - + android:text="@string/label_sign"/> - - - - - - - - - - - - -