Merge branch 'master' of github.com:open-keychain/open-keychain

This commit is contained in:
Dominik Schürmann
2015-08-28 04:20:50 +02:00
10 changed files with 178 additions and 141 deletions

View File

@@ -18,7 +18,11 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -47,7 +51,20 @@ public class BackupFragment extends Fragment {
private int mIndex; private int mIndex;
static final int REQUEST_REPEAT_PASSPHRASE = 1; static final int REQUEST_REPEAT_PASSPHRASE = 1;
private ExportHelper mExportHelper;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// we won't get attached to a non-fragment activity, so the cast should be safe
mExportHelper = new ExportHelper((FragmentActivity) activity);
}
@Override
public void onDetach() {
super.onDetach();
mExportHelper = null;
}
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -80,8 +97,7 @@ public class BackupFragment extends Fragment {
} }
if (!includeSecretKeys) { if (!includeSecretKeys) {
ExportHelper exportHelper = new ExportHelper(activity); startBackup(false);
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false);
return; return;
} }
@@ -136,8 +152,7 @@ public class BackupFragment extends Fragment {
return; return;
} }
ExportHelper exportHelper = new ExportHelper(activity); startBackup(true);
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
} }
}.execute(activity.getContentResolver()); }.execute(activity.getContentResolver());
@@ -167,8 +182,19 @@ public class BackupFragment extends Fragment {
return; return;
} }
ExportHelper exportHelper = new ExportHelper(getActivity()); startBackup(true);
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
} }
} }
private void startBackup(boolean exportSecret) {
File filename;
String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
if (exportSecret) {
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
} else {
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
}
mExportHelper.showExportKeysDialog(null, filename, exportSecret);
}
} }

View File

@@ -36,7 +36,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DefaultItemAnimator;
@@ -222,18 +221,6 @@ public class DecryptListFragment
} }
} }
private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
File file = new File(inputUri.getPath());
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
File targetFile = new File(parentDir, originalFilename);
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, mimeType, originalFilename, REQUEST_CODE_OUTPUT);
}
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) { switch (requestCode) {
@@ -534,7 +521,8 @@ public class DecryptListFragment
return true; return true;
} }
mCurrentInputUri = model.mInputUri; mCurrentInputUri = model.mInputUri;
askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(),
R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
return true; return true;
case R.id.decrypt_delete: case R.id.decrypt_delete:
deleteFile(activity, model.mInputUri); deleteFile(activity, model.mInputUri);

View File

@@ -83,11 +83,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
mDecryptFile.setOnClickListener(new View.OnClickListener() { mDecryptFile.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
FileHelper.openDocument(EncryptDecryptOverviewFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(EncryptDecryptOverviewFragment.this, null, "*/*", REQUEST_CODE_INPUT);
}
} }
}); });

View File

@@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@@ -196,13 +195,9 @@ public class EncryptFilesFragment
} }
private void addInputUri() { private void addInputUri() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri, null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
"*/*", REQUEST_CODE_INPUT); "*/*", true, REQUEST_CODE_INPUT);
}
} }
private void addInputUri(Uri inputUri) { private void addInputUri(Uri inputUri) {
@@ -230,19 +225,8 @@ public class EncryptFilesFragment
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
Uri inputUri = model.inputUri; Uri inputUri = model.inputUri;
saveDocumentIntent(targetName, inputUri); FileHelper.saveDocument(this, targetName, inputUri,
} R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT);
private void saveDocumentIntent(String targetName, Uri inputUri) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
File file = new File(inputUri.getPath());
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
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, "*/*", targetName, REQUEST_CODE_OUTPUT);
}
} }
public void addFile(Intent data) { public void addFile(Intent data) {

View File

@@ -64,8 +64,8 @@ public class ImportKeysFileFragment extends Fragment {
// open .asc or .gpg files // open .asc or .gpg files
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
// or gpg types! // or gpg types!
FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), FileHelper.openDocument(ImportKeysFileFragment.this,
"*/*", REQUEST_CODE_FILE); Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
} }
}); });

View File

