package reordering: merge util and helper, there were no real difference; created ui.util for everything related to formatting

This commit is contained in:
Dominik Schürmann
2014-09-17 21:51:25 +02:00
parent a139be29ba
commit b09d222f34
73 changed files with 236 additions and 262 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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