Merge branch 'master' into yubikey

Conflicts:
	.gitmodules
	OpenKeychain/build.gradle
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
	extern/openpgp-api-lib
	settings.gradle
This commit is contained in:
Dominik Schürmann
2014-08-06 01:08:12 +02:00
207 changed files with 4789 additions and 5294 deletions

View File

@@ -17,16 +17,16 @@
package org.sufficientlysecure.keychain;
import android.os.Build;
import android.os.Environment;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
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;
@@ -49,12 +49,11 @@ public final class Constants {
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
public static 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 {
@@ -89,23 +88,6 @@ public final class Constants {
};
}
public static final class choice {
public static final class algorithm {
// TODO: legacy reasons :/ better: PublicKeyAlgorithmTags
public static final int dsa = 0x21070001;
public static final int elgamal = 0x21070002;
public static final int rsa = 0x21070003;
}
public static final class compression {
// TODO: legacy reasons :/ better: CompressionAlgorithmTags.UNCOMPRESSED
public static final int none = 0x21070001;
public static final int zlib = CompressionAlgorithmTags.ZLIB;
public static final int bzip2 = CompressionAlgorithmTags.BZIP2;
public static final int zip = CompressionAlgorithmTags.ZIP;
}
}
public static final class key {
public static final int none = 0;
public static final int symmetric = -1;

View File

@@ -28,11 +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;
public class KeychainApplication extends Application {
@@ -73,8 +72,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
}
@@ -86,9 +84,11 @@ public class KeychainApplication extends Application {
setupAccountAsNeeded(this);
// Update keyserver list as needed
Preferences.getPreferences(this).updateKeyServers();
Preferences.getPreferences(this).updatePreferences();
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
TemporaryStorageProvider.cleanUp(this);
}
public static void setupAccountAsNeeded(Context context) {

View File

@@ -19,8 +19,13 @@ package org.sufficientlysecure.keychain.helper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.*;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
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,7 +38,14 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.Log;
import java.util.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ContactHelper {
@@ -60,6 +72,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 +246,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
*/
@@ -356,7 +394,7 @@ public class ContactHelper {
int rawContactId, long masterKeyId) {
ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build());
Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(Long.toString(masterKeyId)),
Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(masterKeyId),
USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null);
if (ids != null) {
while (ids.moveToNext()) {

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Messenger;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
@@ -29,6 +30,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class EmailKeyHelper {
@@ -86,7 +88,7 @@ public class EmailKeyHelper {
for (ImportKeysListEntry key : keyServer.search(mail)) {
if (key.isRevoked() || key.isExpired()) continue;
for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase())) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
keys.add(key);
}
}

View File

@@ -30,18 +30,17 @@ 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;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
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 +67,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 +107,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,26 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.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 +66,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 +88,153 @@ 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 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 requestCode used to identify the result coming back from storage browser onActivityResult() in your
* @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) {
return getFileSize(context, uri, -1);
}
public static long getFileSize(Context context, Uri uri, long def) {
long size = def;
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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

@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.helper;
import android.content.Context;
import android.content.SharedPreferences;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants;
@@ -99,7 +100,7 @@ public class Preferences {
public int getDefaultMessageCompression() {
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
Constants.choice.compression.zlib);
CompressionAlgorithmTags.ZLIB);
}
public void setDefaultMessageCompression(int value) {
@@ -110,7 +111,7 @@ public class Preferences {
public int getDefaultFileCompression() {
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
Constants.choice.compression.none);
CompressionAlgorithmTags.UNCOMPRESSED);
}
public void setDefaultFileCompression(int value) {
@@ -170,7 +171,8 @@ public class Preferences {
editor.commit();
}
public void updateKeyServers() {
public void updatePreferences() {
// migrate keyserver to hkps
if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) !=
Constants.Defaults.KEY_SERVERS_VERSION) {
String[] servers = getKeyServers();
@@ -186,6 +188,11 @@ public class Preferences {
.putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION)
.commit();
}
// migrate old uncompressed constant to new one
if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) {
setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED);
}
}
public void setConcealPgpApplication(boolean conceal) {

View File

@@ -18,12 +18,10 @@
package org.sufficientlysecure.keychain.helper;
import android.content.res.AssetManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -34,10 +32,16 @@ import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
public class TlsHelper {
public static class TlsHelperException extends Exception {

View File

@@ -18,11 +18,6 @@
package org.sufficientlysecure.keychain.keyimport;
import de.measite.minidns.Client;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
import de.measite.minidns.record.SRV;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.TlsHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
@@ -45,6 +40,11 @@ import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.measite.minidns.Client;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
import de.measite.minidns.record.SRV;
public class HkpKeyserver extends Keyserver {
private static class HttpError extends Exception {
private static final long serialVersionUID = 1718783705229428893L;
@@ -251,14 +251,14 @@ public class HkpKeyserver extends Keyserver {
data = query(request);
} catch (HttpError e) {
if (e.getData() != null) {
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.US));
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH));
if (e.getData().toLowerCase(Locale.US).contains("no keys found")) {
if (e.getData().toLowerCase(Locale.ENGLISH).contains("no keys found")) {
// NOTE: This is also a 404 error for some keyservers!
return results;
} else if (e.getData().toLowerCase(Locale.US).contains("too many")) {
} else if (e.getData().toLowerCase(Locale.ENGLISH).contains("too many")) {
throw new TooManyResponsesException();
} else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
} else if (e.getData().toLowerCase(Locale.ENGLISH).contains("insufficient")) {
throw new QueryTooShortException();
} else if (e.getCode() == 404) {
// NOTE: handle this 404 at last, maybe it was a "no keys found" error
@@ -285,7 +285,7 @@ public class HkpKeyserver extends Keyserver {
// group 1 contains the full fingerprint (v4) or the long key id if available
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.US);
String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.ENGLISH);
if (fingerprintOrKeyId.length() > 16) {
entry.setFingerprintHex(fingerprintOrKeyId);
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.keyimport;
import android.os.Parcel;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPPublicKey;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.HashAlgorithmTags;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
@@ -6,7 +23,6 @@ import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import android.text.TextUtils;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
/**

View File

@@ -1,102 +0,0 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
public class PgpConversionHelper {
/**
* Convert from byte[] to ArrayList<PGPSecretKey>
*
* @param keysBytes
* @return
*/
public static ArrayList<UncachedSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
Object obj = null;
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
try {
while ((obj = factory.nextObject()) != null) {
PGPSecretKey secKey = null;
if (obj instanceof PGPSecretKey) {
secKey = (PGPSecretKey) obj;
if (secKey == null) {
Log.e(Constants.TAG, "No keys given!");
}
keys.add(new UncachedSecretKey(secKey));
} else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
PGPSecretKeyRing keyRing = null;
keyRing = (PGPSecretKeyRing) obj;
if (keyRing == null) {
Log.e(Constants.TAG, "No keys given!");
}
@SuppressWarnings("unchecked")
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
while (itr.hasNext()) {
keys.add(new UncachedSecretKey(itr.next()));
}
}
}
} catch (IOException e) {
}
return keys;
}
/**
* Convert from byte[] to PGPSecretKey
* <p/>
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
*
* @param keyBytes
* @return
*/
public static UncachedSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
PGPObjectFactory factory = new PGPObjectFactory(keyBytes);
Object obj = null;
try {
obj = factory.nextObject();
} catch (IOException e) {
Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e);
}
PGPSecretKey secKey = null;
if (obj instanceof PGPSecretKey) {
if ((secKey = (PGPSecretKey) obj) == null) {
Log.e(Constants.TAG, "No keys given!");
}
} else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
PGPSecretKeyRing keyRing = null;
if ((keyRing = (PGPSecretKeyRing) obj) == null) {
Log.e(Constants.TAG, "No keys given!");
}
secKey = keyRing.getSecretKey();
}
return new UncachedSecretKey(secKey);
}
}

View File

