Merge pull request #728 from mar-v-in/improve-file-more

Improve file encrypt
This commit is contained in:
Dominik Schürmann 2014-08-04 00:21:59 +02:00
commit c2ca841ac9
55 changed files with 2088 additions and 1383 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -18,6 +18,7 @@ dependencies {
compile project(':extern:SuperToasts:supertoasts')
compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib')
compile project(':extern:TokenAutoComplete:library')
}

View File

@ -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>

View File

@ -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 {

View File

@ -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) {

View File

@ -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
*/

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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!");

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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!");

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
});

View 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: {

View File

@ -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)
);
}

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -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"

View File

@ -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"/>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View File

@ -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

View File

@ -12,3 +12,4 @@ include ':extern:spongycastle:prov'
include ':extern:SuperToasts:supertoasts'
include ':extern:minidns'
include ':extern:KeybaseLib:Lib'
include ':extern:TokenAutoComplete:library'