package reordering: merge util and helper, there were no real difference; created ui.util for everything related to formatting
This commit is contained in:
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user