@@ -24,7 +24,7 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -37,9 +37,6 @@ import java.util.Locale;
public class PgpKeyHelper {
/**
* TODO: Only used in HkpKeyServer. Get rid of this one!
*/
public static String getAlgorithmInfo(int algorithm) {
return getAlgorithmInfo(null, algorithm, 0);
}
@@ -55,25 +52,25 @@ public class PgpKeyHelper {
String algorithmStr;
switch (algorithm) {
case PGPPublicKey.RSA_ENCRYPT:
case PGPPublicKey.RSA_GENERAL:
case PGPPublicKey.RSA_SIGN: {
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_GENERAL:
case PublicKeyAlgorithmTags.RSA_SIGN: {
algorithmStr = "RSA";
break;
}
case PGPPublicKey.DSA: {
case PublicKeyAlgorithmTags.DSA: {
algorithmStr = "DSA";
break;
}
case PGPPublicKey.ELGAMAL_ENCRYPT:
case PGPPublicKey.ELGAMAL_GENERAL: {
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: {
algorithmStr = "ElGamal";
break;
}
case PGPPublicKey.ECDSA:
case PGPPublicKey.ECDH: {
case PublicKeyAlgorithmTags.ECDSA:
case PublicKeyAlgorithmTags.ECDH: {
algorithmStr = "ECC";
break;
}
@@ -82,7 +79,6 @@ public class PgpKeyHelper {
if (context != null) {
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
} else {
// TODO
algorithmStr = "unknown";
}
break;
@@ -104,7 +100,7 @@ public class PgpKeyHelper {
* @return
*/
public static String convertFingerprintToHex(byte[] fingerprint) {
String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.US);
String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
return hexString;
}
@@ -133,7 +129,7 @@ public class PgpKeyHelper {
}
private static String convertKeyIdToHex32bit(long keyId) {
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.ENGLISH);
while (hexString.length() < 8) {
hexString = "0" + hexString;
}

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
@@ -138,7 +139,7 @@ public class PgpKeyOperation {
KeyPairGenerator keyGen;
switch (algorithmChoice) {
case Constants.choice.algorithm.dsa: {
case PublicKeyAlgorithmTags.DSA: {
progress(R.string.progress_generating_dsa, 30);
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom());
@@ -146,7 +147,7 @@ public class PgpKeyOperation {
break;
}
case Constants.choice.algorithm.elgamal: {
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
progress(R.string.progress_generating_elgamal, 30);
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize);
@@ -159,7 +160,7 @@ public class PgpKeyOperation {
break;
}
case Constants.choice.algorithm.rsa: {
case PublicKeyAlgorithmTags.RSA_GENERAL: {
progress(R.string.progress_generating_rsa, 30);
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom());
@@ -217,7 +218,7 @@ public class PgpKeyOperation {
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
if (add.mAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
@@ -356,7 +357,7 @@ public class PgpKeyOperation {
progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddUserIds.size()));
String userId = saveParcel.mAddUserIds.get(i);
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent, userId);
if (userId.equals("")) {
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
@@ -768,7 +769,7 @@ public class PgpKeyOperation {
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
// If this key can sign, we need a primary key binding signature
if ((flags & KeyFlags.SIGN_DATA) != 0) {
if ((flags & KeyFlags.SIGN_DATA) > 0) {
// cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate);

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPException;
@@ -116,7 +117,7 @@ public class PgpSignEncrypt {
// optional
private Progressable mProgressable = null;
private boolean mEnableAsciiArmorOutput = false;
private int mCompressionId = Constants.choice.compression.none;
private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
private long[] mEncryptionMasterKeyIds = null;
private String mSymmetricPassphrase = null;
private int mSymmetricEncryptionAlgorithm = 0;
@@ -264,7 +265,7 @@ public class PgpSignEncrypt {
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
|| mSymmetricPassphrase != null);
boolean enableCompression = (mCompressionId != Constants.choice.compression.none);
boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED);
Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.SignatureSubpacket;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.provider;
import android.database.Cursor;
@@ -201,7 +218,7 @@ public class CachedPublicKeyRing extends KeyRing {
}
private Cursor getSubkeys() throws PgpGeneralException {
Uri keysUri = KeychainContract.Keys.buildKeysUri(Long.toString(extractOrGetMasterKeyId()));
Uri keysUri = KeychainContract.Keys.buildKeysUri(extractOrGetMasterKeyId());
return mProviderHelper.getContentResolver().query(keysUri, null, null, null, null);
}
}

View File

@@ -172,8 +172,8 @@ public class KeychainContract {
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
}
public static Uri buildPublicKeyRingUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build();
public static Uri buildPublicKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build();
}
public static Uri buildPublicKeyRingUri(Uri uri) {
@@ -184,8 +184,8 @@ public class KeychainContract {
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
}
public static Uri buildSecretKeyRingUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
public static Uri buildSecretKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_SECRET).build();
}
public static Uri buildSecretKeyRingUri(Uri uri) {
@@ -210,8 +210,8 @@ public class KeychainContract {
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
public static Uri buildKeysUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build();
public static Uri buildKeysUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build();
}
public static Uri buildKeysUri(Uri uri) {
@@ -237,8 +237,8 @@ public class KeychainContract {
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids";
public static Uri buildUserIdsUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build();
public static Uri buildUserIdsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build();
}
public static Uri buildUserIdsUri(Uri uri) {
@@ -304,12 +304,14 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static Uri buildCertsUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
public static Uri buildCertsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build();
}
public static Uri buildCertsSpecificUri(String masterKeyId, String rank, String certifier) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).appendPath(rank).appendPath(certifier).build();
public static Uri buildCertsSpecificUri(long masterKeyId, long rank, long certifier) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId))
.appendPath(PATH_CERTS).appendPath(Long.toString(rank))
.appendPath(Long.toString(certifier)).build();
}
public static Uri buildCertsUri(Uri uri) {

View File

@@ -165,8 +165,8 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// make sure this is only done once, on the first instance!
boolean iAmIt = false;
synchronized(KeychainDatabase.class) {
if(!KeychainDatabase.apgHack) {
synchronized (KeychainDatabase.class) {
if (!KeychainDatabase.apgHack) {
iAmIt = true;
KeychainDatabase.apgHack = true;
}
@@ -316,42 +316,37 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}
private static void copy(File in, File out) throws IOException {
FileInputStream ss = new FileInputStream(in);
FileOutputStream ds = new FileOutputStream(out);
FileInputStream is = new FileInputStream(in);
FileOutputStream os = new FileOutputStream(out);
byte[] buf = new byte[512];
while (ss.available() > 0) {
int count = ss.read(buf, 0, 512);
ds.write(buf, 0, count);
while (is.available() > 0) {
int count = is.read(buf, 0, 512);
os.write(buf, 0, count);
}
}
public static void debugRead(Context context) throws IOException {
public static void debugBackup(Context context, boolean restore) throws IOException {
if (!Constants.DEBUG) {
return;
}
File in = context.getDatabasePath("debug.db");
File out = context.getDatabasePath("openkeychain.db");
File in;
File out;
if (restore) {
in = context.getDatabasePath("debug_backup.db");
out = context.getDatabasePath(DATABASE_NAME);
} else {
in = context.getDatabasePath(DATABASE_NAME);
out = context.getDatabasePath("debug_backup.db");
out.createNewFile();
}
if (!in.canRead()) {
throw new IOException("Cannot read " + in.getName());
}
if (!out.canRead()) {
if (!out.canWrite()) {
throw new IOException("Cannot write " + out.getName());
}
copy(in, out);
}
public static void debugWrite(Context context) throws IOException {
if (!Constants.DEBUG) {
return;
}
File in = context.getDatabasePath("openkeychain.db");
File out = context.getDatabasePath("debug.db");
if (!in.canRead()) {
throw new IOException("Cannot read " + in.getName());
}
if (!out.canRead()) {
throw new IOException("Cannot write " + out.getName());
}
copy(in, out);
}
}

View File

@@ -28,17 +28,14 @@ import android.os.RemoteException;
import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.NullProgressable;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
@@ -51,6 +48,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -299,7 +299,7 @@ public class ProviderHelper {
return SaveKeyringResult.RESULT_ERROR;
}
Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId);
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
}
@@ -307,7 +307,7 @@ public class ProviderHelper {
progress.setProgress(LogType.MSG_IP_INSERT_SUBKEYS.getMsgId(), 40, 100);
mIndent += 1;
{ // insert subkeys
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
Uri uri = Keys.buildKeysUri(masterKeyId);
int rank = 0;
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
long keyId = key.getKeyId();
@@ -498,7 +498,7 @@ public class ProviderHelper {
try {
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
int deleted = mContentResolver.delete(
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null);
if (deleted > 0) {
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK);
result |= SaveKeyringResult.UPDATED;
@@ -567,7 +567,7 @@ public class ProviderHelper {
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
// insert new version of this keyRing
Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId);
if (mContentResolver.insert(uri, values) == null) {
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
return SaveKeyringResult.RESULT_ERROR;
@@ -579,7 +579,7 @@ public class ProviderHelper {
}
{
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
Uri uri = Keys.buildKeysUri(masterKeyId);
// first, mark all keys as not available
ContentValues values = new ContentValues();
@@ -836,7 +836,7 @@ public class ProviderHelper {
values.put(Certs.VERIFIED, verified);
values.put(Certs.DATA, cert.getEncoded());
Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId));
Uri uri = Certs.buildCertsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
@@ -853,7 +853,7 @@ public class ProviderHelper {
values.put(UserIds.IS_REVOKED, item.isRevoked);
values.put(UserIds.RANK, rank);
Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId));
Uri uri = UserIds.buildUserIdsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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

@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.remote;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants;
@@ -39,7 +40,7 @@ public class AccountSettings {
// defaults:
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
this.mCompression = Constants.choice.compression.zlib;
this.mCompression = CompressionAlgorithmTags.ZLIB;
}
public String getAccountName() {

View File

@@ -26,9 +26,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Button;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;

View File

@@ -23,7 +23,13 @@ import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Intent;
import android.content.SyncResult;
import android.os.*;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.helper.ContactHelper;

View File

@@ -29,6 +29,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;

View File

@@ -24,7 +24,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants;
@@ -32,7 +31,6 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.util.FileImportCache;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
@@ -56,6 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
import org.sufficientlysecure.keychain.util.FileImportCache;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -63,11 +62,9 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
@@ -110,8 +107,10 @@ public class KeychainIntentService extends IntentService
public static final String SOURCE = "source";
// possible targets:
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_URI = 2;
public static final int IO_URIS = 3;
public static final String SELECTED_URI = "selected_uri";
// encrypt
public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id";
@@ -121,8 +120,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 +143,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 +228,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,44 +236,48 @@ public class KeychainIntentService extends IntentService
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
InputData inputData = createEncryptInputData(data);
OutputStream outStream = createCryptOutputStream(data);
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())
.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())
.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);
@@ -415,13 +422,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>();
@@ -453,12 +463,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();
}
@@ -694,22 +711,17 @@ public class KeychainIntentService extends IntentService
byte[] bytes = data.getByteArray(bytesName);
return new InputData(new ByteArrayInputStream(bytes), bytes.length);
case IO_FILE: /* encrypting file */
String inputFile = data.getString(ENCRYPT_INPUT_FILE);
// check if storage is ready
if (!FileHelper.isStorageMounted(inputFile)) {
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
}
return new InputData(new FileInputStream(inputFile), new File(inputFile).length());
case IO_URI: /* encrypting content uri */
Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI);
// InputStream
InputStream in = getContentResolver().openInputStream(providerUri);
return new InputData(in, 0);
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
case IO_URIS:
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI));
// InputStream
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
default:
throw new PgpGeneralException("No target choosen!");
@@ -722,23 +734,16 @@ public class KeychainIntentService extends IntentService
case IO_BYTES:
return new ByteArrayOutputStream();
case IO_FILE:
String outputFile = data.getString(ENCRYPT_OUTPUT_FILE);
// check if storage is ready
if (!FileHelper.isStorageMounted(outputFile)) {
throw new PgpGeneralException(
getString(R.string.error_external_storage_not_ready));
}
// OutputStream
return new FileOutputStream(outputFile);
case IO_URI:
Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI);
return getContentResolver().openOutputStream(providerUri);
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!");
}
@@ -758,12 +763,9 @@ public class KeychainIntentService extends IntentService
case IO_BYTES:
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
resultData.putByteArray(bytesName, output);
break;
case IO_FILE:
// nothing, file was written, just send okay and verification bundle
break;
case IO_URI:
case IO_URIS:
// nothing, output was written, just send okay and verification bundle
break;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Activity;

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Activity;

View File

@@ -34,9 +34,8 @@ import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.util.LongSparseArray;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.bcpg.S2K;
import org.sufficientlysecure.keychain.Constants;
@@ -396,8 +395,8 @@ public class PassphraseCacheService extends Service {
} else {
// Fallback, since expandable notifications weren't available back then
builder.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys,
mPassphraseCache.size())))
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
mPassphraseCache.size()))
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);

View File

@@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
@@ -6,16 +23,17 @@ import android.os.Parcelable;
import java.io.Serializable;
import java.util.ArrayList;
/** This class is a a transferable representation for a collection of changes
/**
* This class is a a transferable representation for a collection of changes
* to be done on a keyring.
*
* <p/>
* This class should include all types of operations supported in the backend.
*
* <p/>
* All changes are done in a differential manner. Besides the two key
* identification attributes, all attributes may be null, which indicates no
* change to the keyring. This is also the reason why boxed values are used
* instead of primitives in the subclasses.
*
* <p/>
* Application of operations in the backend should be fail-fast, which means an
* error in any included operation (for example revocation of a non-existent
* subkey) will cause the operation as a whole to fail.
@@ -65,12 +83,23 @@ public class SaveKeyringParcel implements Parcelable {
public int mKeysize;
public int mFlags;
public Long mExpiry;
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
mAlgorithm = algorithm;
mKeysize = keysize;
mFlags = flags;
mExpiry = expiry;
}
@Override
public String toString() {
String out = "mAlgorithm: " + mAlgorithm + ", ";
out += "mKeysize: " + mKeysize + ", ";
out += "mFlags: " + mFlags;
out += "mExpiry: " + mExpiry;
return out;
}
}
public static class SubkeyChange implements Serializable {
@@ -78,11 +107,46 @@ public class SaveKeyringParcel implements Parcelable {
public Integer mFlags;
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
public Long mExpiry;
public SubkeyChange(long keyId) {
mKeyId = keyId;
}
public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId;
mFlags = flags;
mExpiry = expiry;
}
@Override
public String toString() {
String out = "mKeyId: " + mKeyId + ", ";
out += "mFlags: " + mFlags + ", ";
out += "mExpiry: " + mExpiry;
return out;
}
}
public SubkeyChange getSubkeyChange(long keyId) {
for (SubkeyChange subkeyChange : mChangeSubKeys) {
if (subkeyChange.mKeyId == keyId) {
return subkeyChange;
}
}
return null;
}
public SubkeyChange getOrCreateSubkeyChange(long keyId) {
SubkeyChange foundSubkeyChange = getSubkeyChange(keyId);
if (foundSubkeyChange != null) {
return foundSubkeyChange;
} else {
// else, create a new one
SubkeyChange newSubkeyChange = new SubkeyChange(keyId);
mChangeSubKeys.add(newSubkeyChange);
return newSubkeyChange;
}
}
public SaveKeyringParcel(Parcel source) {
@@ -104,7 +168,7 @@ public class SaveKeyringParcel implements Parcelable {
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeInt(mMasterKeyId == null ? 0 : 1);
if(mMasterKeyId != null) {
if (mMasterKeyId != null) {
destination.writeLong(mMasterKeyId);
}
destination.writeByteArray(mFingerprint);
@@ -136,4 +200,17 @@ public class SaveKeyringParcel implements Parcelable {
return 0;
}
@Override
public String toString() {
String out = "mMasterKeyId: " + mMasterKeyId + "\n";
out += "mNewPassphrase: " + mNewPassphrase + "\n";
out += "mAddUserIds: " + mAddUserIds + "\n";
out += "mAddSubKeys: " + mAddSubKeys + "\n";
out += "mChangeSubKeys: " + mChangeSubKeys + "\n";
out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n";
out += "mRevokeUserIds: " + mRevokeUserIds + "\n";
out += "mRevokeSubKeys: " + mRevokeSubKeys;
return out;
}
}

View File

@@ -31,8 +31,8 @@ import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.KeyRing;
@@ -169,11 +169,12 @@ public class CreateKeyFinalFragment extends Fragment {
Bundle data = new Bundle();
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.CERTIFY_OTHER, null));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.SIGN_DATA, null));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
String userId = KeyRing.createUserId(mName, mEmail, null);
parcel.mAddUserIds.add(userId);
parcel.mChangePrimaryUserId = userId;
parcel.mNewPassphrase = mPassphrase;
// get selected key entries

View File

@@ -23,11 +23,9 @@ import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.util.Log;
@@ -114,7 +112,7 @@ public class DecryptActivity extends DrawerActivity {
} else {
// Binary via content provider (could also be files)
// override uri to get stream from send
uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
action = ACTION_DECRYPT;
}
} else if (Intent.ACTION_VIEW.equals(action)) {
@@ -122,6 +120,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 +154,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,19 +20,16 @@ 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.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.OpenableColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -40,31 +37,28 @@ 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 +66,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);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
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 +93,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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.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 +149,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 +186,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 +218,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

@@ -25,11 +25,11 @@ import android.os.Message;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Button;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.R;
@@ -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

@@ -35,9 +35,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;

View File

@@ -37,7 +37,6 @@ import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
@@ -57,17 +56,15 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ChangeExpiryDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Date;
public class EditKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
@@ -95,8 +92,8 @@ public class EditKeyFragment extends LoaderFragment implements
private Uri mDataUri;
private SaveKeyringParcel mSaveKeyringParcel;
private String mPrimaryUserId;
private String mPrimaryUserId;
private String mCurrentPassphrase;
/**
@@ -215,8 +212,7 @@ public class EditKeyFragment extends LoaderFragment implements
mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String userId = mUserIdsAdapter.getUserId(position);
editUserId(userId);
editUserId(position);
}
});
@@ -230,8 +226,7 @@ public class EditKeyFragment extends LoaderFragment implements
mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
long keyId = mSubkeysAdapter.getKeyId(position);
editSubkey(keyId);
editSubkey(position);
}
});
@@ -320,7 +315,11 @@ public class EditKeyFragment extends LoaderFragment implements
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
}
private void editUserId(final String userId) {
private void editUserId(final int position) {
final String userId = mUserIdsAdapter.getUserId(position);
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position);
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
@@ -353,20 +352,21 @@ public class EditKeyFragment extends LoaderFragment implements
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
EditUserIdDialogFragment dialogFragment =
EditUserIdDialogFragment.newInstance(messenger);
EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending);
dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog");
}
});
}
private void editSubkey(final long keyId) {
private void editSubkey(final int position) {
final long keyId = mSubkeysAdapter.getKeyId(position);
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY:
editSubkeyExpiry(keyId);
editSubkeyExpiry(position);
break;
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
// toggle
@@ -394,19 +394,19 @@ public class EditKeyFragment extends LoaderFragment implements
});
}
private void editSubkeyExpiry(final long keyId) {
private void editSubkeyExpiry(final int position) {
final long keyId = mSubkeysAdapter.getKeyId(position);
final Long creationDate = mSubkeysAdapter.getCreationDate(position);
final Long expiryDate = mSubkeysAdapter.getExpiryDate(position);
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case ChangeExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
// toggle
// if (mSaveKeyringParcel.changePrimaryUserId != null
// && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
// mSaveKeyringParcel.changePrimaryUserId = null;
// } else {
// mSaveKeyringParcel.changePrimaryUserId = userId;
// }
case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
Long expiry = (Long) message.getData().
getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE);
mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry;
break;
}
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
@@ -418,8 +418,8 @@ public class EditKeyFragment extends LoaderFragment implements
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
ChangeExpiryDialogFragment dialogFragment =
ChangeExpiryDialogFragment.newInstance(messenger, new Date(), new Date());
EditSubkeyExpiryDialogFragment dialogFragment =
EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate);
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog");
}
@@ -453,8 +453,21 @@ public class EditKeyFragment extends LoaderFragment implements
}
private void addSubkey() {
// default values
mSubkeysAddedAdapter.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
boolean willBeMasterKey = mSubkeysAdapter.getCount() == 0
&& mSubkeysAddedAdapter.getCount() == 0;
AddSubkeyDialogFragment addSubkeyDialogFragment =
AddSubkeyDialogFragment.newInstance(willBeMasterKey);
addSubkeyDialogFragment
.setOnAlgorithmSelectedListener(
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {
@Override
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) {
mSubkeysAddedAdapter.add(newSubkey);
}
}
);
addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog");
}
private void cachePassphraseForEdit() {
@@ -479,9 +492,7 @@ public class EditKeyFragment extends LoaderFragment implements
}
private void save(String passphrase) {
Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds);
Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(

View File

@@ -18,23 +18,46 @@
package org.sufficientlysecure.keychain.ui;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.LabeledIntent;
import android.content.pm.ResolveInfo;
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.os.Parcelable;
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.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class EncryptActivity extends DrawerActivity implements EncryptActivityInterface {
/* Intents */
public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT";
@@ -51,7 +74,7 @@ public class EncryptActivity extends DrawerActivity implements
// view
ViewPager mViewPagerMode;
PagerTabStrip mPagerTabStripMode;
//PagerTabStrip mPagerTabStripMode;
PagerTabStripAdapter mTabsAdapterMode;
ViewPager mViewPagerContent;
PagerTabStrip mPagerTabStripContent;
@@ -72,37 +95,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 +129,352 @@ 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) {
if (!isContentMessage()) {
Notify.showNotify(EncryptActivity.this, R.string.encrypt_sign_successful, Notify.Style.INFO);
if (mDeleteAfterEncrypt) {
for (Uri inputUri : mInputUris) {
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri);
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
}
mInputUris.clear();
notifyUpdate();
}
}
if (mShareAfterEncrypt) {
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt(message));
} 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)));
}
/**
* Create Intent Chooser but exclude OK's EncryptActivity.
* <p/>
* Put together from some stackoverflow posts...
*
* @param message
* @return
*/
private Intent sendWithChooserExcludingEncrypt(Message message) {
Intent prototype = createSendIntent(message);
String title = isContentMessage() ? getString(R.string.title_share_message)
: getString(R.string.title_share_file);
// fallback on Android 2.3, otherwise we would get weird results
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return Intent.createChooser(prototype, title);
}
// prevent recursion aka Inception :P
String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"};
List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0);
List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>();
if (!resInfoList.isEmpty()) {
for (ResolveInfo resolveInfo : resInfoList) {
// do not add blacklisted ones
if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name))
continue;
resInfoListFiltered.add(resolveInfo);
}
if (!resInfoListFiltered.isEmpty()) {
// sorting for nice readability
Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
@Override
public int compare(ResolveInfo first, ResolveInfo second) {
String firstName = first.loadLabel(getPackageManager()).toString();
String secondName = second.loadLabel(getPackageManager()).toString();
return firstName.compareToIgnoreCase(secondName);
}
});
// create the custom intent list
for (ResolveInfo resolveInfo : resInfoListFiltered) {
Intent targetedShareIntent = (Intent) prototype.clone();
targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
resolveInfo.activityInfo.packageName,
resolveInfo.loadLabel(getPackageManager()),
resolveInfo.activityInfo.icon);
targetedShareIntents.add(lIntent);
}
// Create chooser with only one Intent in it
Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
// append all other Intents
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
return chooserIntent;
}
}
// fallback to Android's default chooser
return Intent.createChooser(prototype, title);
}
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 +511,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,17 +559,21 @@ 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
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to APG Encrypt via share menu
// When sending to OpenKeychain Encrypt via share menu
if ("text/plain".equals(type)) {
// Plain text
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
@@ -201,14 +586,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 +620,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,21 @@ 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);
mEncryptKeyView.setThreshold(1); // Start working from first character
return view;
}
@@ -135,6 +131,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 +216,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,50 @@
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.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.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
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 +83,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 +112,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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.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 +242,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,16 @@
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 +57,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 +96,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 +121,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

