aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2014-09-17 21:51:25 +0200
committerDominik Schürmann <dominik@dominikschuermann.de>2014-09-17 21:51:25 +0200
commitb09d222f3416d155153a681ed256c46fbf5b386a (patch)
tree2fd26f521ebc2d5fc29a2bb0cb83c0d3b91319b1 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util
parenta139be29ba556468ca282c96e5c985762c466b5b (diff)
downloadopen-keychain-b09d222f3416d155153a681ed256c46fbf5b386a.tar.gz
open-keychain-b09d222f3416d155153a681ed256c46fbf5b386a.tar.bz2
open-keychain-b09d222f3416d155153a681ed256c46fbf5b386a.zip
package reordering: merge util and helper, there were no real difference; created ui.util for everything related to formatting
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java467
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java101
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java157
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java240
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java57
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java70
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java343
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java101
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java133
12 files changed, 1661 insertions, 202 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
new file mode 100644
index 000000000..1e2a35be2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
@@ -0,0 +1,467 @@
+/*
+ * 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.util;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.annotation.TargetApi;
+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;
+import android.util.Patterns;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+
+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 {
+
+ public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{
+ KeychainContract.KeyRings.USER_ID,
+ KeychainContract.KeyRings.FINGERPRINT,
+ KeychainContract.KeyRings.KEY_ID,
+ KeychainContract.KeyRings.MASTER_KEY_ID,
+ KeychainContract.KeyRings.EXPIRY,
+ KeychainContract.KeyRings.IS_REVOKED};
+ public static final String[] USER_IDS_PROJECTION = new String[]{
+ KeychainContract.UserIds.USER_ID
+ };
+
+ public static final String NON_REVOKED_SELECTION = KeychainContract.UserIds.IS_REVOKED + "=0";
+
+ public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID};
+ public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID};
+
+ public static final String ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION =
+ ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?";
+ public static final String ACCOUNT_TYPE_SELECTION = ContactsContract.RawContacts.ACCOUNT_TYPE + "=?";
+ public static final String RAW_CONTACT_AND_MIMETYPE_SELECTION =
+ 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));
+ // now return the Set (without duplicates) as a List
+ return new ArrayList<String>(accountMails);
+ }
+
+ public static List<String> getPossibleUserNames(Context context) {
+ Set<String> accountMails = getAccountEmails(context);
+ Set<String> names = getContactNamesFromEmails(context, accountMails);
+ names.addAll(getMainProfileContactName(context));
+ return new ArrayList<String>(names);
+ }
+
+ /**
+ * Get emails from AccountManager
+ *
+ * @param context
+ * @return
+ */
+ private static Set<String> getAccountEmails(Context context) {
+ final Account[] accounts = AccountManager.get(context).getAccounts();
+ final Set<String> emailSet = new HashSet<String>();
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ emailSet.add(account.name);
+ }
+ }
+ return emailSet;
+ }
+
+ /**
+ * Search for contact names based on a list of emails (to find out the names of the
+ * device owner based on the email addresses from AccountsManager)
+ *
+ * @param context
+ * @param emails
+ * @return
+ */
+ private static Set<String> getContactNamesFromEmails(Context context, Set<String> emails) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ Set<String> names = new HashSet<String>();
+ for (String email : emails) {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor profileCursor = resolver.query(
+ ContactsContract.CommonDataKinds.Email.CONTENT_URI,
+ new String[]{ContactsContract.CommonDataKinds.Email.ADDRESS,
+ ContactsContract.Contacts.DISPLAY_NAME},
+ ContactsContract.CommonDataKinds.Email.ADDRESS + "=?",
+ new String[]{email}, null
+ );
+ if (profileCursor == null) return null;
+
+ Set<String> currNames = new HashSet<String>();
+ while (profileCursor.moveToNext()) {
+ String name = profileCursor.getString(1);
+ Log.d(Constants.TAG, "name" + name);
+ if (name != null) {
+ currNames.add(name);
+ }
+ }
+ profileCursor.close();
+ names.addAll(currNames);
+ }
+ return names;
+ } else {
+ return new HashSet<String>();
+ }
+ }
+
+ /**
+ * Retrieves the emails of the primary profile contact
+ * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
+ *
+ * @param context
+ * @return
+ */
+ private static Set<String> getMainProfileContactEmails(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor profileCursor = resolver.query(
+ Uri.withAppendedPath(
+ ContactsContract.Profile.CONTENT_URI,
+ ContactsContract.Contacts.Data.CONTENT_DIRECTORY),
+ new String[]{ContactsContract.CommonDataKinds.Email.ADDRESS,
+ ContactsContract.CommonDataKinds.Email.IS_PRIMARY},
+
+ // Selects only email addresses
+ ContactsContract.Contacts.Data.MIMETYPE + "=?",
+ new String[]{
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
+ },
+ // Show primary rows first. Note that there won't be a primary email address if the
+ // user hasn't specified one.
+ ContactsContract.Contacts.Data.IS_PRIMARY + " DESC"
+ );
+ if (profileCursor == null) return null;
+
+ Set<String> emails = new HashSet<String>();
+ while (profileCursor.moveToNext()) {
+ String email = profileCursor.getString(0);
+ if (email != null) {
+ emails.add(email);
+ }
+ }
+ profileCursor.close();
+ return emails;
+ } else {
+ return new HashSet<String>();
+ }
+ }
+
+ /**
+ * Retrieves the name of the primary profile contact
+ * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
+ *
+ * @param context
+ * @return
+ */
+ private static List<String> getMainProfileContactName(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI,
+ new String[]{ContactsContract.Profile.DISPLAY_NAME},
+ null, null, null);
+ if (profileCursor == null) return null;
+
+ Set<String> names = new HashSet<String>();
+ // should only contain one entry!
+ while (profileCursor.moveToNext()) {
+ String name = profileCursor.getString(0);
+ if (name != null) {
+ names.add(name);
+ }
+ }
+ profileCursor.close();
+ return new ArrayList<String>(names);
+ } else {
+ return new ArrayList<String>();
+ }
+ }
+
+ public static List<String> getContactMails(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
+ new String[]{ContactsContract.CommonDataKinds.Email.DATA},
+ null, null, null);
+ if (mailCursor == null) return new ArrayList<String>();
+
+ Set<String> mails = new HashSet<String>();
+ while (mailCursor.moveToNext()) {
+ String email = mailCursor.getString(0);
+ if (email != null) {
+ mails.add(email);
+ }
+ }
+ mailCursor.close();
+ return new ArrayList<String>(mails);
+ }
+
+ public static List<String> getContactNames(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI,
+ new String[]{ContactsContract.Contacts.DISPLAY_NAME},
+ null, null, null);
+ if (cursor == null) return new ArrayList<String>();
+
+ Set<String> names = new HashSet<String>();
+ while (cursor.moveToNext()) {
+ String name = cursor.getString(0);
+ if (name != null) {
+ names.add(name);
+ }
+ }
+ cursor.close();
+ return new ArrayList<String>(names);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public static Uri dataUriFromContactUri(Context context, Uri contactUri) {
+ Cursor contactMasterKey = context.getContentResolver().query(contactUri,
+ new String[]{ContactsContract.Data.DATA2}, null, null, null, null);
+ if (contactMasterKey != null) {
+ if (contactMasterKey.moveToNext()) {
+ return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0));
+ }
+ contactMasterKey.close();
+ }
+ 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
+ */
+ public static void writeKeysToContacts(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ Set<String> contactFingerprints = getRawContactFingerprints(resolver);
+
+ // Load all Keys from OK
+ Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION,
+ null, null, null);
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ String[] primaryUserId = KeyRing.splitUserId(cursor.getString(0));
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1));
+ contactFingerprints.remove(fingerprint);
+ String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(cursor.getLong(2));
+ long masterKeyId = cursor.getLong(3);
+ boolean isExpired = !cursor.isNull(4) && new Date(cursor.getLong(4) * 1000).before(new Date());
+ boolean isRevoked = cursor.getInt(5) > 0;
+ int rawContactId = findRawContactId(resolver, fingerprint);
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+
+ // Do not store expired or revoked keys in contact db - and remove them if they already exist
+ if (isExpired || isRevoked) {
+ if (rawContactId != -1) {
+ resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ID_SELECTION,
+ new String[]{Integer.toString(rawContactId)});
+ }
+ } else {
+
+ // Create a new rawcontact with corresponding key if it does not exist yet
+ if (rawContactId == -1) {
+ insertContact(ops, context, fingerprint);
+ writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort);
+ }
+
+ // We always update the display name (which is derived from primary user id)
+ // and email addresses from user id
+ writeContactDisplayName(ops, rawContactId, primaryUserId[0]);
+ writeContactEmail(ops, resolver, rawContactId, masterKeyId);
+ try {
+ resolver.applyBatch(ContactsContract.AUTHORITY, ops);
+ } catch (Exception e) {
+ Log.w(Constants.TAG, e);
+ }
+ }
+ }
+ cursor.close();
+ }
+
+ // Delete fingerprints that are no longer present in OK
+ for (String fingerprint : contactFingerprints) {
+ resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION,
+ new String[]{Constants.ACCOUNT_TYPE, fingerprint});
+ }
+
+ }
+
+ /**
+ * @return a set of all key fingerprints currently present in the contact db
+ */
+ private static Set<String> getRawContactFingerprints(ContentResolver resolver) {
+ HashSet<String> result = new HashSet<String>();
+ Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION,
+ ACCOUNT_TYPE_SELECTION, new String[]{Constants.ACCOUNT_TYPE}, null);
+ if (fingerprints != null) {
+ while (fingerprints.moveToNext()) {
+ result.add(fingerprints.getString(0));
+ }
+ fingerprints.close();
+ }
+ return result;
+ }
+
+ /**
+ * This will search the contact db for a raw contact with a given fingerprint
+ *
+ * @return raw contact id or -1 if not found
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private static int findRawContactId(ContentResolver resolver, String fingerprint) {
+ int rawContactId = -1;
+ Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION,
+ ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.ACCOUNT_TYPE, fingerprint}, null, null);
+ if (raw != null) {
+ if (raw.moveToNext()) {
+ rawContactId = raw.getInt(0);
+ }
+ raw.close();
+ }
+ return rawContactId;
+ }
+
+ /**
+ * Creates a empty raw contact with a given fingerprint
+ */
+ private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) {
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
+ .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint)
+ .build());
+ }
+
+ /**
+ * Adds a key id to the given raw contact.
+ * <p/>
+ * This creates the link to OK in contact details
+ */
+ private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, int rawContactId,
+ long masterKeyId, String keyIdShort) {
+ ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId)
+ .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE)
+ .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyIdShort))
+ .withValue(ContactsContract.Data.DATA2, masterKeyId)
+ .build());
+ }
+
+ /**
+ * Write all known email addresses of a key (derived from user ids) to a given raw contact
+ */
+ private static void writeContactEmail(ArrayList<ContentProviderOperation> ops, ContentResolver resolver,
+ 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(masterKeyId),
+ USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null);
+ if (ids != null) {
+ while (ids.moveToNext()) {
+ String[] userId = KeyRing.splitUserId(ids.getString(0));
+ if (userId[1] != null) {
+ ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI),
+ rawContactId)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId[1])
+ .build());
+ }
+ }
+ ids.close();
+ }
+ }
+
+ private static void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, int rawContactId,
+ String displayName) {
+ if (displayName != null) {
+ ops.add(insertOrUpdateForRawContact(ContactsContract.Data.CONTENT_URI, rawContactId,
+ ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
+ .build());
+ }
+ }
+
+ private static ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder,
+ int rawContactId) {
+ return rawContactId == -1 ?
+ builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) :
+ builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
+ }
+
+ private static ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, int rawContactId,
+ String itemType) {
+ if (rawContactId == -1) {
+ return referenceRawContact(ContentProviderOperation.newInsert(uri), rawContactId).withValue(
+ ContactsContract.Data.MIMETYPE, itemType);
+ } else {
+ return selectByRawContactAndItemType(ContentProviderOperation.newUpdate(uri), rawContactId, itemType);
+ }
+ }
+
+ private static ContentProviderOperation.Builder selectByRawContactAndItemType(
+ ContentProviderOperation.Builder builder, int rawContactId, String itemType) {
+ return builder.withSelection(RAW_CONTACT_AND_MIMETYPE_SELECTION,
+ new String[]{Integer.toString(rawContactId), itemType});
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
new file mode 100644
index 000000000..33541718e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util;
+
+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;
+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 {
+
+ public static void importContacts(Context context, Messenger messenger) {
+ importAll(context, messenger, ContactHelper.getContactMails(context));
+ }
+
+ public static void importAll(Context context, Messenger messenger, List<String> mails) {
+ Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>();
+ for (String mail : mails) {
+ keys.addAll(getEmailKeys(context, mail));
+ }
+ importKeys(context, messenger, new ArrayList<ImportKeysListEntry>(keys));
+ }
+
+ public static List<ImportKeysListEntry> getEmailKeys(Context context, String mail) {
+ Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>();
+
+ // Try _hkp._tcp SRV record first
+ String[] mailparts = mail.split("@");
+ if (mailparts.length == 2) {
+ HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]);
+ if (hkp != null) {
+ keys.addAll(getEmailKeys(mail, hkp));
+ }
+ }
+
+ if (keys.isEmpty()) {
+ // Most users don't have the SRV record, so ask a default server as well
+ String server = Preferences.getPreferences(context).getPreferredKeyserver();
+ if (server != null) {
+ HkpKeyserver hkp = new HkpKeyserver(server);
+ keys.addAll(getEmailKeys(mail, hkp));
+ }
+ }
+ return new ArrayList<ImportKeysListEntry>(keys);
+ }
+
+ private static void importKeys(Context context, Messenger messenger, List<ImportKeysListEntry> keys) {
+ Intent importIntent = new Intent(context, KeychainIntentService.class);
+ importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
+ Bundle importData = new Bundle();
+ importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
+ new ArrayList<ImportKeysListEntry>(keys));
+ importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
+ importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ context.startService(importIntent);
+ }
+
+ public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) {
+ Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>();
+ try {
+ for (ImportKeysListEntry key : keyServer.search(mail)) {
+ if (key.isRevoked() || key.isExpired()) continue;
+ for (String userId : key.getUserIds()) {
+ if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
+ keys.add(key);
+ }
+ }
+ }
+ } catch (Keyserver.QueryFailedException ignored) {
+ } catch (Keyserver.QueryNeedsRepairException ignored) {
+ }
+ return new ArrayList<ImportKeysListEntry>(keys);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
new file mode 100644
index 000000000..3c100e272
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util;
+
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.app.ActionBarActivity;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+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 java.io.File;
+
+public class ExportHelper {
+ protected File mExportFile;
+
+ ActionBarActivity mActivity;
+
+ public ExportHelper(ActionBarActivity activity) {
+ super();
+ this.mActivity = activity;
+ }
+
+ public void deleteKey(Uri dataUri, Handler deleteHandler) {
+ try {
+ long masterKeyId = new ProviderHelper(mActivity).getCachedPublicKeyRing(dataUri)
+ .extractOrGetMasterKeyId();
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(deleteHandler);
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
+ new long[]{ masterKeyId });
+ deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
+ } catch (PgpGeneralException e) {
+ Log.e(Constants.TAG, "key not found!", e);
+ }
+ }
+
+ /**
+ * Show dialog where to export keys
+ */
+ public void showExportKeysDialog(final long[] masterKeyIds, final File exportFile,
+ final boolean showSecretCheckbox) {
+ mExportFile = exportFile;
+
+ 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 onFileSelected(File file, boolean checked) {
+ mExportFile = file;
+ exportKeys(masterKeyIds, checked);
+ }
+ }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg);
+ }
+
+ /**
+ * Export keys
+ */
+ public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
+ Log.d(Constants.TAG, "exportKeys started");
+
+ // Send all information needed to service to export key in other thread
+ final Intent intent = new Intent(mActivity, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFile.getAbsolutePath());
+ data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);
+
+ if (masterKeyIds == null) {
+ data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
+ } else {
+ data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
+ }
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after exporting is done in KeychainIntentService
+ KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
+ mActivity.getString(R.string.progress_exporting),
+ 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 returnData = message.getData();
+
+ int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT);
+ String toastMessage;
+ if (exported == 1) {
+ toastMessage = mActivity.getString(R.string.key_exported);
+ } else if (exported > 0) {
+ toastMessage = mActivity.getString(R.string.keys_exported, exported);
+ } else {
+ toastMessage = mActivity.getString(R.string.no_keys_exported);
+ }
+ Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show();
+
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(exportHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ exportHandler.showProgressDialog(mActivity);
+
+ // start service with intent
+ mActivity.startService(intent);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
new file mode 100644
index 000000000..677acb1b8
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
@@ -0,0 +1,240 @@
+/*
+ * 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.util;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+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.R;
+import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+
+import java.io.File;
+import java.text.DecimalFormat;
+
+public class FileHelper {
+
+ /**
+ * Checks if external storage is mounted if file is located on external storage
+ *
+ * @param file
+ * @return true if storage is mounted
+ */
+ public static boolean isStorageMounted(String file) {
+ if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Opens the preferred installed file manager on Android and shows a toast if no manager is
+ * installed.
+ *
+ * @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(Fragment fragment, Uri last, String mimeType, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ intent.setData(last);
+ intent.setType(mimeType);
+
+ try {
+ fragment.startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ 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 mimeType can be text/plain for example
+ * @param multiple allow file chooser to return multiple files
+ * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void openDocument(Fragment fragment, String mimeType, boolean multiple, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(mimeType);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
+
+ fragment.startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * Opens the storage browser on Android 4.4 or later for saving a file
+ *
+ * @param fragment
+ * @param mimeType can be text/plain for example
+ * @param suggestedName a filename desirable for the file to be saved
+ * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void saveDocument(Fragment fragment, String mimeType, String suggestedName, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(mimeType);
+ intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works
+ intent.putExtra(Intent.EXTRA_TITLE, suggestedName);
+ fragment.startActivityForResult(intent, requestCode);
+ }
+
+ public static String getFilename(Context context, Uri uri) {
+ String filename = null;
+ try {
+ Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * Retrieve thumbnail of file, document api feature and thus KitKat only
+ */
+ 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;
+ }
+ }
+
+ public static String readableFileSize(long size) {
+ if (size <= 0) return "0";
+ final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
+ int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
+ return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
+ }
+
+ public static interface FileDialogCallback {
+ public void onFileSelected(File file, boolean checked);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java
deleted file mode 100644
index eeeacf465..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 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.util;
-
-import android.content.Context;
-import android.text.Spannable;
-import android.text.style.ForegroundColorSpan;
-
-import org.sufficientlysecure.keychain.R;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Highlighter {
- private Context mContext;
- private String mQuery;
-
- public Highlighter(Context context, String query) {
- mContext = context;
- mQuery = query;
- }
-
- public Spannable highlight(String text) {
- Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
-
- if (mQuery == null) {
- return highlight;
- }
-
- Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")");
- Matcher matcher = pattern.matcher(text);
- while (matcher.find()) {
- highlight.setSpan(
- new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
- matcher.start(),
- matcher.end(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- return highlight;
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
new file mode 100644
index 000000000..357a9603c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 Daniel Albert
+ *
+ * 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Messenger;
+
+import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeyUpdateHelper {
+
+ public void updateAllKeys(Context context, KeychainIntentServiceHandler finishedHandler) {
+ UpdateTask updateTask = new UpdateTask(context, finishedHandler);
+ updateTask.execute();
+ }
+
+ private class UpdateTask extends AsyncTask<Void, Void, Void> {
+ private Context mContext;
+ private KeychainIntentServiceHandler mHandler;
+
+ public UpdateTask(Context context, KeychainIntentServiceHandler handler) {
+ this.mContext = context;
+ this.mHandler = handler;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ ProviderHelper providerHelper = new ProviderHelper(mContext);
+ List<ImportKeysListEntry> keys = new ArrayList<ImportKeysListEntry>();
+ String[] servers = Preferences.getPreferences(mContext).getKeyServers();
+
+ if (servers != null && servers.length > 0) {
+ // Load all the fingerprints in the database and prepare to import them
+ for (String fprint : providerHelper.getAllFingerprints(KeychainContract.KeyRings.buildUnifiedKeyRingsUri())) {
+ ImportKeysListEntry key = new ImportKeysListEntry();
+ key.setFingerprintHex(fprint);
+ key.setBitStrength(1337);
+ key.addOrigin(servers[0]);
+ keys.add(key);
+ }
+
+ // Start the service and update the keys
+ Intent importIntent = new Intent(mContext, KeychainIntentService.class);
+ importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
+
+ Bundle importData = new Bundle();
+ importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
+ new ArrayList<ImportKeysListEntry>(keys));
+ importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
+
+ importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, new Messenger(mHandler));
+
+ mContext.startService(importIntent);
+ }
+ return null;
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
index 10c0fc4d0..4dda74ace 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
@@ -17,8 +17,13 @@
package org.sufficientlysecure.keychain.util;
+import android.os.Bundle;
+
import org.sufficientlysecure.keychain.Constants;
+import java.util.Iterator;
+import java.util.Set;
+
/**
* Wraps Android Logging to enable or disable debug output using Constants
*/
@@ -80,4 +85,35 @@ public final class Log {
android.util.Log.e(tag, msg, tr);
}
+
+ /**
+ * Logs bundle content to debug for inspecting the content
+ *
+ * @param bundle
+ * @param bundleName
+ */
+ public static void logDebugBundle(Bundle bundle, String bundleName) {
+ if (Constants.DEBUG) {
+ if (bundle != null) {
+ Set<String> ks = bundle.keySet();
+ Iterator<String> iterator = ks.iterator();
+
+ Log.d(Constants.TAG, "Bundle " + bundleName + ":");
+ Log.d(Constants.TAG, "------------------------------");
+ while (iterator.hasNext()) {
+ String key = iterator.next();
+ Object value = bundle.get(key);
+
+ if (value != null) {
+ Log.d(Constants.TAG, key + " : " + value.toString());
+ } else {
+ Log.d(Constants.TAG, key + " : null");
+ }
+ }
+ Log.d(Constants.TAG, "------------------------------");
+ } else {
+ Log.d(Constants.TAG, "Bundle " + bundleName + ": null");
+ }
+ }
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java
deleted file mode 100644
index 22e3f5d66..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Notify.java
+++ /dev/null
@@ -1,70 +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.util;
-
-import android.app.Activity;
-import android.content.res.Resources;
-
-import com.github.johnpersano.supertoasts.SuperCardToast;
-import com.github.johnpersano.supertoasts.SuperToast;
-
-/**
- * @author danielhass
- * Notify wrapper which allows a more easy use of different notification libraries
- */
-public class Notify {
-
- public static enum Style {OK, WARN, INFO, ERROR}
-
- /**
- * Shows a simple in-layout notification with the CharSequence given as parameter
- * @param activity
- * @param text Text to show
- * @param style Notification styling
- */
- public static void showNotify(Activity activity, CharSequence text, Style style) {
-
- SuperCardToast st = new SuperCardToast(activity);
- st.setText(text);
- st.setDuration(SuperToast.Duration.MEDIUM);
- switch (style){
- case OK:
- st.setBackground(SuperToast.Background.GREEN);
- break;
- case WARN:
- st.setBackground(SuperToast.Background.ORANGE);
- break;
- case ERROR:
- st.setBackground(SuperToast.Background.RED);
- break;
- }
- st.show();
-
- }
-
- /**
- * Shows a simple in-layout notification with the resource text from given id
- * @param activity
- * @param resId ResourceId of notification text
- * @param style Notification styling
- * @throws Resources.NotFoundException
- */
- public static void showNotify(Activity activity, int resId, Style style) throws Resources.NotFoundException {
- showNotify(activity, activity.getResources().getText(resId), style);
- }
-} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
new file mode 100644
index 000000000..35570ef6e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2012-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.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Constants.Pref;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ListIterator;
+import java.util.Vector;
+
+/**
+ * Singleton Implementation of a Preference Helper
+ */
+public class Preferences {
+ private static Preferences sPreferences;
+ private SharedPreferences mSharedPreferences;
+
+ public static synchronized Preferences getPreferences(Context context) {
+ return getPreferences(context, false);
+ }
+
+ public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
+ if (sPreferences == null || forceNew) {
+ sPreferences = new Preferences(context);
+ } else {
+ // to make it safe for multiple processes, call getSharedPreferences everytime
+ sPreferences.updateSharedPreferences(context);
+ }
+ return sPreferences;
+ }
+
+ private Preferences(Context context) {
+ updateSharedPreferences(context);
+ }
+
+ public void updateSharedPreferences(Context context) {
+ // multi-process preferences
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS);
+ } else {
+ mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE);
+ }
+ }
+
+ public String getLanguage() {
+ return mSharedPreferences.getString(Constants.Pref.LANGUAGE, "");
+ }
+
+ public void setLanguage(String value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(Constants.Pref.LANGUAGE, value);
+ editor.commit();
+ }
+
+ public long getPassphraseCacheTtl() {
+ int ttl = mSharedPreferences.getInt(Constants.Pref.PASSPHRASE_CACHE_TTL, 180);
+ // fix the value if it was set to "never" in previous versions, which currently is not
+ // supported
+ if (ttl == 0) {
+ ttl = 180;
+ }
+ return (long) ttl;
+ }
+
+ public void setPassphraseCacheTtl(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.PASSPHRASE_CACHE_TTL, value);
+ editor.commit();
+ }
+
+ public int getDefaultEncryptionAlgorithm() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM,
+ PGPEncryptedData.AES_256);
+ }
+
+ public void setDefaultEncryptionAlgorithm(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
+ editor.commit();
+ }
+
+ public int getDefaultHashAlgorithm() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM,
+ HashAlgorithmTags.SHA256);
+ }
+
+ public void setDefaultHashAlgorithm(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, value);
+ editor.commit();
+ }
+
+ public int getDefaultMessageCompression() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
+ CompressionAlgorithmTags.ZLIB);
+ }
+
+ public void setDefaultMessageCompression(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, value);
+ editor.commit();
+ }
+
+ public int getDefaultFileCompression() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
+ CompressionAlgorithmTags.UNCOMPRESSED);
+ }
+
+ public void setDefaultFileCompression(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, value);
+ editor.commit();
+ }
+
+ public boolean getDefaultAsciiArmor() {
+ return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false);
+ }
+
+ public void setDefaultAsciiArmor(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value);
+ editor.commit();
+ }
+
+ public boolean getShowAdvancedTabs() {
+ return mSharedPreferences.getBoolean(Pref.SHOW_ADVANCED_TABS, false);
+ }
+
+ public void setShowAdvancedTabs(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.SHOW_ADVANCED_TABS, value);
+ editor.commit();
+ }
+
+ public boolean getCachedConsolidate() {
+ return mSharedPreferences.getBoolean(Pref.CACHED_CONSOLIDATE, false);
+ }
+
+ public void setCachedConsolidate(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.CACHED_CONSOLIDATE, value);
+ editor.commit();
+ }
+
+ public int getCachedConsolidateNumPublics() {
+ return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_PUBLICS, -1);
+ }
+
+ public void setCachedConsolidateNumPublics(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Pref.CACHED_CONSOLIDATE_PUBLICS, value);
+ editor.commit();
+ }
+
+ public int getCachedConsolidateNumSecrets() {
+ return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_SECRETS, -1);
+ }
+
+ public void setCachedConsolidateNumSecrets(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Pref.CACHED_CONSOLIDATE_SECRETS, value);
+ editor.commit();
+ }
+
+ public boolean isFirstTime() {
+ return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
+ }
+
+ public boolean useDefaultYubikeyPin() {
+ return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, true);
+ }
+
+ public void setUseDefaultYubikeyPin(boolean useDefaultYubikeyPin) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, useDefaultYubikeyPin);
+ editor.commit();
+ }
+
+ public void setFirstTime(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.Pref.FIRST_TIME, value);
+ editor.commit();
+ }
+
+ public String[] getKeyServers() {
+ String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
+ Constants.Defaults.KEY_SERVERS);
+ Vector<String> servers = new Vector<String>();
+ String chunks[] = rawData.split(",");
+ for (String c : chunks) {
+ String tmp = c.trim();
+ if (tmp.length() > 0) {
+ servers.add(tmp);
+ }
+ }
+ return servers.toArray(chunks);
+ }
+ public String getPreferredKeyserver() {
+ return getKeyServers()[0];
+ }
+
+ public void setKeyServers(String[] value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ String rawData = "";
+ for (String v : value) {
+ String tmp = v.trim();
+ if (tmp.length() == 0) {
+ continue;
+ }
+ if (!"".equals(rawData)) {
+ rawData += ",";
+ }
+ rawData += tmp;
+ }
+ editor.putString(Constants.Pref.KEY_SERVERS, rawData);
+ editor.commit();
+ }
+
+ public void setWriteVersionHeader(boolean conceal) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.Pref.WRITE_VERSION_HEADER, conceal);
+ editor.commit();
+ }
+
+ public boolean getWriteVersionHeader() {
+ return mSharedPreferences.getBoolean(Constants.Pref.WRITE_VERSION_HEADER, false);
+ }
+
+ public void setSearchKeyserver(boolean searchKeyserver) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.SEARCH_KEYSERVER, searchKeyserver);
+ editor.commit();
+ }
+ public void setSearchKeybase(boolean searchKeybase) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Pref.SEARCH_KEYBASE, searchKeybase);
+ editor.commit();
+ }
+
+ public CloudSearchPrefs getCloudSearchPrefs() {
+ return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true),
+ mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true),
+ getPreferredKeyserver());
+ }
+
+ public static class CloudSearchPrefs {
+ public final boolean searchKeyserver;
+ public final boolean searchKeybase;
+ public final String keyserver;
+
+ public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, String keyserver) {
+ this.searchKeyserver = searchKeyserver;
+ this.searchKeybase = searchKeybase;
+ this.keyserver = keyserver;
+ }
+ }
+
+ public void updatePreferences() {
+ if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) !=
+ Constants.Defaults.PREF_VERSION) {
+ switch (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0)) {
+ case 1:
+ // fall through
+ case 2:
+ // fall through
+ case 3: {
+ // migrate keyserver to hkps
+ String[] serversArray = getKeyServers();
+ ArrayList<String> servers = new ArrayList<String>(Arrays.asList(serversArray));
+ ListIterator<String> it = servers.listIterator();
+ while (it.hasNext()) {
+ String server = it.next();
+ if (server == null) {
+ continue;
+ }
+ if (server.equals("pool.sks-keyservers.net")) {
+ // use HKPS!
+ it.set("hkps://hkps.pool.sks-keyservers.net");
+ } else if (server.equals("pgp.mit.edu")) {
+ // use HKPS!
+ it.set("hkps://pgp.mit.edu");
+ } else if (server.equals("subkeys.pgp.net")) {
+ // remove, because often down and no HKPS!
+ it.remove();
+ }
+
+ }
+ setKeyServers(servers.toArray(new String[servers.size()]));
+
+ // migrate old uncompressed constant to new one
+ if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0)
+ == 0x21070001) {
+ setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED);
+ }
+
+ // migrate away from MD5
+ if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0)
+ == HashAlgorithmTags.MD5) {
+ setDefaultHashAlgorithm(HashAlgorithmTags.SHA256);
+ }
+ }
+ // fall through
+ case 4: {
+ // for compatibility: change from SHA512 to SHA256
+ if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0)
+ == HashAlgorithmTags.SHA512) {
+ setDefaultHashAlgorithm(HashAlgorithmTags.SHA256);
+ }
+ }
+ }
+
+ // write new preference version
+ mSharedPreferences.edit()
+ .putInt(Constants.Pref.PREF_DEFAULT_VERSION, Constants.Defaults.PREF_VERSION)
+ .commit();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java
deleted file mode 100644
index 28e567d76..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
- * Copyright (C) 2011 Andreas Schildbach
- *
- * 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.util;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import com.google.zxing.BarcodeFormat;
-import com.google.zxing.EncodeHintType;
-import com.google.zxing.WriterException;
-import com.google.zxing.common.BitMatrix;
-import com.google.zxing.qrcode.QRCodeWriter;
-import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
-
-import org.sufficientlysecure.keychain.Constants;
-
-import java.util.Hashtable;
-
-/**
- * Copied from Bitcoin Wallet
- */
-public class QrCodeUtils {
- public static final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
-
- /**
- * Generate Bitmap with QR Code based on input.
- *
- * @param input
- * @param size
- * @return QR Code as Bitmap
- */
- public static Bitmap getQRCodeBitmap(final String input, final int size) {
- try {
- final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
- hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
- final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size,
- size, hints);
-
- final int width = result.getWidth();
- final int height = result.getHeight();
- final int[] pixels = new int[width * height];
-
- for (int y = 0; y < height; y++) {
- final int offset = y * width;
- for (int x = 0; x < width; x++) {
- pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
- }
- }
-
- final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
- return bitmap;
- } catch (final WriterException e) {
- Log.e(Constants.TAG, "QrCodeUtils", e);
- return null;
- }
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
new file mode 100644
index 000000000..27f026f80
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
@@ -0,0 +1,101 @@
+package org.sufficientlysecure.keychain.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LabeledIntent;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+/*
+ * 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/>.
+ */
+
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ShareHelper {
+ Context mContext;
+
+ public ShareHelper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Create Intent Chooser but exclude OK's EncryptActivity.
+ * <p/>
+ * Put together from some stackoverflow posts...
+ */
+ public Intent createChooserExcluding(Intent prototype, String title, String[] activityBlacklist) {
+ // Produced an empty list on Huawei U8860 with Android Version 4.0.3 and weird results on 2.3
+ // TODO: test on 4.1, 4.2, 4.3, only tested on 4.4
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ return Intent.createChooser(prototype, title);
+ }
+
+ List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
+
+ List<ResolveInfo> resInfoList = mContext.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(activityBlacklist).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(mContext.getPackageManager()).toString();
+ String secondName = second.loadLabel(mContext.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(mContext.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);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
new file mode 100644
index 000000000..9946d81aa
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013-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.util;
+
+import android.content.res.AssetManager;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+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 {
+ public TlsHelperException(Exception e) {
+ super(e);
+ }
+ }
+
+ private static Map<String, byte[]> sStaticCA = new HashMap<String, byte[]>();
+
+ public static void addStaticCA(String domain, byte[] certificate) {
+ sStaticCA.put(domain, certificate);
+ }
+
+ public static void addStaticCA(String domain, AssetManager assetManager, String name) {
+ try {
+ InputStream is = assetManager.open(name);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int reads = is.read();
+
+ while(reads != -1){
+ baos.write(reads);
+ reads = is.read();
+ }
+
+ is.close();
+
+ addStaticCA(domain, baos.toByteArray());
+ } catch (IOException e) {
+ Log.w(Constants.TAG, e);
+ }
+ }
+
+ public static URLConnection openConnection(URL url) throws IOException, TlsHelperException {
+ if (url.getProtocol().equals("https")) {
+ for (String domain : sStaticCA.keySet()) {
+ if (url.getHost().endsWith(domain)) {
+ return openCAConnection(sStaticCA.get(domain), url);
+ }
+ }
+ }
+ return url.openConnection();
+ }
+
+ /**
+ * Opens a Connection that will only accept certificates signed with a specific CA and skips common name check.
+ * This is required for some distributed Keyserver networks like sks-keyservers.net
+ *
+ * @param certificate The X.509 certificate used to sign the servers certificate
+ * @param url Connection target
+ */
+ public static HttpsURLConnection openCAConnection(byte[] certificate, URL url)
+ throws TlsHelperException, IOException {
+ try {
+ // Load CA
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate));
+
+ // Create a KeyStore containing our trusted CAs
+ String keyStoreType = KeyStore.getDefaultType();
+ KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+ keyStore.load(null, null);
+ keyStore.setCertificateEntry("ca", ca);
+
+ // Create a TrustManager that trusts the CAs in our KeyStore
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(keyStore);
+
+ // Create an SSLContext that uses our TrustManager
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, tmf.getTrustManagers(), null);
+
+ // Tell the URLConnection to use a SocketFactory from our SSLContext
+ HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ urlConnection.setSSLSocketFactory(context.getSocketFactory());
+
+ return urlConnection;
+ } catch (CertificateException e) {
+ throw new TlsHelperException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new TlsHelperException(e);
+ } catch (KeyStoreException e) {
+ throw new TlsHelperException(e);
+ } catch (KeyManagementException e) {
+ throw new TlsHelperException(e);
+ }
+ }
+}