@@ -252,7 +252,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
String message = this.getString(R.string.specify_file_to_export_log_to); String message = this.getString(R.string.specify_file_to_export_log_to);
FileHelper.saveFile(new FileHelper.FileDialogCallback() { FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() {
@Override @Override
public void onFileSelected(File file, boolean checked) { public void onFileSelected(File file, boolean checked) {
writeToLogFile(mResult.getLog(), file); writeToLogFile(mResult.getLog(), file);

View File

@@ -82,6 +82,7 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.NfcHelper;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -423,7 +424,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
private void backupToFile() { private void backupToFile() {
new ExportHelper(this).showExportKeysDialog( new ExportHelper(this).showExportKeysDialog(
mMasterKeyId, Constants.Path.APP_DIR_FILE, true); mMasterKeyId, new File(Constants.Path.APP_DIR,
KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true);
} }
private void deleteKey() { private void deleteKey() {

View File

@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
@@ -119,14 +120,19 @@ public class FileDialogFragment extends DialogFragment {
mFilename = (EditText) view.findViewById(R.id.input); mFilename = (EditText) view.findViewById(R.id.input);
mFilename.setText(mFile.getName()); mFilename.setText(mFile.getName());
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
mBrowse.setVisibility(View.GONE);
} else {
mBrowse.setOnClickListener(new View.OnClickListener() { mBrowse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
// only .asc or .gpg files // only .asc or .gpg files
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
// or gpg types! // or gpg types!
FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE); FileHelper.saveDocumentKitKat(
FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE);
} }
}); });
}
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox); mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
if (checkboxText == null) { if (checkboxText == null) {

View File

@@ -69,7 +69,7 @@ public class ExportHelper
: R.string.specify_backup_dest_single); : R.string.specify_backup_dest_single);
} }
FileHelper.saveFile(new FileHelper.FileDialogCallback() { FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() {
@Override @Override
public void onFileSelected(File file, boolean checked) { public void onFileSelected(File file, boolean checked) {
mExportFile = file; mExportFile = file;

View File

@@ -28,16 +28,19 @@ import android.graphics.Bitmap;
import android.graphics.Point; import android.graphics.Point;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.widget.Toast; import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
@@ -46,42 +49,95 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
/** This class offers a number of helper functions for saving documents.
*
* There are three entry points here: openDocument, saveDocument and
* saveDocumentDialog. Each behaves a little differently depending on whether
* the Android version used is pre or post KitKat.
*
* - openDocument queries for a document for reading. Used in "open encrypted
* file" ui flow. On pre-kitkat, this relies on an external file manager,
* and will fail with a toast message if none is installed.
*
* - saveDocument queries for a document name for saving. on pre-kitkat, this
* shows a dialog where a filename can be input. on kitkat and up, it
* directly triggers a "save document" intent. Used in "save encrypted file"
* ui flow.
*
* - saveDocumentDialog queries for a document. this shows a dialog on all
* versions of android. the browse button opens an external browser on
* pre-kitkat or the "save document" intent on post-kitkat devices. Used in
* "backup key" ui flow.
*
* It is noteworthy that the "saveDocument" call is essentially substituted
* by the "saveDocumentDialog" on pre-kitkat devices.
*
*/
public class FileHelper { public class FileHelper {
/** public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
* Checks if external storage is mounted if file is located on external storage if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
* openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode);
* @return true if storage is mounted } else {
*/ openDocumentKitKat(fragment, mimeType, multiple, requestCode);
public static boolean isStorageMounted(String file) {
if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
} }
} }
return true; public static void saveDocument(Fragment fragment, String targetName, Uri inputUri,
@StringRes int title, @StringRes int message, int requestCode) {
saveDocument(fragment, targetName, inputUri, "*/*", title, message, requestCode);
} }
/** public static void saveDocument(Fragment fragment, String targetName, Uri inputUri, String mimeType,
* Opens the preferred installed file manager on Android and shows a toast if no manager is @StringRes int title, @StringRes int message, int requestCode) {
* installed. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
* saveDocumentDialog(fragment, targetName, inputUri, title, message, requestCode);
* @param last default selected Uri, not supported by all file managers } else {
* @param mimeType can be text/plain for example saveDocumentKitKat(fragment, mimeType, targetName, requestCode);
* @param requestCode requestCode used to identify the result coming back from file manager to }
* onActivityResult() in your activity }
*/
public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) { public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
@StringRes int title, @StringRes int message, final int requestCode) {
saveDocumentDialog(fragment, targetName, inputUri, title, message, new FileDialogCallback() {
// is this a good idea? seems hacky...
@Override
public void onFileSelected(File file, boolean checked) {
Intent intent = new Intent();
intent.setData(Uri.fromFile(file));
fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent);
}
});
}
public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
@StringRes int title, @StringRes int message, FileDialogCallback callback) {
File file = inputUri == null ? null : new File(inputUri.getPath());
File parentDir = file != null && file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
File targetFile = new File(parentDir, targetName);
saveDocumentDialog(callback, fragment.getActivity().getSupportFragmentManager(),
fragment.getString(title), fragment.getString(message), targetFile, null);
}
/** Opens the preferred installed file manager on Android and shows a toast
* if no manager is installed. */
private static void openDocumentPreKitKat(
Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
}
intent.setData(last); intent.setData(last);
intent.setType(mimeType); intent.setType(mimeType);
@@ -92,9 +148,32 @@ public class FileHelper {
Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed, Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
} }
public static void saveFile(final FileDialogCallback callback, final FragmentManager fragmentManager, /** Opens the storage browser on Android 4.4 or later for opening a file */
@TargetApi(Build.VERSION_CODES.KITKAT)
private static void openDocumentKitKat(Fragment fragment, String mimeType, boolean multiple, int requestCode) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
fragment.startActivityForResult(intent, requestCode);
}
/** Opens the storage browser on Android 4.4 or later for saving a file. */
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void saveDocumentKitKat(Fragment fragment, String mimeType, String suggestedName, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
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);
}
public static void saveDocumentDialog(
final FileDialogCallback callback, final FragmentManager fragmentManager,
final String title, final String message, final File defaultFile, final String title, final String message, final File defaultFile,
final String checkMsg) { final String checkMsg) {
// Message is received after file is selected // Message is received after file is selected
@@ -123,61 +202,6 @@ public class FileHelper {
}); });
} }
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);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void openDocument(Fragment fragment, String mimeType, int requestCode) {
openDocument(fragment, mimeType, false, requestCode);
}
/**
* Opens the storage browser on Android 4.4 or later for opening a file
*
* @param mimeType can be text/plain for example
* @param multiple allow file chooser to return multiple files
* @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, String mimeType, boolean multiple, int requestCode) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
fragment.startActivityForResult(intent, requestCode);
}
/**
* Opens the storage browser on Android 4.4 or later for saving a 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, String mimeType, String suggestedName, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
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);
}
public static String getFilename(Context context, Uri uri) { public static String getFilename(Context context, Uri uri) {
String filename = null; String filename = null;
try { try {
@@ -298,6 +322,17 @@ public class FileHelper {
} }
} }
/** Checks if external storage is mounted if file is located on external storage. */
public static boolean isStorageMounted(String file) {
if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
}
}
return true;
}
public interface FileDialogCallback { public interface FileDialogCallback {
void onFileSelected(File file, boolean checked); void onFileSelected(File file, boolean checked);
} }