@@ -40,7 +40,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.util.FileImportCache;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
@@ -49,6 +48,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
import org.sufficientlysecure.keychain.util.FileImportCache;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;

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

@@ -94,22 +94,22 @@ public class KeyListActivity extends DrawerActivity {
case R.id.menu_key_list_debug_read:
try {
KeychainDatabase.debugRead(this);
Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO);
KeychainDatabase.debugBackup(this, true);
Notify.showNotify(this, "Restored debug_backup.db", Notify.Style.INFO);
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
} catch (IOException e) {
Log.e(Constants.TAG, "IO Error", e);
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
Notify.showNotify(this, "IO Error " + e.getMessage(), Notify.Style.ERROR);
}
return true;
case R.id.menu_key_list_debug_write:
try {
KeychainDatabase.debugWrite(this);
Notify.showNotify(this, "Backup Notify.Style", Notify.Style.INFO);
KeychainDatabase.debugBackup(this, false);
Notify.showNotify(this, "Backup to debug_backup.db completed", Notify.Style.INFO);
} catch(IOException e) {
Log.e(Constants.TAG, "IO Error", e);
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
Notify.showNotify(this, "IO Error: " + e.getMessage(), Notify.Style.ERROR);
}
return true;

View File

@@ -47,10 +47,10 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Button;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -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

