Merge pull request #728 from mar-v-in/improve-file-more
Improve file encrypt
3
.gitmodules
vendored
@ -28,3 +28,6 @@
|
||||
[submodule "extern/minidns"]
|
||||
path = extern/minidns
|
||||
url = https://github.com/open-keychain/minidns.git
|
||||
[submodule "extern/TokenAutoComplete"]
|
||||
path = extern/TokenAutoComplete
|
||||
url = https://github.com/open-keychain/TokenAutoComplete
|
||||
|
@ -18,6 +18,7 @@ dependencies {
|
||||
compile project(':extern:SuperToasts:supertoasts')
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
compile project(':extern:TokenAutoComplete:library')
|
||||
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,9 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
||||
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
@ -152,12 +155,13 @@
|
||||
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<!-- TODO: accept other schemes! -->
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content"/>
|
||||
</intent-filter>
|
||||
<!-- Android's Send Action -->
|
||||
<intent-filter android:label="@string/intent_send_encrypt">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@ -202,8 +206,8 @@
|
||||
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<!-- TODO: accept other schemes! -->
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content"/>
|
||||
</intent-filter>
|
||||
<!-- Android's Send Action -->
|
||||
<intent-filter android:label="@string/intent_send_decrypt">
|
||||
@ -223,7 +227,7 @@
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
@ -644,6 +648,12 @@
|
||||
android:resource="@xml/custom_pgp_contacts_structure" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".provider.TemporaryStorageProvider"
|
||||
android:authorities="org.sufficientlysecure.keychain.tempstorage"
|
||||
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"
|
||||
android:exported="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -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;
|
||||
@ -51,10 +53,11 @@ 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 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 {
|
||||
|
@ -28,10 +28,10 @@ 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
@ -73,8 +73,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
|
||||
}
|
||||
@ -89,6 +88,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) {
|
||||
|
@ -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 {
|
||||
@ -60,6 +63,8 @@ public class ContactHelper {
|
||||
ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?";
|
||||
public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?";
|
||||
|
||||
private static final Map<String, Bitmap> photoCache = new HashMap<String, Bitmap>();
|
||||
|
||||
public static List<String> getPossibleUserEmails(Context context) {
|
||||
Set<String> accountMails = getAccountEmails(context);
|
||||
accountMails.addAll(getMainProfileContactEmails(context));
|
||||
@ -232,6 +237,30 @@ public class ContactHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Bitmap photoFromFingerprint(ContentResolver contentResolver, String fingerprint) {
|
||||
if (fingerprint == null) return null;
|
||||
if (!photoCache.containsKey(fingerprint)) {
|
||||
photoCache.put(fingerprint, loadPhotoFromFingerprint(contentResolver, fingerprint));
|
||||
}
|
||||
return photoCache.get(fingerprint);
|
||||
}
|
||||
|
||||
private static Bitmap loadPhotoFromFingerprint(ContentResolver contentResolver, String fingerprint) {
|
||||
if (fingerprint == null) return null;
|
||||
try {
|
||||
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);
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current Keychain to the contact db
|
||||
*/
|
||||
|
@ -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;
|
||||
mExportFile = exportFile;
|
||||
|
||||
// Message is received after file is selected
|
||||
Handler returnHandler = new Handler() {
|
||||
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;
|
||||
|
||||
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
|
||||
@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));
|
||||
}
|
||||
public void onFileSelected(File file, boolean checked) {
|
||||
mExportFile = file;
|
||||
exportKeys(masterKeyIds, checked);
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
|
||||
exportFilename, checkMsg);
|
||||
|
||||
mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
});
|
||||
}, 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) {
|
||||
|
@ -23,15 +23,22 @@ 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.*;
|
||||
import android.provider.DocumentsContract;
|
||||
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;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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, String filename, String mimeType, int requestCode) {
|
||||
Intent intent = buildFileIntent(filename, mimeType);
|
||||
intent.setData(last);
|
||||
intent.setType(mimeType);
|
||||
|
||||
try {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
@ -84,86 +84,147 @@ 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);
|
||||
}
|
||||
|
||||
@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 fragment
|
||||
* @param last default selected 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, Uri last, String mimeType, int requestCode) {
|
||||
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.setData(last);
|
||||
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 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);
|
||||
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);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType(mimeType);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToNext()) {
|
||||
filename = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} 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 intent;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path from a Uri.
|
||||
* <p/>
|
||||
* from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/
|
||||
* afilechooser/utils/FileUtils.java
|
||||
*
|
||||
* @param context
|
||||
* @param uri
|
||||
* @return
|
||||
* @author paulburke
|
||||
* Retrieve thumbnail of file, document api feature and thus KitKat only
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
public static Bitmap getThumbnail(Context context, Uri uri, Point size) {
|
||||
if (Constants.KITKAT) {
|
||||
return DocumentsContract.getDocumentThumbnail(context.getContentResolver(), uri, size, null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
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) {
|
||||
// Note: If we can find a files mime type, we can decrypt it to temp storage and open it after
|
||||
// encryption. The mime type is needed, else UI really sucks and some apps break.
|
||||
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);
|
||||
}
|
||||
}
|
@ -112,6 +112,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";
|
||||
@ -121,8 +124,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
|
||||
@ -142,6 +147,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";
|
||||
@ -226,6 +232,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);
|
||||
@ -233,45 +240,49 @@ 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);
|
||||
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);
|
||||
/* 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));
|
||||
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();
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
finalizeEncryptOutputStream(data, resultData, outStream);
|
||||
|
||||
// this assumes that the bytes are cleartext (valid for current implementation!)
|
||||
if (source == IO_BYTES) {
|
||||
builder.setCleartextInput(true);
|
||||
}
|
||||
|
||||
builder.build().execute();
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
finalizeEncryptOutputStream(data, resultData, outStream);
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
@ -416,13 +427,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<Long> publicMasterKeyIds = new ArrayList<Long>();
|
||||
@ -454,12 +468,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) {
|
||||
new File(outputFile).delete();
|
||||
}
|
||||
|
||||
@ -709,8 +730,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.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||
|
||||
// InputStream
|
||||
return new InputData(getContentResolver().openInputStream(providerUri), 0);
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
@ -740,6 +766,11 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
return getContentResolver().openOutputStream(providerUri);
|
||||
|
||||
case IO_URIS:
|
||||
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||
|
||||
return getContentResolver().openOutputStream(providerUri);
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
}
|
||||
@ -765,6 +796,7 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
break;
|
||||
case IO_URI:
|
||||
case IO_URIS:
|
||||
// nothing, output was written, just send okay and verification bundle
|
||||
|
||||
break;
|
||||
|
@ -23,11 +23,8 @@ 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 +111,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)) {
|
||||
@ -122,6 +119,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
// override action
|
||||
action = ACTION_DECRYPT;
|
||||
mFileFragmentBundle.putBoolean(DecryptFileFragment.ARG_FROM_VIEW_INTENT, true);
|
||||
}
|
||||
|
||||
String textData = extras.getString(EXTRA_TEXT);
|
||||
@ -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 if (ACTION_DECRYPT.equals(action)) {
|
||||
Log.e(Constants.TAG,
|
||||
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
||||
|
@ -20,13 +20,10 @@ 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;
|
||||
@ -34,37 +31,35 @@ import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
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 org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DecryptFileFragment extends DecryptFragment {
|
||||
public static final String ARG_FILENAME = "filename";
|
||||
public static final String ARG_URI = "uri";
|
||||
public static final String ARG_FROM_VIEW_INTENT = "view_intent";
|
||||
|
||||
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 ImageButton 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
|
||||
*/
|
||||
@ -72,17 +67,16 @@ 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);
|
||||
mBrowse = (ImageButton) view.findViewById(R.id.decrypt_file_browse);
|
||||
mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename);
|
||||
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, 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -100,74 +94,47 @@ 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().<Uri>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);
|
||||
}
|
||||
return Constants.Path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void decryptAction() {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
private void setInputUri(Uri inputUri) {
|
||||
if (inputUri == null) {
|
||||
mInputUri = null;
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
}
|
||||
|
||||
if (mInputUri == null) {
|
||||
mOutputFilename = guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
mFilename.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputUri == null && mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Notify.showNotify(getActivity(), getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
mInputUri = inputUri;
|
||||
mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri));
|
||||
}
|
||||
|
||||
private void decryptAction() {
|
||||
if (mInputUri == null) {
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
askForOutputFilename();
|
||||
}
|
||||
|
||||
private String removeEncryptedAppend(String name) {
|
||||
if (name.endsWith(".asc") || name.endsWith(".gpg") || name.endsWith(".pgp")) {
|
||||
return name.substring(0, name.length() - 4);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog");
|
||||
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
|
||||
@ -183,25 +150,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);
|
||||
|
||||
@ -232,15 +187,19 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,28 +219,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;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
|
||||
public class DecryptFragment extends Fragment {
|
||||
public abstract class DecryptFragment extends Fragment {
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
|
||||
protected long mSignatureKeyId = 0;
|
||||
@ -217,8 +217,6 @@ public class DecryptFragment extends Fragment {
|
||||
*
|
||||
* @param passphrase
|
||||
*/
|
||||
protected void decryptStart(String passphrase) {
|
||||
|
||||
}
|
||||
protected abstract void decryptStart(String passphrase);
|
||||
|
||||
}
|
||||
|
@ -18,23 +18,37 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
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.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class EncryptActivity extends DrawerActivity implements
|
||||
EncryptSymmetricFragment.OnSymmetricKeySelection,
|
||||
EncryptAsymmetricFragment.OnAsymmetricKeySelection,
|
||||
EncryptActivityInterface {
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptActivity extends DrawerActivity implements EncryptActivityInterface {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT";
|
||||
@ -51,7 +65,7 @@ public class EncryptActivity extends DrawerActivity implements
|
||||
|
||||
// view
|
||||
ViewPager mViewPagerMode;
|
||||
PagerTabStrip mPagerTabStripMode;
|
||||
//PagerTabStrip mPagerTabStripMode;
|
||||
PagerTabStripAdapter mTabsAdapterMode;
|
||||
ViewPager mViewPagerContent;
|
||||
PagerTabStrip mPagerTabStripContent;
|
||||
@ -72,37 +86,27 @@ 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;
|
||||
private String mPassphrase = "";
|
||||
private boolean mUseArmor;
|
||||
private boolean mDeleteAfterEncrypt = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private ArrayList<Uri> mInputUris;
|
||||
private ArrayList<Uri> mOutputUris;
|
||||
private String mMessage = "";
|
||||
|
||||
@Override
|
||||
public void onSigningKeySelected(long signingKeyId) {
|
||||
mSigningKeyId = signingKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionKeysSelected(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseUpdate(String passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseAgainUpdate(String passphrase) {
|
||||
mPassphraseAgain = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModeSymmetric() {
|
||||
if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem();
|
||||
}
|
||||
|
||||
public boolean isContentMessage() {
|
||||
return PAGER_CONTENT_MESSAGE == mViewPagerContent.getCurrentItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseArmor() {
|
||||
return mUseArmor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,19 +120,277 @@ public class EncryptActivity extends DrawerActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassphrase() {
|
||||
return mPassphrase;
|
||||
public String[] getEncryptionUsers() {
|
||||
return mEncryptionUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassphraseAgain() {
|
||||
return mPassphraseAgain;
|
||||
public void setSignatureKey(long signatureKey) {
|
||||
mSigningKeyId = signatureKey;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionKeys(long[] encryptionKeys) {
|
||||
mEncryptionKeyIds = encryptionKeys;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionUsers(String[] encryptionUsers) {
|
||||
mEncryptionUserIds = encryptionUsers;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassphrase(String passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<Uri>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<Uri>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputUris(ArrayList<Uri> uris) {
|
||||
mInputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputUris(ArrayList<Uri> uris) {
|
||||
mOutputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (fragment instanceof EncryptActivityInterface.UpdateListener) {
|
||||
((UpdateListener) fragment).onNotifyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
startEncrypt();
|
||||
}
|
||||
|
||||
public void startEncrypt() {
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, createEncryptBundle());
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Notify.showNotify(EncryptActivity.this, R.string.encrypt_sign_successful, Notify.Style.INFO);
|
||||
|
||||
if (!isContentMessage() && mDeleteAfterEncrypt) {
|
||||
for (Uri inputUri : mInputUris) {
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
mInputUris.clear();
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted file
|
||||
startActivity(Intent.createChooser(createSendIntent(message), getString(R.string.title_share_file)));
|
||||
} else if (isContentMessage()) {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(message);
|
||||
Notify.showNotify(EncryptActivity.this,
|
||||
R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private Bundle createEncryptBundle() {
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
if (isContentMessage()) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES);
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, mMessage.getBytes());
|
||||
} else {
|
||||
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS);
|
||||
data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUris);
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS);
|
||||
data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUris);
|
||||
}
|
||||
|
||||
// Always use armor for messages
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mUseArmor || isContentMessage());
|
||||
|
||||
// TODO: Only default compression right now...
|
||||
int compressionId = Preferences.getPreferences(this).getDefaultMessageCompression();
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
String passphrase = mPassphrase;
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, mSigningKeyId);
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(Message message) {
|
||||
ClipboardReflection.copyToClipboard(this, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES)));
|
||||
}
|
||||
|
||||
private Intent createSendIntent(Message message) {
|
||||
Intent sendIntent;
|
||||
if (isContentMessage()) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES)));
|
||||
} else {
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
|
||||
} else {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType("application/pgp-encrypted");
|
||||
}
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<String>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
String[] userId = KeyRing.splitUserId(user);
|
||||
if (userId[1] != null) {
|
||||
users.add(userId[1]);
|
||||
}
|
||||
}
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
private boolean inputIsValid() {
|
||||
if (isContentMessage()) {
|
||||
if (mMessage == null) {
|
||||
Notify.showNotify(this, R.string.error_message, Notify.Style.ERROR);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// file checks
|
||||
|
||||
if (mInputUris.isEmpty()) {
|
||||
Notify.showNotify(this, R.string.no_file_selected, Notify.Style.ERROR);
|
||||
return false;
|
||||
} else if (mInputUris.size() > 1 && !mShareAfterEncrypt) {
|
||||
// This should be impossible...
|
||||
return false;
|
||||
} else if (mInputUris.size() != mOutputUris.size()) {
|
||||
// This as well
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
// symmetric encryption checks
|
||||
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.showNotify(this, R.string.passphrases_do_not_match, Notify.Style.ERROR);
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.showNotify(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
// Files must be encrypted, only text can be signed-only right now
|
||||
if (!gotEncryptionKeys && !isContentMessage()) {
|
||||
Notify.showNotify(this, R.string.select_encryption_key, Notify.Style.ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gotEncryptionKeys && mSigningKeyId == 0) {
|
||||
Notify.showNotify(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSigningKeyId != 0 && PassphraseCacheService.getCachedPassphrase(this, mSigningKeyId) == null) {
|
||||
PassphraseDialogFragment.show(this, mSigningKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
// restart
|
||||
startEncrypt();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -165,8 +427,43 @@ public class EncryptActivity extends DrawerActivity implements
|
||||
mTabsAdapterContent.addTab(EncryptMessageFragment.class,
|
||||
mMessageFragmentBundle, getString(R.string.label_message));
|
||||
mTabsAdapterContent.addTab(EncryptFileFragment.class,
|
||||
mFileFragmentBundle, getString(R.string.label_file));
|
||||
mFileFragmentBundle, getString(R.string.label_files));
|
||||
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);
|
||||
notifyUpdate();
|
||||
break;
|
||||
case R.id.check_use_armor:
|
||||
mUseArmor = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
case R.id.check_delete_after_encrypt:
|
||||
mDeleteAfterEncrypt = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,12 +475,16 @@ public class EncryptActivity extends DrawerActivity implements
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
Uri uri = intent.getData();
|
||||
ArrayList<Uri> uris = new ArrayList<Uri>();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
if (intent.getData() != null) {
|
||||
uris.add(intent.getData());
|
||||
}
|
||||
|
||||
/*
|
||||
* Android's Action
|
||||
*/
|
||||
@ -201,14 +502,19 @@ public class EncryptActivity extends DrawerActivity implements
|
||||
}
|
||||
} else {
|
||||
// Files via content provider, override uri and action
|
||||
uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
uris.clear();
|
||||
uris.add(intent.<Uri>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);
|
||||
@ -230,25 +536,10 @@ 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
|
||||
|
||||
// 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.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris);
|
||||
mSwitchToContent = PAGER_CONTENT_FILE;
|
||||
} else if (ACTION_ENCRYPT.equals(action)) {
|
||||
Log.e(Constants.TAG,
|
||||
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
||||
|
@ -17,14 +17,41 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface EncryptActivityInterface {
|
||||
|
||||
public boolean isModeSymmetric();
|
||||
public interface UpdateListener {
|
||||
void onNotifyUpdate();
|
||||
}
|
||||
|
||||
public boolean isUseArmor();
|
||||
|
||||
public long getSignatureKey();
|
||||
public long[] getEncryptionKeys();
|
||||
public String[] getEncryptionUsers();
|
||||
public void setSignatureKey(long signatureKey);
|
||||
public void setEncryptionKeys(long[] encryptionKeys);
|
||||
public void setEncryptionUsers(String[] encryptionUsers);
|
||||
|
||||
public String getPassphrase();
|
||||
public String getPassphraseAgain();
|
||||
public void setPassphrase(String passphrase);
|
||||
|
||||
// ArrayList on purpose as only those are parcelable
|
||||
public ArrayList<Uri> getInputUris();
|
||||
public ArrayList<Uri> getOutputUris();
|
||||
public void setInputUris(ArrayList<Uri> uris);
|
||||
public void setOutputUris(ArrayList<Uri> uris);
|
||||
|
||||
public String getMessage();
|
||||
public void setMessage(String message);
|
||||
|
||||
/**
|
||||
* Call this to notify the UI for changes done on the array lists or arrays,
|
||||
* automatically called if setter is used
|
||||
*/
|
||||
public void notifyUpdate();
|
||||
|
||||
public void startEncrypt(boolean share);
|
||||
}
|
||||
|
@ -18,16 +18,25 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
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.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.tokenautocomplete.TokenCompleteTextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -37,61 +46,52 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
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 org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class EncryptAsymmetricFragment extends Fragment {
|
||||
public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id";
|
||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
public static final int REQUEST_CODE_PUBLIC_KEYS = 0x00007001;
|
||||
public static final int REQUEST_CODE_SECRET_KEYS = 0x00007002;
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
OnAsymmetricKeySelection mKeySelectionListener;
|
||||
|
||||
// view
|
||||
private Button mSelectKeysButton;
|
||||
private CheckBox mSign;
|
||||
private TextView mMainUserId;
|
||||
private TextView mMainUserIdRest;
|
||||
private Spinner mSign;
|
||||
private EncryptKeyCompletionView mEncryptKeyView;
|
||||
private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter();
|
||||
|
||||
// model
|
||||
private long mSecretKeyId = Constants.key.none;
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnAsymmetricKeySelection {
|
||||
public void onSigningKeySelected(long signingKeyId);
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
|
||||
public void onEncryptionKeysSelected(long[] encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mKeySelectionListener = (OnAsymmetricKeySelection) activity;
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection");
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
}
|
||||
}
|
||||
|
||||
private void setSignatureKeyId(long signatureKeyId) {
|
||||
mSecretKeyId = signatureKeyId;
|
||||
// update key selection in EncryptActivity
|
||||
mKeySelectionListener.onSigningKeySelected(signatureKeyId);
|
||||
updateView();
|
||||
mEncryptInterface.setSignatureKey(signatureKeyId);
|
||||
}
|
||||
|
||||
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
// update key selection in EncryptActivity
|
||||
mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds);
|
||||
updateView();
|
||||
mEncryptInterface.setEncryptionKeys(encryptionKeyIds);
|
||||
}
|
||||
|
||||
private void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptInterface.setEncryptionUsers(encryptionUserIds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,25 +101,20 @@ 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;
|
||||
if (checkBox.isChecked()) {
|
||||
selectSecretKey();
|
||||
} else {
|
||||
setSignatureKeyId(Constants.key.none);
|
||||
}
|
||||
mSign = (Spinner) view.findViewById(R.id.sign);
|
||||
mSign.setAdapter(mSignAdapter);
|
||||
mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
setSignatureKeyId(parent.getAdapter().getItemId(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
setSignatureKeyId(Constants.key.none);
|
||||
}
|
||||
});
|
||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||
|
||||
return view;
|
||||
}
|
||||
@ -135,6 +130,65 @@ public class EncryptAsymmetricFragment extends Fragment {
|
||||
|
||||
// preselect keys given by arguments (given by Intent to EncryptActivity)
|
||||
preselectKeys(signatureKeyId, encryptionKeyIds, mProviderHelper);
|
||||
|
||||
getLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.KEY_ID,
|
||||
KeyRings.USER_ID,
|
||||
KeyRings.EXPIRY,
|
||||
KeyRings.IS_REVOKED,
|
||||
// can certify info only related to master key
|
||||
KeyRings.CAN_CERTIFY,
|
||||
// has sign may be any subkey
|
||||
KeyRings.HAS_SIGN,
|
||||
KeyRings.HAS_ANY_SECRET,
|
||||
KeyRings.HAS_SECRET
|
||||
};
|
||||
|
||||
String where = KeyRings.HAS_ANY_SECRET + " = 1";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, where, null, null);
|
||||
/*return new CursorLoader(getActivity(), KeyRings.buildUnifiedKeyRingsUri(),
|
||||
new String[]{KeyRings.USER_ID, KeyRings.KEY_ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET}, SIGN_KEY_SELECTION,
|
||||
null, null);*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mSignAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
mSignAdapter.swapCursor(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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,117 +215,125 @@ public class EncryptAsymmetricFragment extends Fragment {
|
||||
}
|
||||
|
||||
if (preselectedEncryptionKeyIds != null) {
|
||||
Vector<Long> goodIds = new Vector<Long>();
|
||||
for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
|
||||
for (long preselectedId : preselectedEncryptionKeyIds) {
|
||||
try {
|
||||
long id = providerHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(
|
||||
preselectedEncryptionKeyIds[i])
|
||||
).getMasterKeyId();
|
||||
goodIds.add(id);
|
||||
CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId));
|
||||
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);
|
||||
}
|
||||
updateEncryptionKeys();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
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));
|
||||
private void updateEncryptionKeys() {
|
||||
List<Object> objects = mEncryptKeyView.getObjects();
|
||||
List<Long> keyIds = new ArrayList<Long>();
|
||||
List<String> userIds = new ArrayList<String>();
|
||||
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<Long> 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 class SelectSignKeyCursorAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||
private CursorAdapter inner;
|
||||
private int mIndexUserId;
|
||||
private int mIndexKeyId;
|
||||
private int mIndexMasterKeyId;
|
||||
|
||||
public SelectSignKeyCursorAdapter() {
|
||||
inner = new CursorAdapter(null, null, 0) {
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return getActivity().getLayoutInflater().inflate(R.layout.encrypt_asymmetric_signkey, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
||||
((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")"));
|
||||
((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]);
|
||||
((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(mIndexMasterKeyId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
String[] userIdSplit = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback();
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == null) return inner.swapCursor(null);
|
||||
|
||||
if (userIdSplit[0] != null) {
|
||||
mMainUserId.setText(userIdSplit[0]);
|
||||
mIndexKeyId = newCursor.getColumnIndex(KeyRings.KEY_ID);
|
||||
mIndexUserId = newCursor.getColumnIndex(KeyRings.USER_ID);
|
||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
||||
if (newCursor.moveToFirst()) {
|
||||
do {
|
||||
if (newCursor.getLong(mIndexMasterKeyId) == mEncryptInterface.getSignatureKey()) {
|
||||
mSign.setSelection(newCursor.getPosition() + 1);
|
||||
}
|
||||
} while (newCursor.moveToNext());
|
||||
}
|
||||
return inner.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return inner.getCount() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (position == 0) return null;
|
||||
return inner.getItem(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position == 0) return Constants.key.none;
|
||||
return inner.getItemId(position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = getDropDownView(position, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
View v;
|
||||
if (position == 0) {
|
||||
if (convertView == null) {
|
||||
v = inner.newView(null, null, parent);
|
||||
} else {
|
||||
mMainUserId.setText(R.string.user_id_no_name);
|
||||
v = convertView;
|
||||
}
|
||||
if (userIdSplit[1] != null) {
|
||||
mMainUserIdRest.setText(userIdSplit[1]);
|
||||
} else {
|
||||
mMainUserIdRest.setText(getString(R.string.label_key_id) + ": "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(mSecretKeyId));
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Notify.showNotify(getActivity(), "Key not found! This is a bug!", Notify.Style.ERROR);
|
||||
}
|
||||
mSign.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectPublicKeys() {
|
||||
Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
|
||||
Vector<Long> keyIds = new Vector<Long>();
|
||||
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
|
||||
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
|
||||
keyIds.add(mEncryptionKeyIds[i]);
|
||||
}
|
||||
}
|
||||
long[] initialKeyIds = null;
|
||||
if (keyIds.size() > 0) {
|
||||
initialKeyIds = new long[keyIds.size()];
|
||||
for (int i = 0; i < keyIds.size(); ++i) {
|
||||
initialKeyIds[i] = keyIds.get(i);
|
||||
}
|
||||
}
|
||||
intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds);
|
||||
startActivityForResult(intent, REQUEST_CODE_PUBLIC_KEYS);
|
||||
}
|
||||
|
||||
private void selectSecretKey() {
|
||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
||||
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_SIGN, true);
|
||||
startActivityForResult(intent, REQUEST_CODE_SECRET_KEYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PUBLIC_KEYS: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
setEncryptionKeyIds(bundle
|
||||
.getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_SECRET_KEYS: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uriMasterKey = data.getData();
|
||||
setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment()));
|
||||
} else {
|
||||
setSignatureKeyId(Constants.key.none);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
((TextView) v.findViewById(android.R.id.title)).setText("None");
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.GONE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.GONE);
|
||||
} else {
|
||||
v = inner.getView(position - 1, convertView, parent);
|
||||
v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE);
|
||||
v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,66 +17,51 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
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.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.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
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.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class EncryptFileFragment extends Fragment {
|
||||
public static final String ARG_FILENAME = "filename";
|
||||
public static final String ARG_ASCII_ARMOR = "ascii_armor";
|
||||
public class EncryptFileFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public static final String ARG_URIS = "uris";
|
||||
|
||||
private static final int REQUEST_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 CheckBox mDeleteAfter = null;
|
||||
private CheckBox mShareAfter = null;
|
||||
private ImageButton mBrowse = null;
|
||||
private View mAddView;
|
||||
private View mShareFile;
|
||||
private View mEncryptFile;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
// model
|
||||
private String mInputFilename = null;
|
||||
private Uri mInputUri = null;
|
||||
private String mOutputFilename = null;
|
||||
private Uri mOutputUri = null;
|
||||
private ListView mSelectedFiles;
|
||||
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
|
||||
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<Uri, Bitmap>();
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
@ -99,52 +84,27 @@ public class EncryptFileFragment extends Fragment {
|
||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked();
|
||||
encryptClicked(false);
|
||||
}
|
||||
});
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.filename);
|
||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
mShareFile = view.findViewById(R.id.action_encrypt_share);
|
||||
mShareFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (Constants.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", REQUEST_CODE_FILE);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
||||
REQUEST_CODE_FILE);
|
||||
}
|
||||
encryptClicked(true);
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (Spinner) view.findViewById(R.id.fileCompression);
|
||||
Choice[] choices = new Choice[]{
|
||||
new Choice(Constants.choice.compression.none, getString(R.string.choice_none) + " ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Constants.choice.compression.zip, "ZIP ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Constants.choice.compression.zlib, "ZLIB ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Constants.choice.compression.bzip2, "BZIP2 ("
|
||||
+ getString(R.string.compression_very_slow) + ")"),
|
||||
};
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getActivity(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mFileCompression.setAdapter(adapter);
|
||||
|
||||
int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression();
|
||||
for (int i = 0; i < choices.length; ++i) {
|
||||
if (choices[i].getId() == defaultFileCompression) {
|
||||
mFileCompression.setSelection(i);
|
||||
break;
|
||||
mAddView = inflater.inflate(R.layout.file_list_entry_add, null);
|
||||
mAddView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addInputUri();
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
});
|
||||
mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
|
||||
mSelectedFiles.addFooterView(mAddView);
|
||||
mSelectedFiles.setAdapter(mAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
@ -153,267 +113,128 @@ 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);
|
||||
}
|
||||
boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR);
|
||||
if (asciiArmor) {
|
||||
mAsciiArmor.setChecked(asciiArmor);
|
||||
addInputUris(getArguments().<Uri>getParcelableArrayList(ARG_URIS));
|
||||
}
|
||||
|
||||
private void addInputUri() {
|
||||
if (Constants.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFileFragment.this, "*/*", true, REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFileFragment.this, mEncryptInterface.getInputUris().isEmpty() ?
|
||||
null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1),
|
||||
"*/*", REQUEST_CODE_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
private void addInputUris(List<Uri> uris) {
|
||||
if (uris != null) {
|
||||
for (Uri uri : uris) {
|
||||
addInputUri(uri);
|
||||
}
|
||||
};
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
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("")) {
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
private void addInputUri(Uri inputUri) {
|
||||
if (inputUri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputUri == null && !mInputFilename.startsWith("content")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Notify.showNotify(
|
||||
getActivity(),
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), Notify.Style.ERROR
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mEncryptInterface.getInputUris().add(inputUri);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
// symmetric encryption
|
||||
/**
|
||||
* We hide the encrypt to file button if multiple files are selected.
|
||||
*
|
||||
* With Android L it will be possible to select a target directory for multiple files, so we might want to
|
||||
* change this later
|
||||
*/
|
||||
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
if (mEncryptInterface.getInputUris().size() > 1) {
|
||||
mEncryptFile.setVisibility(View.GONE);
|
||||
} else {
|
||||
// asymmetric encryption
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys) {
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getSignatureKey() != 0 &&
|
||||
PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mEncryptInterface.getSignatureKey()) == null) {
|
||||
PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
showOutputFileDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
mEncryptFile.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
showOutputFileDialog();
|
||||
}
|
||||
|
||||
private void encryptStart() {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
private void delInputUri(int position) {
|
||||
mEncryptInterface.getInputUris().remove(position);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri="
|
||||
+ mOutputUri);
|
||||
|
||||
if (mInputUri != null) {
|
||||
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI);
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri);
|
||||
if (mEncryptInterface.getInputUris().size() > 1) {
|
||||
mEncryptFile.setVisibility(View.GONE);
|
||||
} else {
|
||||
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE);
|
||||
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
|
||||
mEncryptFile.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mOutputUri != null) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI);
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri);
|
||||
private void showOutputFileDialog() {
|
||||
if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(0);
|
||||
if (!Constants.KITKAT) {
|
||||
File file = new File(inputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
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 {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE);
|
||||
data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
|
||||
FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), inputUri) +
|
||||
(mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
String passphrase = mEncryptInterface.getPassphrase();
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
private void encryptClicked(boolean share) {
|
||||
if (share) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
for (Uri uri : mEncryptInterface.getInputUris()) {
|
||||
String targetName = FileHelper.getFilename(getActivity(), uri) +
|
||||
(mEncryptInterface.isUseArmor() ? ".asc" : ".gpg");
|
||||
mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
|
||||
mEncryptInterface.getSignatureKey());
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
|
||||
mEncryptInterface.getEncryptionKeys());
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
} else if (mEncryptInterface.getInputUris().size() == 1) {
|
||||
showOutputFileDialog();
|
||||
}
|
||||
}
|
||||
|
||||
boolean useAsciiArmor = mAsciiArmor.isChecked();
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor);
|
||||
|
||||
int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Notify.showNotify(getActivity(), R.string.encrypt_sign_successful,
|
||||
Notify.Style.INFO);
|
||||
|
||||
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);
|
||||
}
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
startActivity(Intent.createChooser(sendFileIntent,
|
||||
getString(R.string.title_share_file)));
|
||||
}
|
||||
}
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public boolean handleClipData(Intent data) {
|
||||
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
|
||||
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
|
||||
Uri uri = data.getClipData().getItemAt(i).getUri();
|
||||
if (uri != null) addInputUri(uri);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_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!");
|
||||
}
|
||||
if (!Constants.KITKAT || !handleClipData(data)) {
|
||||
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) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
mEncryptInterface.getOutputUris().add(data.getData());
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
@ -422,4 +243,68 @@ public class EncryptFileFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
// Clear cache if needed
|
||||
for (Uri uri : new HashSet<Uri>(thumbnailCache.keySet())) {
|
||||
if (!mEncryptInterface.getInputUris().contains(uri)) {
|
||||
thumbnailCache.remove(uri);
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private class SelectedFilesAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mEncryptInterface.getInputUris().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mEncryptInterface.getInputUris().get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(position);
|
||||
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(), inputUri));
|
||||
long size = FileHelper.getFileSize(getActivity(), inputUri);
|
||||
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);
|
||||
if (!thumbnailCache.containsKey(inputUri)) {
|
||||
thumbnailCache.put(inputUri, FileHelper.getThumbnail(getActivity(), inputUri, new Point(px, px)));
|
||||
}
|
||||
Bitmap bitmap = thumbnailCache.get(inputUri);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,28 +18,15 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.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 org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class EncryptMessageFragment extends Fragment {
|
||||
public static final String ARG_TEXT = "text";
|
||||
@ -69,18 +56,34 @@ public class EncryptMessageFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false);
|
||||
|
||||
mMessage = (TextView) view.findViewById(R.id.message);
|
||||
mMessage.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
mEncryptInterface.setMessage(s.toString());
|
||||
}
|
||||
});
|
||||
mEncryptClipboard = view.findViewById(R.id.action_encrypt_clipboard);
|
||||
mEncryptShare = view.findViewById(R.id.action_encrypt_share);
|
||||
mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked(true);
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
}
|
||||
});
|
||||
mEncryptShare.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked(false);
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,7 +95,7 @@ public class EncryptMessageFragment extends Fragment {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String text = getArguments().getString(ARG_TEXT);
|
||||
String text = mEncryptInterface.getMessage();
|
||||
if (text != null) {
|
||||
mMessage.setText(text);
|
||||
}
|
||||
@ -117,138 +120,4 @@ public class EncryptMessageFragment extends Fragment {
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private void encryptClicked(final boolean toClipboard) {
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
// symmetric encryption
|
||||
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getSignatureKey() != 0 &&
|
||||
PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mEncryptInterface.getSignatureKey()) == null) {
|
||||
PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
encryptStart(toClipboard);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
encryptStart(toClipboard);
|
||||
}
|
||||
|
||||
private void encryptStart(final boolean toClipboard) {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES);
|
||||
|
||||
String message = mMessage.getText().toString();
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
String passphrase = mEncryptInterface.getPassphrase();
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
|
||||
mEncryptInterface.getSignatureKey());
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
|
||||
mEncryptInterface.getEncryptionKeys());
|
||||
|
||||
boolean signOnly = (mEncryptInterface.getEncryptionKeys() == null
|
||||
|| mEncryptInterface.getEncryptionKeys().length == 0);
|
||||
if (signOnly) {
|
||||
message = fixBadCharactersForGmail(message);
|
||||
}
|
||||
}
|
||||
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes());
|
||||
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true);
|
||||
|
||||
int compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression();
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle data = message.getData();
|
||||
|
||||
String output = new String(data.getByteArray(KeychainIntentService.RESULT_BYTES));
|
||||
Log.d(Constants.TAG, "output: " + output);
|
||||
|
||||
if (toClipboard) {
|
||||
ClipboardReflection.copyToClipboard(getActivity(), output);
|
||||
Notify.showNotify(getActivity(),
|
||||
R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO);
|
||||
} else {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
|
||||
// Type is set to text/plain so that encrypted messages can
|
||||
// be sent with Whatsapp, Hangouts, SMS etc...
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, output);
|
||||
startActivity(Intent.createChooser(sendIntent,
|
||||
getString(R.string.title_share_with)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
}
|
||||
|
@ -29,27 +29,20 @@ import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class EncryptSymmetricFragment extends Fragment {
|
||||
public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
|
||||
OnSymmetricKeySelection mPassphraseUpdateListener;
|
||||
EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
private EditText mPassphrase;
|
||||
private EditText mPassphraseAgain;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnSymmetricKeySelection {
|
||||
public void onPassphraseUpdate(String passphrase);
|
||||
|
||||
public void onPassphraseAgainUpdate(String passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mPassphraseUpdateListener = (OnSymmetricKeySelection) activity;
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection");
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +55,7 @@ public class EncryptSymmetricFragment extends Fragment {
|
||||
|
||||
mPassphrase = (EditText) view.findViewById(R.id.passphrase);
|
||||
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
||||
mPassphrase.addTextChangedListener(new TextWatcher() {
|
||||
TextWatcher textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@ -74,25 +67,21 @@ public class EncryptSymmetricFragment extends Fragment {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// update passphrase in EncryptActivity
|
||||
mPassphraseUpdateListener.onPassphraseUpdate(s.toString());
|
||||
if (mPassphrase.getText().toString().equals(mPassphraseAgain.getText().toString())) {
|
||||
mEncryptInterface.setPassphrase(s.toString());
|
||||
} else {
|
||||
mEncryptInterface.setPassphrase(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
mPassphraseAgain.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// update passphrase in EncryptActivity
|
||||
mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString());
|
||||
}
|
||||
});
|
||||
};
|
||||
mPassphrase.addTextChangedListener(textWatcher);
|
||||
mPassphraseAgain.addTextChangedListener(textWatcher);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -176,8 +176,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: {
|
||||
|
@ -296,8 +296,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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -18,40 +18,21 @@
|
||||
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 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
|
||||
*/
|
||||
@ -73,15 +54,15 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().<Uri>getParcelable(ARG_DELETE_URI) : null;
|
||||
final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
|
||||
final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI);
|
||||
final 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,51 +70,23 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
if (deleteUri != null) {
|
||||
// We can not securely delete Documents, so just use usual delete on them
|
||||
DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri);
|
||||
// We can not securely delete Uris, so just use usual delete on them
|
||||
if (Constants.KITKAT) {
|
||||
if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) {
|
||||
Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) {
|
||||
Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(activity, KeychainIntentService.class);
|
||||
Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, deleteFilename), Toast.LENGTH_SHORT).show();
|
||||
|
||||
// fill values for this action
|
||||
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(
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog");
|
||||
|
||||
// start service with intent
|
||||
activity.startService(intent);
|
||||
// Note: We can't delete every 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() {
|
||||
|
@ -18,18 +18,15 @@
|
||||
package org.sufficientlysecure.keychain.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
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;
|
||||
@ -37,12 +34,17 @@ import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
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";
|
||||
@ -52,8 +54,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;
|
||||
@ -63,8 +64,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;
|
||||
|
||||
@ -72,14 +72,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);
|
||||
@ -98,7 +98,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
|
||||
@ -112,18 +116,14 @@ public class FileDialogFragment extends DialogFragment {
|
||||
mMessageTextView.setText(message);
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.input);
|
||||
mFilename.setText(mOutputFilename);
|
||||
mFilename.setText(mFile.getName());
|
||||
mBrowse = (ImageButton) 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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -146,19 +146,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);
|
||||
@ -175,44 +179,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);
|
||||
}
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,251 @@
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
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() {
|
||||
swapCursor(null);
|
||||
setPrefix(getContext().getString(R.string.label_to) + ": ");
|
||||
allowDuplicates(false);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(getContext(), KeychainContract.KeyRings.buildUnifiedKeyRingsUri(),
|
||||
new String[]{KeychainContract.KeyRings.HAS_ENCRYPT, KeychainContract.KeyRings.KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.FINGERPRINT},
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
swapCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
swapCursor(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
|
||||
super.onFocusChanged(hasFocus, direction, previous);
|
||||
if (hasFocus) {
|
||||
((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
|
||||
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
public void swapCursor(Cursor cursor) {
|
||||
if (cursor == null) {
|
||||
setAdapter(new EncryptKeyAdapter(Collections.<EncryptionKey>emptyList()));
|
||||
return;
|
||||
}
|
||||
ArrayList<EncryptionKey> keys = new ArrayList<EncryptionKey>();
|
||||
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 mUserIdFull;
|
||||
private String[] mUserId;
|
||||
private long mKeyId;
|
||||
private String mFingerprint;
|
||||
|
||||
public EncryptionKey(String userId, long keyId, String fingerprint) {
|
||||
this.mUserId = KeyRing.splitUserId(userId);
|
||||
this.mUserIdFull = 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 mUserIdFull;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return mFingerprint;
|
||||
}
|
||||
|
||||
public String getPrimary() {
|
||||
if (mUserId[0] != null && mUserId[2] != null) {
|
||||
return mUserId[0] + " (" + mUserId[2] + ")";
|
||||
} else if (mUserId[0] != null) {
|
||||
return mUserId[0];
|
||||
} else {
|
||||
return mUserId[1];
|
||||
}
|
||||
}
|
||||
|
||||
public String getSecondary() {
|
||||
if (mUserId[0] != null) {
|
||||
return mUserId[1];
|
||||
} else {
|
||||
return getKeyIdHex();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTertiary() {
|
||||
if (mUserId[0] != null) {
|
||||
return getKeyIdHex();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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<EncryptionKey> {
|
||||
|
||||
public EncryptKeyAdapter(List<EncryptionKey> 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());
|
||||
((TextView) view.findViewById(android.R.id.text2)).setText(getItem(position).getTertiary());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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;
|
||||
View child = getChildAt(getCurrentItem());
|
||||
if (child != null) {
|
||||
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
height = child.getMeasuredHeight();
|
||||
} else {
|
||||
height = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
BIN
OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png
Normal file
After Width: | Height: | Size: 694 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png
Normal file
After Width: | Height: | Size: 561 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 344 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png
Normal file
After Width: | Height: | Size: 831 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png
Normal file
After Width: | Height: | Size: 585 B |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
@ -26,32 +26,41 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:orientation="horizontal"
|
||||
|
||||
<EditText
|
||||
android:id="@+id/decrypt_file_filename"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top|left"
|
||||
android:inputType="textMultiLine|textUri"
|
||||
android:lines="4"
|
||||
android:maxLines="10"
|
||||
android:minLines="2"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/decrypt_file_browse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:src="@drawable/ic_action_collection"
|
||||
android:background="@drawable/button_rounded"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/label_file_colon"
|
||||
android:gravity="center_vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/decrypt_file_filename"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/filemanager_title_open"
|
||||
android:drawableRight="@drawable/ic_action_collection"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/decrypt_file_delete_after_decryption"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.widget.FixedDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<android.support.v4.widget.FixedDrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.EncryptActivity">
|
||||
|
||||
<include layout="@layout/encrypt_content"/>
|
||||
|
||||
|
@ -1,77 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingLeft="16dp">
|
||||
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingLeft="16dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/sign"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/label_sign" />
|
||||
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="4dip">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mainUserId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text=""
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mainUserIdRest"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text=""
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
android:padding="0dp"
|
||||
android:layout_margin="0dp"
|
||||
style="@android:style/Widget.EditText">
|
||||
<TextView
|
||||
android:id="@+id/label_selectPublicKeys"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/label_select_public_keys"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/label_asymmetric_from"/>
|
||||
<Spinner
|
||||
android:id="@+id/sign"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_selectEncryptKeys"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="4dp"
|
||||
android:text="@string/select_keys_button_default"
|
||||
android:background="@drawable/button_edgy"
|
||||
android:drawableLeft="@drawable/ic_action_person" />
|
||||
</LinearLayout>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView
|
||||
android:id="@+id/recipient_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:padding="4dp"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:id="@android:id/title"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
<TextView android:id="@android:id/text1"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginTop="-4dip"/>
|
||||
<TextView android:id="@android:id/text2"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginTop="-4dip"/>
|
||||
</LinearLayout>
|
@ -1,23 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content_frame"
|
||||
android:layout_marginLeft="@dimen/drawer_content_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/encrypt_pager_mode"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content_frame"
|
||||
android:layout_marginLeft="@dimen/drawer_content_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v4.view.PagerTabStrip
|
||||
android:id="@+id/encrypt_pager_tab_strip_mode"
|
||||
<include layout="@layout/notify_area"/>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.NoSwipeWrapContentViewPager
|
||||
android:id="@+id/encrypt_pager_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:textColor="@color/emphasis" />
|
||||
</android.support.v4.view.ViewPager>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.NoSwipeWrapContentViewPager>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/encrypt_pager_content"
|
||||
|
@ -21,43 +21,4 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/deleteAfterEncryption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/label_delete_after_encryption"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/shareAfterEncryption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/label_share_after_encryption"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/asciiArmor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/label_ascii_armor"/>
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
|
@ -1,87 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/filename"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top|left"
|
||||
android:inputType="textMultiLine|textUri"
|
||||
android:lines="4"
|
||||
android:maxLines="10"
|
||||
android:minLines="2"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_browse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_action_collection"
|
||||
android:background="@drawable/button_rounded"/>
|
||||
</LinearLayout>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
custom:foldedLabel="@string/btn_encryption_advanced_settings_show"
|
||||
custom:unFoldedLabel="@string/btn_encryption_advanced_settings_hide"
|
||||
custom:foldedIcon="fa-chevron-right"
|
||||
custom:unFoldedIcon="fa-chevron-down">
|
||||
|
||||
<include layout="@layout/encrypt_content_adv_settings" />
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/action_encrypt_file"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="vertical">
|
||||
<ListView
|
||||
android:id="@+id/selected_files_list"
|
||||
android:dividerHeight="4dip"
|
||||
android:divider="@android:color/transparent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:text="@string/btn_encrypt_file"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:drawableRight="@drawable/ic_action_save"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginBottom="8dp" />
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<View
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:layout_above="@+id/action_encrypt_file" />
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<!-- Note: The following construct should be a widget, we use it quiet often -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/action_encrypt_share"
|
||||
android:paddingLeft="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/btn_encrypt_share_file"
|
||||
android:layout_weight="1"
|
||||
android:drawableRight="@drawable/ic_action_share"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"/>
|
||||
|
||||
<View
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="right"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_encrypt_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_action_save"
|
||||
android:layout_gravity="center_vertical"
|
||||
style="@style/SelectableItem"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -1,52 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/modeSymmetric"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:stretchColumns="1"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="vertical">
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/modeSymmetric"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:stretchColumns="1"
|
||||
android:layout_centerVertical="true">
|
||||
<TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:id="@+id/label_passphrase"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="8dp"
|
||||
android:text="@string/label_passphrase"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_passphrase"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="8dp"
|
||||
android:text="@string/label_passphrase"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
<EditText
|
||||
android:id="@+id/passphrase"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword" />
|
||||
</TableRow>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passphrase"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword" />
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:id="@+id/label_passphraseAgain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="8dp"
|
||||
android:text="@string/label_passphrase_again"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_passphraseAgain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="8dp"
|
||||
android:text="@string/label_passphrase_again"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passphraseAgain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</RelativeLayout>
|
||||
<EditText
|
||||
android:id="@+id/passphraseAgain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
60
OpenKeychain/src/main/res/layout/file_list_entry.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dip"
|
||||
android:background="@drawable/attachment_bg_holo">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:scaleType="center"
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/thumbnail"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filename"
|
||||
android:layout_marginLeft="8dip"
|
||||
android:layout_marginRight="32dip"
|
||||
android:maxLines="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
android:layout_marginLeft="8dip"
|
||||
android:layout_marginRight="32dip"
|
||||
android:maxLines="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="12sp"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_remove_file_from_list"
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_alignParentRight="true"
|
||||
android:paddingRight="16dip"
|
||||
android:paddingLeft="16dip"
|
||||
android:src="@drawable/ic_action_cancel"
|
||||
android:clickable="true"
|
||||
android:layout_centerVertical="true"
|
||||
style="@style/SelectableItem"/>
|
||||
</RelativeLayout>
|
21
OpenKeychain/src/main/res/layout/file_list_entry_add.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:padding="4dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
style="@style/SelectableItem">
|
||||
<TextView
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/btn_add_files"
|
||||
android:drawableLeft="@drawable/ic_action_collection"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center"/>
|
||||
</FrameLayout>
|
24
OpenKeychain/src/main/res/layout/recipient_box_entry.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/attachment_bg_holo">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dip"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="32dip"
|
||||
android:layout_height="32dip"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:cropToPadding="true"
|
||||
android:background="#ccc"
|
||||
android:scaleType="centerCrop"/>
|
||||
</LinearLayout>
|
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dip"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<LinearLayout
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1">
|
||||
<TextView android:id="@android:id/title"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dip"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
<TextView android:id="@android:id/text1"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dip"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginTop="-4dip"/>
|
||||
<TextView android:id="@android:id/text2"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dip"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginTop="-4dip"/>
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="56dip"
|
||||
android:layout_height="56dip"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:cropToPadding="true"
|
||||
android:background="#ccc"
|
||||
android:scaleType="centerCrop"/>
|
||||
</LinearLayout>
|
7
OpenKeychain/src/main/res/menu/encrypt_activity.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/check_use_symmetric" android:title="@string/label_symmetric" android:checkable="true"/>
|
||||
<item android:id="@+id/check_use_armor" android:title="@string/label_ascii_armor" android:checkable="true" />
|
||||
<item android:id="@+id/check_delete_after_encrypt" android:title="@string/label_delete_after_encryption" android:checkable="true" />
|
||||
</menu>
|
@ -54,6 +54,8 @@
|
||||
<string name="btn_decrypt_verify_message">Decrypt and verify message</string>
|
||||
<string name="btn_decrypt_verify_clipboard">From Clipboard</string>
|
||||
<string name="btn_encrypt_file">Encrypt and save file</string>
|
||||
<string name="btn_encrypt_share_file">Encrypt and share file</string>
|
||||
<string name="btn_encrypt_sign_share">Encrypt, sign and share</string>
|
||||
<string name="btn_save">Save</string>
|
||||
<string name="btn_do_not_save">Cancel</string>
|
||||
<string name="btn_delete">Delete</string>
|
||||
@ -72,6 +74,7 @@
|
||||
<string name="btn_share_encrypted_signed">Share encrypted/signed message…</string>
|
||||
<string name="btn_view_cert_key">View certification key</string>
|
||||
<string name="btn_create_key">Create key</string>
|
||||
<string name="btn_add_files">Add file(s)</string>
|
||||
|
||||
<!-- menu -->
|
||||
<string name="menu_preferences">Settings</string>
|
||||
@ -103,6 +106,8 @@
|
||||
<string name="label_sign">Sign</string>
|
||||
<string name="label_message">Message</string>
|
||||
<string name="label_file">File</string>
|
||||
<string name="label_files">File(s)</string>
|
||||
<string name="label_file_colon">File:</string>
|
||||
<string name="label_no_passphrase">No Passphrase</string>
|
||||
<string name="label_passphrase">Passphrase</string>
|
||||
<string name="label_passphrase_again">Repeat Passphrase</string>
|
||||
@ -111,13 +116,15 @@
|
||||
<string name="label_conceal_pgp_application">Let others know that you\'re using OpenKeychain</string>
|
||||
<string name="label_conceal_pgp_application_summary">Writes \'OpenKeychain v2.7\' to OpenPGP signatures, ciphertext, and exported keys</string>
|
||||
<string name="label_select_public_keys">Recipients</string>
|
||||
<string name="label_asymmetric_from">From:</string>
|
||||
<string name="label_to">To</string>
|
||||
<string name="label_delete_after_encryption">Delete After Encryption</string>
|
||||
<string name="label_delete_after_decryption">Delete After Decryption</string>
|
||||
<string name="label_share_after_encryption">Share After Encryption</string>
|
||||
<string name="label_encryption_algorithm">Encryption Algorithm</string>
|
||||
<string name="label_hash_algorithm">Hash Algorithm</string>
|
||||
<string name="label_asymmetric">with Public Key</string>
|
||||
<string name="label_symmetric">with Passphrase</string>
|
||||
<string name="label_asymmetric">With Public Key</string>
|
||||
<string name="label_symmetric">With Passphrase</string>
|
||||
<string name="label_passphrase_cache_ttl">Passphrase Cache</string>
|
||||
<string name="label_message_compression">Message Compression</string>
|
||||
<string name="label_file_compression">File Compression</string>
|
||||
@ -196,6 +203,7 @@
|
||||
<string name="wrong_passphrase">Wrong passphrase.</string>
|
||||
<string name="set_a_passphrase">Set a passphrase first.</string>
|
||||
<string name="no_filemanager_installed">No compatible file manager installed.</string>
|
||||
<string name="filemanager_no_write">The file manager does not support saving files.</string>
|
||||
<string name="passphrases_do_not_match">The passphrases didn\'t match.</string>
|
||||
<string name="passphrase_must_not_be_empty">Please enter a passphrase.</string>
|
||||
<string name="passphrase_for_symmetric_encryption">Symmetric encryption.</string>
|
||||
|
1
extern/TokenAutoComplete
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4239ef065b738a53ac86f3807cad26d4471aedf6
|
@ -12,3 +12,4 @@ include ':extern:spongycastle:prov'
|
||||
include ':extern:SuperToasts:supertoasts'
|
||||
include ':extern:minidns'
|
||||
include ':extern:KeybaseLib:Lib'
|
||||
include ':extern:TokenAutoComplete:library'
|
||||
|