@@ -41,9 +41,7 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryPar
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class LogDisplayFragment extends ListFragment implements OnTouchListener {

View File

@@ -27,6 +27,7 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants;
@@ -88,10 +89,10 @@ public class PreferencesActivity extends PreferenceActivity {
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
int[] valueIds = new int[]{
Constants.choice.compression.none,
Constants.choice.compression.zip,
Constants.choice.compression.zlib,
Constants.choice.compression.bzip2,
CompressionAlgorithmTags.UNCOMPRESSED,
CompressionAlgorithmTags.ZIP,
CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.BZIP2,
};
String[] entries = new String[]{
getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
@@ -229,10 +230,10 @@ public class PreferencesActivity extends PreferenceActivity {
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
int[] valueIds = new int[]{
Constants.choice.compression.none,
Constants.choice.compression.zip,
Constants.choice.compression.zlib,
Constants.choice.compression.bzip2,
CompressionAlgorithmTags.UNCOMPRESSED,
CompressionAlgorithmTags.ZIP,
CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.BZIP2,
};
String[] entries = new String[]{

View File

@@ -34,8 +34,8 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;

View File

@@ -42,7 +42,6 @@ import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -57,9 +56,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.Date;
@@ -103,7 +102,6 @@ public class ViewKeyActivity extends ActionBarActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this);
@@ -296,8 +294,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

@@ -150,7 +150,7 @@ public class ViewKeyCertsFragment extends LoaderFragment
Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class);
viewIntent.setData(Certs.buildCertsSpecificUri(
Long.toString(masterKeyId), Long.toString(rank), Long.toString(certifierId)));
masterKeyId, rank, certifierId));
startActivity(viewIntent);
}
}

View File

@@ -49,7 +49,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;

View File

@@ -111,7 +111,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
holder.keyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.fingerprint = (TextView) convertView.findViewById(R.id.view_key_fingerprint);
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
holder.status = (TextView) convertView.findViewById(R.id.status);

View File

@@ -33,7 +33,6 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ImportKeysListLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {

View File

@@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBarActivity;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants;
import java.util.ArrayList;

View File

@@ -152,7 +152,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
holder.view = view;
holder.mainUserId = (TextView) view.findViewById(R.id.mainUserId);
holder.mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
holder.keyId = (TextView) view.findViewById(R.id.keyId);
holder.keyId = (TextView) view.findViewById(R.id.subkey_item_key_id);
holder.status = (TextView) view.findViewById(R.id.status);
holder.selected = (CheckBox) view.findViewById(R.id.selected);
view.setTag(holder);

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.Typeface;
import android.support.v4.widget.CursorAdapter;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
@@ -89,6 +90,20 @@ public class SubkeysAdapter extends CursorAdapter {
return mCursor.getLong(INDEX_KEY_ID);
}
public long getCreationDate(int position) {
mCursor.moveToPosition(position);
return mCursor.getLong(INDEX_CREATION);
}
public Long getExpiryDate(int position) {
mCursor.moveToPosition(position);
if (mCursor.isNull(INDEX_EXPIRY)) {
return null;
} else {
return mCursor.getLong(INDEX_EXPIRY);
}
}
@Override
public Cursor swapCursor(Cursor newCursor) {
hasAnySecret = false;
@@ -106,15 +121,18 @@ public class SubkeysAdapter extends CursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView vKeyId = (TextView) view.findViewById(R.id.keyId);
TextView vKeyDetails = (TextView) view.findViewById(R.id.keyDetails);
TextView vKeyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
ImageView vMasterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
ImageView vSignIcon = (ImageView) view.findViewById(R.id.ic_signKey);
ImageView vRevokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_item_edit_image);
TextView vKeyId = (TextView) view.findViewById(R.id.subkey_item_key_id);
TextView vKeyDetails = (TextView) view.findViewById(R.id.subkey_item_details);
TextView vKeyExpiry = (TextView) view.findViewById(R.id.subkey_item_expiry);
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_certify);
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_encrypt);
ImageView vSignIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_sign);
ImageView vRevokedIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_revoked);
ImageView vEditImage = (ImageView) view.findViewById(R.id.subkey_item_edit_image);
// not used:
ImageView deleteImage = (ImageView) view.findViewById(R.id.subkey_item_delete_button);
deleteImage.setVisibility(View.GONE);
long keyId = cursor.getLong(INDEX_KEY_ID);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
@@ -133,14 +151,26 @@ public class SubkeysAdapter extends CursorAdapter {
vKeyDetails.setText(algorithmStr);
}
boolean isMasterKey = cursor.getInt(INDEX_RANK) == 0;
if (isMasterKey) {
vKeyId.setTypeface(null, Typeface.BOLD);
} else {
vKeyId.setTypeface(null, Typeface.NORMAL);
}
// Set icons according to properties
vMasterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
// TODO: missing icon for authenticate
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
Date expiryDate = null;
if (!cursor.isNull(INDEX_EXPIRY)) {
expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
}
// for edit key
if (mSaveKeyringParcel != null) {
boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
@@ -151,24 +181,22 @@ public class SubkeysAdapter extends CursorAdapter {
}
}
SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId);
if (subkeyChange != null) {
if (subkeyChange.mExpiry == null) {
expiryDate = null;
} else {
expiryDate = new Date(subkeyChange.mExpiry * 1000);
}
}
vEditImage.setVisibility(View.VISIBLE);
} else {
vEditImage.setVisibility(View.GONE);
}
if (isRevoked) {
vRevokedKeyIcon.setVisibility(View.VISIBLE);
} else {
vKeyId.setTextColor(mDefaultTextColor);
vKeyDetails.setTextColor(mDefaultTextColor);
vKeyExpiry.setTextColor(mDefaultTextColor);
vRevokedKeyIcon.setVisibility(View.GONE);
}
boolean isExpired;
if (!cursor.isNull(INDEX_EXPIRY)) {
Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
if (expiryDate != null) {
isExpired = expiryDate.before(new Date());
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
@@ -179,6 +207,16 @@ public class SubkeysAdapter extends CursorAdapter {
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " + context.getString(R.string.none));
}
if (isRevoked) {
vRevokedIcon.setVisibility(View.VISIBLE);
} else {
vKeyId.setTextColor(mDefaultTextColor);
vKeyDetails.setTextColor(mDefaultTextColor);
vKeyExpiry.setTextColor(mDefaultTextColor);
vRevokedIcon.setVisibility(View.GONE);
}
// if key is expired or revoked, strike through text
boolean isInvalid = isRevoked || isExpired;
if (isInvalid) {
@@ -195,7 +233,7 @@ public class SubkeysAdapter extends CursorAdapter {
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
if (mDefaultTextColor == null) {
TextView keyId = (TextView) view.findViewById(R.id.keyId);
TextView keyId = (TextView) view.findViewById(R.id.subkey_item_key_id);
mDefaultTextColor = keyId.getTextColors();
}
return view;

View File

@@ -17,48 +17,29 @@
package org.sufficientlysecure.keychain.ui.adapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
private LayoutInflater mInflater;
private Activity mActivity;
public interface OnAlgorithmSelectedListener {
public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
}
// hold a private reference to the underlying data List
private List<SaveKeyringParcel.SubkeyAdd> mData;
@@ -70,12 +51,12 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
}
static class ViewHolder {
public OnAlgorithmSelectedListener mAlgorithmSelectedListener;
public Spinner mAlgorithmSpinner;
public Spinner mKeySizeSpinner;
public TextView mCustomKeyTextView;
public EditText mCustomKeyEditText;
public TextView mCustomKeyInfoTextView;
public TextView vKeyId;
public TextView vKeyDetails;
public TextView vKeyExpiry;
public ImageView vCertifyIcon;
public ImageView vEncryptIcon;
public ImageView vSignIcon;
public ImageButton vDelete;
// also hold a reference to the model item
public SaveKeyringParcel.SubkeyAdd mModel;
@@ -84,44 +65,25 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.edit_key_subkey_added_item, null);
convertView = mInflater.inflate(R.layout.view_key_subkey_item, null);
final ViewHolder holder = new ViewHolder();
holder.mAlgorithmSpinner = (Spinner) convertView.findViewById(R.id.create_key_algorithm);
holder.mKeySizeSpinner = (Spinner) convertView.findViewById(R.id.create_key_size);
holder.mCustomKeyTextView = (TextView) convertView.findViewById(R.id.custom_key_size_label);
holder.mCustomKeyEditText = (EditText) convertView.findViewById(R.id.custom_key_size_input);
holder.mCustomKeyInfoTextView = (TextView) convertView.findViewById(R.id.custom_key_size_info);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_added_item_delete);
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
holder.vKeyExpiry = (TextView) convertView.findViewById(R.id.subkey_item_expiry);
holder.vCertifyIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_certify);
holder.vEncryptIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_encrypt);
holder.vSignIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_sign);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_item_delete_button);
holder.vDelete.setVisibility(View.VISIBLE); // always visible
// not used:
ImageView editImage = (ImageView) convertView.findViewById(R.id.subkey_item_edit_image);
editImage.setVisibility(View.GONE);
ImageView revokedIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_revoked);
revokedIcon.setVisibility(View.GONE);
convertView.setTag(holder);
holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem();
// update referenced model item
holder.mModel.mAlgorithm = newKeyAlgorithmChoice.getId();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem();
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(),
getSelectedKeyLength(holder.mKeySizeSpinner, holder.mCustomKeyEditText));
// update referenced model item
holder.mModel.mKeysize = newKeySize;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.vDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -136,226 +98,44 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
// save reference to model item
holder.mModel = getItem(position);
// TODO
boolean wouldBeMasterKey = false;
// boolean wouldBeMasterKey = (childCount == 0);
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
mActivity,
holder.mModel.mAlgorithm,
holder.mModel.mKeysize
);
holder.vKeyId.setText(R.string.edit_key_new_subkey);
holder.vKeyDetails.setText(algorithmStr);
ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(Constants.choice.algorithm.dsa, mActivity.getResources().getString(
R.string.dsa)));
if (!wouldBeMasterKey) {
choices.add(new Choice(Constants.choice.algorithm.elgamal, mActivity.getResources().getString(
R.string.elgamal)));
if (holder.mModel.mExpiry != null) {
Date expiryDate = new Date(holder.mModel.mExpiry * 1000);
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
+ DateFormat.getDateFormat(getContext()).format(expiryDate));
} else {
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
+ getContext().getString(R.string.none));
}
choices.add(new Choice(Constants.choice.algorithm.rsa, mActivity.getResources().getString(
R.string.rsa)));
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(mActivity,
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.mAlgorithmSpinner.setAdapter(adapter);
// make RSA the default
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Constants.choice.algorithm.rsa) {
holder.mAlgorithmSpinner.setSelection(i);
break;
}
int flags = holder.mModel.mFlags;
if ((flags & KeyFlags.CERTIFY_OTHER) > 0) {
holder.vCertifyIcon.setVisibility(View.VISIBLE);
} else {
holder.vCertifyIcon.setVisibility(View.GONE);
}
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(mActivity, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(Arrays.asList(mActivity.getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.mKeySizeSpinner.setAdapter(keySizeAdapter);
holder.mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
holder.mCustomKeyEditText.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) {
// setOkButtonAvailability(alertDialog);
}
});
holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText,
holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView);
// setOkButtonAvailability(alertDialog);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId(),
holder.mKeySizeSpinner, holder.mCustomKeyInfoTextView);
setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText,
holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView);
// setOkButtonAvailability(alertDialog);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
//
// holder.vAddress.setText(holder.mModel.address);
// holder.vAddress.setThreshold(1); // Start working from first character
// holder.vAddress.setAdapter(mAutoCompleteEmailAdapter);
//
// holder.vName.setText(holder.mModel.name);
// holder.vName.setThreshold(1); // Start working from first character
// holder.vName.setAdapter(mAutoCompleteNameAdapter);
//
// holder.vComment.setText(holder.mModel.comment);
if ((flags & KeyFlags.SIGN_DATA) > 0) {
holder.vSignIcon.setVisibility(View.VISIBLE);
} else {
holder.vSignIcon.setVisibility(View.GONE);
}
if (((flags & KeyFlags.ENCRYPT_COMMS) > 0)
|| ((flags & KeyFlags.ENCRYPT_STORAGE) > 0)) {
holder.vEncryptIcon.setVisibility(View.VISIBLE);
} else {
holder.vEncryptIcon.setVisibility(View.GONE);
}
// TODO: missing icon for authenticate
return convertView;
}
private int getSelectedKeyLength(Spinner keySizeSpinner, EditText customKeyEditText) {
final String selectedItemString = (String) keySizeSpinner.getSelectedItem();
final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
String keyLengthString = customSelected ? customKeyEditText.getText().toString() : selectedItemString;
int keySize;
try {
keySize = Integer.parseInt(keyLengthString);
} catch (NumberFormatException e) {
keySize = 0;
}
return keySize;
}
/**
* <h3>RSA</h3>
* <p>for RSA algorithm, key length must be greater than 1024 (according to
* <a href="https://github.com/open-keychain/open-keychain/issues/102">#102</a>). Possibility to generate keys bigger
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
* multiplicity of 8.</p>
* <h3>ElGamal</h3>
* <p>For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.</p>
* <h3>DSA</h3>
* <p>For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.</p>
*
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
* inappropriate.
*/
private int getProperKeyLength(int algorithmId, int currentKeyLength) {
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
int properKeyLength = -1;
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
if (currentKeyLength > 1024 && currentKeyLength <= 8192) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
}
break;
case Constants.choice.algorithm.elgamal:
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
}
int minimalValue = Integer.MAX_VALUE;
int minimalIndex = -1;
for (int i = 0; i < elGammalKeyDiff.length; i++) {
if (elGammalKeyDiff[i] <= minimalValue) {
minimalValue = elGammalKeyDiff[i];
minimalIndex = i;
}
}
properKeyLength = elGamalSupportedLengths[minimalIndex];
break;
case Constants.choice.algorithm.dsa:
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
}
break;
}
return properKeyLength;
}
// TODO: make this an error message on the field
// private boolean setOkButtonAvailability(AlertDialog alertDialog) {
// final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem();
// final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem());
// final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize);
// alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0);
// }
private void setCustomKeyVisibility(Spinner keySizeSpinner, EditText customkeyedittext, TextView customKeyTextView, TextView customKeyInfoTextView) {
final String selectedItemString = (String) keySizeSpinner.getSelectedItem();
final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom);
final boolean customSelected = customLengthString.equals(selectedItemString);
final int visibility = customSelected ? View.VISIBLE : View.GONE;
customkeyedittext.setVisibility(visibility);
customKeyTextView.setVisibility(visibility);
customKeyInfoTextView.setVisibility(visibility);
// hide keyboard after setting visibility to gone
if (visibility == View.GONE) {
InputMethodManager imm = (InputMethodManager)
mActivity.getSystemService(mActivity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(customkeyedittext.getWindowToken(), 0);
}
}
private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId, Spinner keySizeSpinner, TextView customKeyInfoTextView) {
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) keySizeSpinner.getAdapter();
final Object selectedItem = keySizeSpinner.getSelectedItem();
keySizeAdapter.clear();
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_rsa));
break;
case Constants.choice.algorithm.elgamal:
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
customKeyInfoTextView.setText(""); // ElGamal does not support custom key length
break;
case Constants.choice.algorithm.dsa:
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_dsa));
break;
}
keySizeAdapter.notifyDataSetChanged();
// when switching algorithm, try to select same key length as before
for (int i = 0; i < keySizeAdapter.getCount(); i++) {
if (selectedItem.equals(keySizeAdapter.getItem(i))) {
keySizeSpinner.setSelection(i);
break;
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) {
final String[] spinnerValuesStringArray = mActivity.getResources().getStringArray(stringArrayResourceId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
arrayAdapter.addAll(spinnerValuesStringArray);
} else {
for (final String value : spinnerValuesStringArray) {
arrayAdapter.add(value);
}
}
}
}

View File

@@ -252,6 +252,26 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
return mCursor.getString(INDEX_USER_ID);
}
public boolean getIsRevoked(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_IS_REVOKED) > 0;
}
public boolean getIsRevokedPending(int position) {
mCursor.moveToPosition(position);
String userId = mCursor.getString(INDEX_USER_ID);
boolean isRevokedPending = false;
if (mSaveKeyringParcel != null) {
if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) {
isRevokedPending = true;
}
}
return isRevokedPending;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);

View File

@@ -27,46 +27,64 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TableRow;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class CreateKeyDialogFragment extends DialogFragment {
public class AddSubkeyDialogFragment extends DialogFragment {
public interface OnAlgorithmSelectedListener {
public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey);
}
private static final String ARG_EDITOR_CHILD_COUNT = "child_count";
private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key";
private OnAlgorithmSelectedListener mAlgorithmSelectedListener;
private CheckBox mNoExpiryCheckBox;
private TableRow mExpiryRow;
private DatePicker mExpiryDatePicker;
private Spinner mAlgorithmSpinner;
private Spinner mKeySizeSpinner;
private TextView mCustomKeyTextView;
private EditText mCustomKeyEditText;
private TextView mCustomKeyInfoTextView;
private CheckBox mFlagCertify;
private CheckBox mFlagSign;
private CheckBox mFlagEncrypt;
private CheckBox mFlagAuthenticate;
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
mAlgorithmSelectedListener = listener;
}
public static CreateKeyDialogFragment newInstance(int mEditorChildCount) {
CreateKeyDialogFragment frag = new CreateKeyDialogFragment();
public static AddSubkeyDialogFragment newInstance(boolean willBeMasterKey) {
AddSubkeyDialogFragment frag = new AddSubkeyDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_EDITOR_CHILD_COUNT, mEditorChildCount);
args.putBoolean(ARG_WILL_BE_MASTER_KEY, willBeMasterKey);
frag.setArguments(args);
@@ -78,42 +96,64 @@ public class CreateKeyDialogFragment extends DialogFragment {
final FragmentActivity context = getActivity();
final LayoutInflater mInflater;
final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT);
final boolean willBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY);
mInflater = context.getLayoutInflater();
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
View view = mInflater.inflate(R.layout.create_key_dialog, null);
View view = mInflater.inflate(R.layout.add_subkey_dialog, null);
dialog.setView(view);
dialog.setTitle(R.string.title_create_key);
dialog.setTitle(R.string.title_add_subkey);
boolean wouldBeMasterKey = (childCount == 0);
mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry);
mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row);
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm);
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size);
mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label);
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info);
mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify);
mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign);
mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt);
mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate);
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.create_key_algorithm);
ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(Constants.choice.algorithm.dsa, getResources().getString(
R.string.dsa)));
if (!wouldBeMasterKey) {
choices.add(new Choice(Constants.choice.algorithm.elgamal, getResources().getString(
R.string.elgamal)));
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mExpiryRow.setVisibility(View.GONE);
} else {
mExpiryRow.setVisibility(View.VISIBLE);
}
}
});
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
mExpiryDatePicker.setMinDate(new Date().getTime() + DateUtils.DAY_IN_MILLIS);
}
choices.add(new Choice(Constants.choice.algorithm.rsa, getResources().getString(
ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(PublicKeyAlgorithmTags.DSA, getResources().getString(
R.string.dsa)));
if (!willBeMasterKey) {
choices.add(new Choice(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, getResources().getString(
R.string.elgamal)));
}
choices.add(new Choice(PublicKeyAlgorithmTags.RSA_GENERAL, getResources().getString(
R.string.rsa)));
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context,
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAlgorithmSpinner.setAdapter(adapter);
// make RSA the default
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == Constants.choice.algorithm.rsa) {
if (choices.get(i).getId() == PublicKeyAlgorithmTags.RSA_GENERAL) {
mAlgorithmSpinner.setSelection(i);
break;
}
}
mKeySizeSpinner = (Spinner) view.findViewById(R.id.create_key_size);
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(context, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
@@ -121,9 +161,6 @@ public class CreateKeyDialogFragment extends DialogFragment {
mKeySizeSpinner.setAdapter(keySizeAdapter);
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
mCustomKeyTextView = (TextView) view.findViewById(R.id.custom_key_size_label);
mCustomKeyEditText = (EditText) view.findViewById(R.id.custom_key_size_input);
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.custom_key_size_info);
dialog.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@@ -131,7 +168,39 @@ public class CreateKeyDialogFragment extends DialogFragment {
di.dismiss();
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength());
mAlgorithmSelectedListener.onAlgorithmSelected(newKeyAlgorithmChoice, newKeySize);
int flags = 0;
if (mFlagCertify.isChecked()) {
flags |= KeyFlags.CERTIFY_OTHER;
}
if (mFlagSign.isChecked()) {
flags |= KeyFlags.SIGN_DATA;
}
if (mFlagEncrypt.isChecked()) {
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
if (mFlagAuthenticate.isChecked()) {
flags |= KeyFlags.AUTHENTICATION;
}
Long expiry;
if (mNoExpiryCheckBox.isChecked()) {
expiry = null;
} else {
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
//noinspection ResourceType
selectedCal.set(mExpiryDatePicker.getYear(),
mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth());
expiry = selectedCal.getTime().getTime() / 1000;
}
SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd(
newKeyAlgorithmChoice.getId(),
newKeySize,
flags,
expiry
);
mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey);
}
}
);
@@ -142,7 +211,8 @@ public class CreateKeyDialogFragment extends DialogFragment {
public void onClick(DialogInterface di, int id) {
di.dismiss();
}
});
}
);
final AlertDialog alertDialog = dialog.show();
@@ -224,12 +294,12 @@ public class CreateKeyDialogFragment extends DialogFragment {
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
int properKeyLength = -1;
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
if (currentKeyLength > 1024 && currentKeyLength <= 8192) {
case PublicKeyAlgorithmTags.RSA_GENERAL:
if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
}
break;
case Constants.choice.algorithm.elgamal:
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
@@ -244,7 +314,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
}
properKeyLength = elGamalSupportedLengths[minimalIndex];
break;
case Constants.choice.algorithm.dsa:
case PublicKeyAlgorithmTags.DSA:
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
}
@@ -283,15 +353,15 @@ public class CreateKeyDialogFragment extends DialogFragment {
final Object selectedItem = mKeySizeSpinner.getSelectedItem();
keySizeAdapter.clear();
switch (algorithmId) {
case Constants.choice.algorithm.rsa:
case PublicKeyAlgorithmTags.RSA_GENERAL:
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa));
break;
case Constants.choice.algorithm.elgamal:
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length
break;
case Constants.choice.algorithm.dsa:
case PublicKeyAlgorithmTags.DSA:
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa));
break;

View File

@@ -29,7 +29,6 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.KeyEvent;
@@ -149,6 +148,14 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA
}
});
mName.setThreshold(1); // Start working from first character
mName.setAdapter(
new ArrayAdapter<String>
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserNames(getActivity())
)
);
alert.setNegativeButton(android.R.string.cancel, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {

View File

@@ -1,187 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.text.format.DateUtils;
import android.widget.DatePicker;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
public class ChangeExpiryDialogFragment extends DialogFragment {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_CREATION_DATE = "creation_date";
private static final String ARG_EXPIRY_DATE = "expiry_date";
public static final int MESSAGE_NEW_EXPIRY_DATE = 1;
public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date";
private Messenger mMessenger;
private Calendar mCreationCal;
private Calendar mExpiryCal;
private int mDatePickerResultCount = 0;
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
new DatePickerDialog.OnDateSetListener() {
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
// Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) {
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
selectedCal.set(year, monthOfYear, dayOfMonth);
if (mExpiryCal != null) {
long numDays = (selectedCal.getTimeInMillis() / 86400000)
- (mExpiryCal.getTimeInMillis() / 86400000);
if (numDays > 0) {
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
} else {
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
}
}
};
public class ExpiryDatePickerDialog extends DatePickerDialog {
public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack,
int year, int monthOfYear, int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
}
// set permanent title
public void setTitle(CharSequence title) {
super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
}
}
/**
* Creates new instance of this dialog fragment
*/
public static ChangeExpiryDialogFragment newInstance(Messenger messenger,
Date creationDate, Date expiryDate) {
ChangeExpiryDialogFragment frag = new ChangeExpiryDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
args.putSerializable(ARG_CREATION_DATE, creationDate);
args.putSerializable(ARG_EXPIRY_DATE, expiryDate);
frag.setArguments(args);
return frag;
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
Date creationDate = (Date) getArguments().getSerializable(ARG_CREATION_DATE);
Date expiryDate = (Date) getArguments().getSerializable(ARG_EXPIRY_DATE);
mCreationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
mCreationCal.setTime(creationDate);
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
mExpiryCal.setTime(expiryDate);
/*
* Using custom DatePickerDialog which overrides the setTitle because
* the DatePickerDialog title is buggy (unix warparound bug).
* See: https://code.google.com/p/android/issues/detail?id=49066
*/
DatePickerDialog dialog = new ExpiryDatePickerDialog(getActivity(),
mExpiryDateSetListener, mExpiryCal.get(Calendar.YEAR), mExpiryCal.get(Calendar.MONTH),
mExpiryCal.get(Calendar.DAY_OF_MONTH));
mDatePickerResultCount = 0;
dialog.setCancelable(true);
dialog.setButton(Dialog.BUTTON_NEGATIVE,
getActivity().getString(R.string.btn_no_date),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) {
// none expiry dates corresponds to a null message
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null);
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
}
}
);
// setCalendarViewShown() is supported from API 11 onwards.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// Hide calendarView in tablets because of the unix warparound bug.
dialog.getDatePicker().setCalendarViewShown(false);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// will crash with IllegalArgumentException if we set a min date
// that is not before expiry
if (mCreationCal != null && mCreationCal.before(mExpiryCal)) {
dialog.getDatePicker().setMinDate(mCreationCal.getTime().getTime()
+ DateUtils.DAY_IN_MILLIS);
} else {
// When created date isn't available
dialog.getDatePicker().setMinDate(mExpiryCal.getTime().getTime()
+ DateUtils.DAY_IN_MILLIS);
}
}
return dialog;
}
/**
* Send message back to handler which is initialized in a activity
*
* @param what Message integer you want to send
*/
private void sendMessageToHandler(Integer what, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
}

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.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.provider.DocumentsContract;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
import org.sufficientlysecure.keychain.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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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

@@ -123,7 +123,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
boolean success = false;
for (long masterKeyId : masterKeyIds) {
int count = activity.getContentResolver().delete(
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null
);
success = count > 0;
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class EditSubkeyExpiryDialogFragment extends DialogFragment {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_CREATION_DATE = "creation_date";
private static final String ARG_EXPIRY_DATE = "expiry_date";
public static final int MESSAGE_NEW_EXPIRY_DATE = 1;
public static final int MESSAGE_CANCEL = 2;
public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date";
private Messenger mMessenger;
private Calendar mExpiryCal;
private DatePicker mDatePicker;
/**
* Creates new instance of this dialog fragment
*/
public static EditSubkeyExpiryDialogFragment newInstance(Messenger messenger,
Long creationDate, Long expiryDate) {
EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
args.putSerializable(ARG_CREATION_DATE, creationDate);
args.putSerializable(ARG_EXPIRY_DATE, expiryDate);
frag.setArguments(args);
return frag;
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
Date creationDate = new Date(getArguments().getLong(ARG_CREATION_DATE) * 1000);
Date expiryDate = new Date(getArguments().getLong(ARG_EXPIRY_DATE) * 1000);
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationCal.setTime(creationDate);
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
mExpiryCal.setTime(expiryDate);
Log.d(Constants.TAG, "onCreateDialog");
// Explicitly not using DatePickerDialog here!
// DatePickerDialog is difficult to customize and has many problems (see old git versions)
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(R.string.expiry_date_dialog_title);
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.edit_subkey_expiry_dialog, null);
alert.setView(view);
mDatePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// will crash with IllegalArgumentException if we set a min date
// that is not before expiry
if (creationCal.before(mExpiryCal)) {
mDatePicker.setMinDate(creationCal.getTime().getTime()
+ DateUtils.DAY_IN_MILLIS);
} else {
// when creation date isn't available
mDatePicker.setMinDate(mExpiryCal.getTime().getTime()
+ DateUtils.DAY_IN_MILLIS);
}
}
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
//noinspection ResourceType
selectedCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
if (mExpiryCal != null) {
long numDays = (selectedCal.getTimeInMillis() / 86400000)
- (mExpiryCal.getTimeInMillis() / 86400000);
if (numDays > 0) {
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000);
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
} else {
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000);
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
}
});
alert.setNeutralButton(R.string.btn_no_date, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
Bundle data = new Bundle();
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null);
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
}
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
}
});
return alert.show();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
dismiss();
sendMessageToHandler(MESSAGE_CANCEL, null);
}
/**
* Send message back to handler which is initialized in a activity
*
* @param what Message integer you want to send
*/
private void sendMessageToHandler(Integer what, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
}

View File

@@ -32,6 +32,9 @@ import org.sufficientlysecure.keychain.util.Log;
public class EditUserIdDialogFragment extends DialogFragment {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_IS_REVOKED = "is_revoked";
private static final String ARG_IS_REVOKED_PENDING = "is_revoked_pending";
public static final int MESSAGE_CHANGE_PRIMARY_USER_ID = 1;
public static final int MESSAGE_REVOKE = 2;
@@ -40,10 +43,13 @@ public class EditUserIdDialogFragment extends DialogFragment {
/**
* Creates new instance of this dialog fragment
*/
public static EditUserIdDialogFragment newInstance(Messenger messenger) {
public static EditUserIdDialogFragment newInstance(Messenger messenger, boolean isRevoked,
boolean isRevokedPending) {
EditUserIdDialogFragment frag = new EditUserIdDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
args.putBoolean(ARG_IS_REVOKED, isRevoked);
args.putBoolean(ARG_IS_REVOKED_PENDING, isRevokedPending);
frag.setArguments(args);
@@ -56,27 +62,49 @@ public class EditUserIdDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
boolean isRevoked = getArguments().getBoolean(ARG_IS_REVOKED);
boolean isRevokedPending = getArguments().getBoolean(ARG_IS_REVOKED_PENDING);
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity());
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id);
builder.setTitle(R.string.edit_key_edit_user_id_title);
builder.setItems(array, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null);
break;
case 1:
sendMessageToHandler(MESSAGE_REVOKE, null);
break;
default:
break;
if (isRevokedPending) {
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id_revert_revocation);
builder.setItems(array, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
sendMessageToHandler(MESSAGE_REVOKE, null);
break;
default:
break;
}
}
}
});
});
} else if (isRevoked) {
builder.setMessage(R.string.edit_key_edit_user_id_revoked);
} else {
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id);
builder.setItems(array, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null);
break;
case 1:
sendMessageToHandler(MESSAGE_REVOKE, null);
break;
default:
break;
}
}
});
}
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {

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;
@@ -42,7 +39,13 @@ 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 +55,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 +65,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 +73,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 +99,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 +117,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 +147,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 +180,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

@@ -31,7 +31,6 @@ import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;

View File

@@ -0,0 +1,271 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
import java.util.Locale;
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(Locale.ENGLISH);
return obj.getUserId().toLowerCase(Locale.ENGLISH).contains(m) ||
obj.getKeyIdHex().contains(m) ||
obj.getKeyIdHexShort().startsWith(m);
}
}
}

View File

@@ -24,9 +24,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ImageButton;
import org.sufficientlysecure.keychain.R;

View File

@@ -1,380 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.annotation.TargetApi;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Button;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
private UncachedSecretKey mKey;
private EditorListener mEditorListener = null;
private boolean mIsMasterKey;
ImageButton mDeleteButton;
TextView mAlgorithm;
TextView mKeyId;
TextView mCreationDate;
Button mExpiryDateButton;
Calendar mCreatedDate;
Calendar mExpiryDate;
Calendar mOriginalExpiryDate = null;
CheckBox mChkCertify;
CheckBox mChkSign;
CheckBox mChkEncrypt;
CheckBox mChkAuthenticate;
int mUsage;
int mOriginalUsage;
boolean mIsNewKey;
private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
};
private int mDatePickerResultCount = 0;
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
new DatePickerDialog.OnDateSetListener() {
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
// Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) {
GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
date.set(year, monthOfYear, dayOfMonth);
if (mOriginalExpiryDate != null) {
long numDays = (date.getTimeInMillis() / 86400000) -
(mOriginalExpiryDate.getTimeInMillis() / 86400000);
if (numDays == 0) {
setExpiryDate(mOriginalExpiryDate);
} else {
setExpiryDate(date);
}
} else {
setExpiryDate(date);
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
};
public KeyEditor(Context context) {
super(context);
}
public KeyEditor(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
setDrawingCacheEnabled(true);
setAlwaysDrawnWithCacheEnabled(true);
mAlgorithm = (TextView) findViewById(R.id.algorithm);
mKeyId = (TextView) findViewById(R.id.keyId);
mCreationDate = (TextView) findViewById(R.id.creation);
mExpiryDateButton = (Button) findViewById(R.id.expiry);
mDeleteButton = (ImageButton) findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this);
mChkCertify = (CheckBox) findViewById(R.id.chkCertify);
mChkCertify.setOnCheckedChangeListener(mCheckChanged);
mChkSign = (CheckBox) findViewById(R.id.chkSign);
mChkSign.setOnCheckedChangeListener(mCheckChanged);
mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt);
mChkEncrypt.setOnCheckedChangeListener(mCheckChanged);
mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate);
mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged);
setExpiryDate(null);
mExpiryDateButton.setOnClickListener(new OnClickListener() {
@TargetApi(11)
public void onClick(View v) {
Calendar expiryDate = mExpiryDate;
if (expiryDate == null) {
expiryDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
}
/*
* Using custom DatePickerDialog which overrides the setTitle because
* the DatePickerDialog title is buggy (unix warparound bug).
* See: https://code.google.com/p/android/issues/detail?id=49066
*/
DatePickerDialog dialog = new ExpiryDatePickerDialog(getContext(),
mExpiryDateSetListener, expiryDate.get(Calendar.YEAR), expiryDate.get(Calendar.MONTH),
expiryDate.get(Calendar.DAY_OF_MONTH));
mDatePickerResultCount = 0;
dialog.setCancelable(true);
dialog.setButton(Dialog.BUTTON_NEGATIVE,
getContext().getString(R.string.btn_no_date),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) {
setExpiryDate(null);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
});
// setCalendarViewShown() is supported from API 11 onwards.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// Hide calendarView in tablets because of the unix warparound bug.
dialog.getDatePicker().setCalendarViewShown(false);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
// will crash with IllegalArgumentException if we set a min date
// that is not before expiry
if (mCreatedDate != null && mCreatedDate.before(expiryDate)) {
dialog.getDatePicker()
.setMinDate(
mCreatedDate.getTime().getTime() + DateUtils.DAY_IN_MILLIS);
} else {
// When created date isn't available
dialog.getDatePicker().setMinDate(expiryDate.getTime().getTime() + DateUtils.DAY_IN_MILLIS);
}
}
dialog.show();
}
});
super.onFinishInflate();
}
public void setCanBeEdited(boolean canBeEdited) {
if (!canBeEdited) {
mDeleteButton.setVisibility(View.INVISIBLE);
mExpiryDateButton.setEnabled(false);
mChkSign.setEnabled(false); //certify is always disabled
mChkEncrypt.setEnabled(false);
mChkAuthenticate.setEnabled(false);
}
}
public void setValue(UncachedSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) {
mKey = key;
mIsMasterKey = isMasterKey;
if (mIsMasterKey) {
mDeleteButton.setVisibility(View.INVISIBLE);
}
mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(getContext(), key.getAlgorithm()));
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(key.getKeyId());
mKeyId.setText(keyIdStr);
boolean isElGamalKey = (key.isElGamalEncrypt());
boolean isDSAKey = (key.isDSA());
if (isElGamalKey) {
mChkSign.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
TableRow row = (TableRow) findViewById(R.id.row_sign);
table.removeView(row);
}
if (isDSAKey) {
mChkEncrypt.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
TableRow row = (TableRow) findViewById(R.id.row_encrypt);
table.removeView(row);
}
if (!mIsMasterKey) {
mChkCertify.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
TableRow row = (TableRow) findViewById(R.id.row_certify);
table.removeView(row);
} else {
TextView mLabelUsage2 = (TextView) findViewById(R.id.label_usage2);
mLabelUsage2.setVisibility(View.INVISIBLE);
}
mIsNewKey = isNewKey;
if (isNewKey) {
mUsage = usage;
mChkCertify.setChecked(
(usage & UncachedSecretKey.CERTIFY_OTHER) == UncachedSecretKey.CERTIFY_OTHER);
mChkSign.setChecked(
(usage & UncachedSecretKey.SIGN_DATA) == UncachedSecretKey.SIGN_DATA);
mChkEncrypt.setChecked(
((usage & UncachedSecretKey.ENCRYPT_COMMS) == UncachedSecretKey.ENCRYPT_COMMS) ||
((usage & UncachedSecretKey.ENCRYPT_STORAGE) == UncachedSecretKey.ENCRYPT_STORAGE));
mChkAuthenticate.setChecked(
(usage & UncachedSecretKey.AUTHENTICATION) == UncachedSecretKey.AUTHENTICATION);
} else {
mUsage = key.getKeyUsage();
mOriginalUsage = mUsage;
if (key.isMasterKey()) {
mChkCertify.setChecked(key.canCertify());
}
mChkSign.setChecked(key.canSign());
mChkEncrypt.setChecked(key.canEncrypt());
mChkAuthenticate.setChecked(key.canAuthenticate());
}
{
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(key.getCreationTime());
setCreatedDate(cal);
}
Date expiryDate = key.getExpiryTime();
if (expiryDate == null) {
setExpiryDate(null);
} else {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(expiryDate);
setExpiryDate(cal);
mOriginalExpiryDate = cal;
}
}
public UncachedSecretKey getValue() {
return mKey;
}
public void onClick(View v) {
final ViewGroup parent = (ViewGroup) getParent();
if (v == mDeleteButton) {
parent.removeView(this);
if (mEditorListener != null) {
mEditorListener.onDeleted(this, mIsNewKey);
}
}
}
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
private void setCreatedDate(Calendar date) {
mCreatedDate = date;
if (date == null) {
mCreationDate.setText(getContext().getString(R.string.none));
} else {
mCreationDate.setText(DateFormat.getDateInstance().format(date.getTime()));
}
}
private void setExpiryDate(Calendar date) {
mExpiryDate = date;
if (date == null) {
mExpiryDateButton.setText(getContext().getString(R.string.none));
} else {
mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
}
}
public Calendar getExpiryDate() {
return mExpiryDate;
}
public int getUsage() {
mUsage = (mUsage & ~UncachedSecretKey.CERTIFY_OTHER) |
(mChkCertify.isChecked() ? UncachedSecretKey.CERTIFY_OTHER : 0);
mUsage = (mUsage & ~UncachedSecretKey.SIGN_DATA) |
(mChkSign.isChecked() ? UncachedSecretKey.SIGN_DATA : 0);
mUsage = (mUsage & ~UncachedSecretKey.ENCRYPT_COMMS) |
(mChkEncrypt.isChecked() ? UncachedSecretKey.ENCRYPT_COMMS : 0);
mUsage = (mUsage & ~UncachedSecretKey.ENCRYPT_STORAGE) |
(mChkEncrypt.isChecked() ? UncachedSecretKey.ENCRYPT_STORAGE : 0);
mUsage = (mUsage & ~UncachedSecretKey.AUTHENTICATION) |
(mChkAuthenticate.isChecked() ? UncachedSecretKey.AUTHENTICATION : 0);
return mUsage;
}
public boolean needsSaving() {
if (mIsNewKey) {
return true;
}
boolean retval = (getUsage() != mOriginalUsage);
boolean dateChanged;
boolean mOEDNull = (mOriginalExpiryDate == null);
boolean mEDNull = (mExpiryDate == null);
if (mOEDNull != mEDNull) {
dateChanged = true;
} else {
if (mOEDNull) {
//both null, no change
dateChanged = false;
} else {
dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0);
}
}
retval |= dateChanged;
return retval;
}
public boolean getIsNewKey() {
return mIsNewKey;
}
}
class ExpiryDatePickerDialog extends DatePickerDialog {
public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack,
int year, int monthOfYear, int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
}
//Set permanent title.
public void setTitle(CharSequence title) {
super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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

@@ -1,428 +0,0 @@
/*
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ImageButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor {
private LayoutInflater mInflater;
private ImageButton mPlusButton;
private ViewGroup mEditors;
private TextView mTitle;
private int mType = 0;
private EditorListener mEditorListener = null;
private Choice mNewKeyAlgorithmChoice;
private int mNewKeySize;
private boolean mOldItemDeleted = false;
private ArrayList<String> mDeletedIDs = new ArrayList<String>();
private ArrayList<UncachedSecretKey> mDeletedKeys = new ArrayList<UncachedSecretKey>();
private boolean mCanBeEdited = true;
private ActionBarActivity mActivity;
private ProgressDialogFragment mGeneratingDialog;
public static final int TYPE_USER_ID = 1;
public static final int TYPE_KEY = 2;
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
public SectionView(Context context) {
super(context);
mActivity = (ActionBarActivity) context;
}
public SectionView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = (ActionBarActivity) context;
}
public ViewGroup getEditors() {
return mEditors;
}
public void setType(int type) {
mType = type;
switch (type) {
case TYPE_USER_ID: {
mTitle.setText(R.string.section_user_ids);
break;
}
case TYPE_KEY: {
mTitle.setText(R.string.section_keys);
break;
}
default: {
break;
}
}
}
public void setCanBeEdited(boolean canBeEdited) {
mCanBeEdited = canBeEdited;
if (!mCanBeEdited) {
mPlusButton.setVisibility(View.INVISIBLE);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onFinishInflate() {
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
setDrawingCacheEnabled(true);
setAlwaysDrawnWithCacheEnabled(true);
mPlusButton = (ImageButton) findViewById(R.id.plusbutton);
mPlusButton.setOnClickListener(this);
mEditors = (ViewGroup) findViewById(R.id.editors);
mTitle = (TextView) findViewById(R.id.title);
updateEditorsVisible();
super.onFinishInflate();
}
/**
* {@inheritDoc}
*/
public void onDeleted(Editor editor, boolean wasNewItem) {
mOldItemDeleted |= !wasNewItem;
if (mOldItemDeleted) {
if (mType == TYPE_USER_ID) {
mDeletedIDs.add(((UserIdEditor) editor).getOriginalID());
} else if (mType == TYPE_KEY) {
mDeletedKeys.add(((KeyEditor) editor).getValue());
}
}
this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
@Override
public void onEdited() {
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
protected void updateEditorsVisible() {
final boolean hasChildren = mEditors.getChildCount() > 0;
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
}
public boolean needsSaving() {
//check each view for needs saving, take account of deleted items
boolean ret = mOldItemDeleted;
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
ret |= editor.needsSaving();
if (mType == TYPE_USER_ID) {
ret |= ((UserIdEditor) editor).primarySwapped();
}
}
return ret;
}
public boolean primaryChanged() {
boolean ret = false;
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
if (mType == TYPE_USER_ID) {
ret |= ((UserIdEditor) editor).primarySwapped();
}
}
return ret;
}
public String getOriginalPrimaryID() {
//NB: this will have to change when we change how Primary IDs are chosen, and so we need to be
// careful about where Master key capabilities are stored... multiple primaries and
// revoked ones make this harder than the simple case we are continuing to assume here
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
if (mType == TYPE_USER_ID) {
if (((UserIdEditor) editor).getIsOriginallyMainUserID()) {
return ((UserIdEditor) editor).getOriginalID();
}
}
}
return null;
}
public ArrayList<String> getOriginalIDs() {
ArrayList<String> orig = new ArrayList<String>();
if (mType == TYPE_USER_ID) {
for (int i = 0; i < mEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i);
if (editor.isMainUserId()) {
orig.add(0, editor.getOriginalID());
} else {
orig.add(editor.getOriginalID());
}
}
return orig;
} else {
return null;
}
}
public ArrayList<String> getDeletedIDs() {
return mDeletedIDs;
}
public ArrayList<UncachedSecretKey> getDeletedKeys() {
return mDeletedKeys;
}
public List<Boolean> getNeedsSavingArray() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
mList.add(editor.needsSaving());
}
return mList;
}
public List<Boolean> getNewIDFlags() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
for (int i = 0; i < mEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i);
if (editor.isMainUserId()) {
mList.add(0, editor.getIsNewID());
} else {
mList.add(editor.getIsNewID());
}
}
return mList;
}
public List<Boolean> getNewKeysArray() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
if (mType == TYPE_KEY) {
for (int i = 0; i < mEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) mEditors.getChildAt(i);
mList.add(editor.getIsNewKey());
}
}
return mList;
}
/**
* {@inheritDoc}
*/
public void onClick(View v) {
if (mCanBeEdited) {
switch (mType) {
case TYPE_USER_ID: {
UserIdEditor view = (UserIdEditor) mInflater.inflate(
R.layout.edit_key_user_id_item, mEditors, false);
view.setEditorListener(this);
view.setValue("", mEditors.getChildCount() == 0, true);
mEditors.addView(view);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
break;
}
case TYPE_KEY: {
CreateKeyDialogFragment mCreateKeyDialogFragment =
CreateKeyDialogFragment.newInstance(mEditors.getChildCount());
mCreateKeyDialogFragment
.setOnAlgorithmSelectedListener(
new CreateKeyDialogFragment.OnAlgorithmSelectedListener() {
@Override
public void onAlgorithmSelected(Choice algorithmChoice, int keySize) {
mNewKeyAlgorithmChoice = algorithmChoice;
mNewKeySize = keySize;
createKey();
}
});
mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog");
break;
}
default: {
break;
}
}
this.updateEditorsVisible();
}
}
public void setUserIds(Vector<String> list) {
if (mType != TYPE_USER_ID) {
return;
}
mEditors.removeAllViews();
for (String userId : list) {
UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
mEditors, false);
view.setEditorListener(this);
view.setValue(userId, mEditors.getChildCount() == 0, false);
view.setCanBeEdited(mCanBeEdited);
mEditors.addView(view);
}
this.updateEditorsVisible();
}
public void setKeys(Vector<UncachedSecretKey> list, Vector<Integer> usages, boolean newKeys) {
if (mType != TYPE_KEY) {
return;
}
mEditors.removeAllViews();
// go through all keys and set view based on them
for (int i = 0; i < list.size(); i++) {
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors,
false);
view.setEditorListener(this);
boolean isMasterKey = (mEditors.getChildCount() == 0);
view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys);
view.setCanBeEdited(mCanBeEdited);
mEditors.addView(view);
}
this.updateEditorsVisible();
}
private void createKey() {
// fill values for this action
Boolean isMasterKey;
String passphrase;
if (mEditors.getChildCount() > 0) {
UncachedSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
passphrase = PassphraseCacheService
.getCachedPassphrase(mActivity, masterKey.getKeyId());
isMasterKey = false;
} else {
passphrase = "";
isMasterKey = true;
}
/*
data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey);
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase);
data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId());
data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// show progress dialog
mGeneratingDialog = ProgressDialogFragment.newInstance(
getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_SPINNER,
true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mActivity.stopService(intent);
}
});
// Message is received after generating is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity,
mGeneratingDialog) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service
Bundle data = message.getDataAsStringList();
UncachedSecretKey newKey = PgpConversionHelper
.BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
addGeneratedKeyToView(newKey);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
mGeneratingDialog.show(mActivity.getSupportFragmentManager(), "dialog");
// start service with intent
mActivity.startService(intent);
*/
}
private void addGeneratedKeyToView(UncachedSecretKey newKey) {
// add view with new key
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
mEditors, false);
view.setEditorListener(SectionView.this);
int usage = 0;
if (mEditors.getChildCount() == 0) {
usage = UncachedSecretKey.CERTIFY_OTHER;
}
view.setValue(newKey, newKey.isMasterKey(), usage, true);
mEditors.addView(view);
SectionView.this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}

View File

@@ -1,267 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Patterns;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.ImageButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import java.util.regex.Matcher;
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
private EditorListener mEditorListener = null;
private ImageButton mDeleteButton;
private RadioButton mIsMainUserId;
private String mOriginalID;
private EditText mName;
private String mOriginalName;
private AutoCompleteTextView mEmail;
private String mOriginalEmail;
private EditText mComment;
private String mOriginalComment;
private boolean mOriginallyMainUserID;
private boolean mIsNewId;
public void setCanBeEdited(boolean canBeEdited) {
if (!canBeEdited) {
mDeleteButton.setVisibility(View.INVISIBLE);
mName.setEnabled(false);
mIsMainUserId.setEnabled(false);
mEmail.setEnabled(false);
mComment.setEnabled(false);
}
}
public static class InvalidEmailException extends Exception {
static final long serialVersionUID = 0xf812773345L;
public InvalidEmailException(String message) {
super(message);
}
}
public UserIdEditor(Context context) {
super(context);
}
public UserIdEditor(Context context, AttributeSet attrs) {
super(context, attrs);
}
TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
};
@Override
protected void onFinishInflate() {
setDrawingCacheEnabled(true);
setAlwaysDrawnWithCacheEnabled(true);
mDeleteButton = (ImageButton) findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this);
mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId);
mIsMainUserId.setOnClickListener(this);
mName = (EditText) findViewById(R.id.name);
mName.addTextChangedListener(mTextWatcher);
mEmail = (AutoCompleteTextView) findViewById(R.id.email);
mComment = (EditText) findViewById(R.id.user_id_item_comment);
mComment.addTextChangedListener(mTextWatcher);
mEmail.setThreshold(1); // Start working from first character
mEmail.setAdapter(
new ArrayAdapter<String>
(this.getContext(), android.R.layout.simple_dropdown_item_1line,
ContactHelper.getPossibleUserEmails(getContext())
));
mEmail.addTextChangedListener(new TextWatcher(){
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
@Override
public void afterTextChanged(Editable editable) {
String email = editable.toString();
if (email.length() > 0) {
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
if (emailMatcher.matches()) {
mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.uid_mail_ok, 0);
} else {
mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.uid_mail_bad, 0);
}
} else {
// remove drawable if email is empty
mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
});
super.onFinishInflate();
}
public void setValue(String userId, boolean isMainID, boolean isNewId) {
mName.setText("");
mOriginalName = "";
mComment.setText("");
mOriginalComment = "";
mEmail.setText("");
mOriginalEmail = "";
mIsNewId = isNewId;
mOriginalID = userId;
String[] result = KeyRing.splitUserId(userId);
if (result[0] != null) {
mName.setText(result[0]);
mOriginalName = result[0];
}
if (result[1] != null) {
mEmail.setText(result[1]);
mOriginalEmail = result[1];
}
if (result[2] != null) {
mComment.setText(result[2]);
mOriginalComment = result[2];
}
mOriginallyMainUserID = isMainID;
setIsMainUserId(isMainID);
}
public String getValue() {
String name = ("" + mName.getText()).trim();
String email = ("" + mEmail.getText()).trim();
String comment = ("" + mComment.getText()).trim();
String userId = name;
if (comment.length() > 0) {
userId += " (" + comment + ")";
}
if (email.length() > 0) {
userId += " <" + email + ">";
}
if (userId.equals("")) {
// ok, empty one...
return userId;
}
//TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed
return userId;
}
public void onClick(View v) {
final ViewGroup parent = (ViewGroup) getParent();
if (v == mDeleteButton) {
boolean wasMainUserId = mIsMainUserId.isChecked();
parent.removeView(this);
if (mEditorListener != null) {
mEditorListener.onDeleted(this, mIsNewId);
}
if (wasMainUserId && parent.getChildCount() > 0) {
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
editor.setIsMainUserId(true);
}
} else if (v == mIsMainUserId) {
for (int i = 0; i < parent.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
if (editor == this) {
editor.setIsMainUserId(true);
} else {
editor.setIsMainUserId(false);
}
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
public void setIsMainUserId(boolean value) {
mIsMainUserId.setChecked(value);
}
public boolean isMainUserId() {
return mIsMainUserId.isChecked();
}
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
@Override
public boolean needsSaving() {
boolean retval = false; //(mOriginallyMainUserID != isMainUserId());
retval |= !(mOriginalName.equals(("" + mName.getText()).trim()));
retval |= !(mOriginalEmail.equals(("" + mEmail.getText()).trim()));
retval |= !(mOriginalComment.equals(("" + mComment.getText()).trim()));
retval |= mIsNewId;
return retval;
}
public boolean getIsOriginallyMainUserID() {
return mOriginallyMainUserID;
}
public boolean primarySwapped() {
return (mOriginallyMainUserID != isMainUserId());
}
public String getOriginalID() {
return mOriginalID;
}
public boolean getIsNewID() { return mIsNewId; }
}

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util;
import android.annotation.SuppressLint;
import android.app.Activity;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants;
@@ -57,13 +58,13 @@ public class AlgorithmNames {
mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512");
mCompressionNames.put(Constants.choice.compression.none, mActivity.getString(R.string.choice_none)
mCompressionNames.put(CompressionAlgorithmTags.UNCOMPRESSED, mActivity.getString(R.string.choice_none)
+ " (" + mActivity.getString(R.string.compression_fast) + ")");
mCompressionNames.put(Constants.choice.compression.zip,
mCompressionNames.put(CompressionAlgorithmTags.ZIP,
"ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
mCompressionNames.put(Constants.choice.compression.zlib,
mCompressionNames.put(CompressionAlgorithmTags.ZLIB,
"ZLIB (" + mActivity.getString(R.string.compression_fast) + ")");
mCompressionNames.put(Constants.choice.compression.bzip2,
mCompressionNames.put(CompressionAlgorithmTags.BZIP2,
"BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")");
}

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