diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 43fdc7751..810f22d8e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +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; @@ -48,22 +49,20 @@ public class ExportHelper { } public void deleteKey(Uri dataUri, Handler deleteHandler) { - long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); - // Create a new Messenger for the communication back Messenger messenger = new Messenger(deleteHandler); + long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri); DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[]{keyRingRowId}); - + new long[]{ masterKeyId }); deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog"); } /** * Show dialog where to export keys */ - public void showExportKeysDialog(final long[] masterKeyIds, final int keyType, - final String exportFilename, final String checkboxString) { + public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename, + final boolean showSecretCheckbox) { mExportFilename = exportFilename; // Message is received after file is selected @@ -72,14 +71,9 @@ public class ExportHelper { public void handleMessage(Message message) { if (message.what == FileDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - int type = keyType; mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - if (data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)) { - type = Id.type.public_secret_key; - } - - exportKeys(masterKeyIds, type); + exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); } } }; @@ -99,9 +93,11 @@ public class ExportHelper { } String message = mActivity.getString(R.string.specify_file_to_export_to); + String checkMsg = showSecretCheckbox ? + mActivity.getString(R.string.also_export_secret_keys) : null; mFileDialog = FileDialogFragment.newInstance(messenger, title, message, - exportFilename, checkboxString); + exportFilename, checkMsg); mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog"); } @@ -111,7 +107,7 @@ public class ExportHelper { /** * Export keys */ - public void exportKeys(long[] masterKeyIds, int keyType) { + 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 @@ -123,7 +119,7 @@ public class ExportHelper { Bundle data = new Bundle(); data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); - data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); + data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret); if (masterKeyIds == null) { data.putBoolean(KeychainIntentService.EXPORT_ALL, true); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 11b87fc97..8a0bf99d7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -53,6 +53,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactory import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.util.InputData; @@ -66,6 +67,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SignatureException; +import java.util.HashMap; import java.util.Iterator; import java.util.Set; @@ -232,47 +234,51 @@ public class PgpDecryptVerify { updateProgress(R.string.progress_finding_key, currentProgress, 100); PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID()); - if (secretKey != null) { - // secret key exists in database - - // allow only a specific key for decryption? - if (mAllowedKeyIds != null) { - // TODO: improve this code! get master key directly! - PGPSecretKeyRing secretKeyRing = - ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, encData.getKeyID()); - long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID(); - Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); - Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds); - Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); - - if (!mAllowedKeyIds.contains(masterKeyId)) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_secret_key_found)); - } - } - - encryptedDataAsymmetric = encData; - - // if no passphrase was explicitly set try to get it from the cache service - if (mPassphrase == null) { - // returns "" if key has no passphrase - mPassphrase = - PassphraseCacheService.getCachedPassphrase(mContext, encData.getKeyID()); - - // if passphrase was not cached, return here - // indicating that a passphrase is missing! - if (mPassphrase == null) { - returnData.setKeyIdPassphraseNeeded(encData.getKeyID()); - returnData.setStatus(PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED); - return returnData; - } - } - - // break out of while, only get first object here - // TODO???: There could be more pgp objects, which are not decrypted! - break; + long masterKeyId = ProviderHelper.getMasterKeyId(mContext, + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(encData.getKeyID())) + ); + PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRing(mContext, masterKeyId); + if (secretKeyRing == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); } + secretKey = secretKeyRing.getSecretKey(encData.getKeyID()); + if (secretKey == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); + } + // secret key exists in database + + // allow only a specific key for decryption? + if (mAllowedKeyIds != null) { + Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); + Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds); + Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); + + if (!mAllowedKeyIds.contains(masterKeyId)) { + throw new PgpGeneralException( + mContext.getString(R.string.error_no_secret_key_found)); + } + } + + encryptedDataAsymmetric = encData; + + // if no passphrase was explicitly set try to get it from the cache service + if (mPassphrase == null) { + // returns "" if key has no passphrase + mPassphrase = + PassphraseCacheService.getCachedPassphrase(mContext, masterKeyId); + + // if passphrase was not cached, return here + // indicating that a passphrase is missing! + if (mPassphrase == null) { + returnData.setKeyIdPassphraseNeeded(masterKeyId); + returnData.setStatus(PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED); + return returnData; + } + } + + // break out of while, only get first object here + // TODO???: There could be more pgp objects, which are not decrypted! + break; } else if (mAllowSymmetricDecryption && obj instanceof PGPPBEEncryptedData) { symmetricPacketFound = true; @@ -362,7 +368,7 @@ public class PgpDecryptVerify { for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); signatureKey = ProviderHelper - .getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); + .getPGPPublicKeyRing(mContext, signature.getKeyID()).getPublicKey(); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } @@ -375,7 +381,7 @@ public class PgpDecryptVerify { PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId( mContext, signatureKeyId); if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + userId = PgpKeyHelper.getMainUserId(signKeyRing.getPublicKey()); } signatureResult.setUserId(userId); break; @@ -545,25 +551,27 @@ public class PgpDecryptVerify { long signatureKeyId = 0; PGPPublicKey signatureKey = null; for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); - signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); + signatureKeyId = signature.getKeyID(); + + // find data about this subkey + HashMap data = ProviderHelper.getGenericData(mContext, + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(signature.getKeyID())), + new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID }, + new int[] { ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_STRING }); + // any luck? otherwise, try next. + if(data.get(KeyRings.MASTER_KEY_ID) == null) { + signature = null; + // do NOT reset signatureKeyId, that one is shown when no known one is found! + continue; } - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(mContext, - signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - signatureResult.setUserId(userId); - break; - } + // this one can't fail now (yay database constraints) + signatureKey = ProviderHelper.getPGPPublicKeyRing(mContext, (Long) data.get(KeyRings.MASTER_KEY_ID)).getPublicKey(); + signatureResult.setUserId((String) data.get(KeyRings.USER_ID)); + + break; } signatureResult.setKeyId(signatureKeyId); @@ -624,7 +632,7 @@ public class PgpDecryptVerify { signatureKeyId); PGPPublicKey mKey = null; if (signKeyRing != null) { - mKey = PgpKeyHelper.getMasterKey(signKeyRing); + mKey = signKeyRing.getPublicKey(); } if (signature.getKeyID() != mKey.getKeyID()) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index 49ce8d3bb..d03f3ccc2 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -260,7 +260,6 @@ public class PgpImportExport { } if (save) { - ProviderHelper.saveKeyRing(mContext, secretKeyRing); // TODO: preserve certifications // (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?) PGPPublicKeyRing newPubRing = null; @@ -275,6 +274,7 @@ public class PgpImportExport { if (newPubRing != null) { ProviderHelper.saveKeyRing(mContext, newPubRing); } + ProviderHelper.saveKeyRing(mContext, secretKeyRing); // TODO: remove status returns, use exceptions! status = Id.return_value.ok; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 290c8870b..9a97bc717 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -60,34 +60,6 @@ public class PgpKeyHelper { return key.getPublicKey().getCreationTime(); } - @SuppressWarnings("unchecked") - public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - @SuppressWarnings("unchecked") - public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - @SuppressWarnings("unchecked") public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) { long cnt = 0; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index c1baed402..a864a165d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -306,7 +306,7 @@ public class PgpSignEncrypt { signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(signatureType, signaturePrivateKey); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey()); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); @@ -505,7 +505,7 @@ public class PgpSignEncrypt { signatureGenerator.init(type, signaturePrivateKey); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey()); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index fcf8f7962..9eeb57222 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -83,10 +83,9 @@ public class KeychainContract { public static final String PATH_UNIFIED = "unified"; - public static final String PATH_BY_MASTER_KEY_ID = "master_key_id"; - public static final String PATH_BY_KEY_ID = "key_id"; - public static final String PATH_BY_EMAILS = "emails"; - public static final String PATH_BY_LIKE_EMAIL = "like_email"; + public static final String PATH_FIND = "find"; + public static final String PATH_BY_EMAIL = "email"; + public static final String PATH_BY_SUBKEY = "subkey"; public static final String PATH_PUBLIC = "public"; public static final String PATH_SECRET = "secret"; @@ -96,31 +95,49 @@ public class KeychainContract { public static final String BASE_API_APPS = "api_apps"; public static final String PATH_ACCOUNTS = "accounts"; - public static class KeyRings implements KeyRingsColumns, BaseColumns { + public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { + public static final String MASTER_KEY_ID = "master_key_id"; + public static final String HAS_SECRET = "has_secret"; + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - /** - * Use if multiple items get returned - */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring"; - - /** - * Use if a single item is returned - */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring"; public static Uri buildUnifiedKeyRingsUri() { return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); } - public static Uri buildUnifiedKeyRingsByEmailUri(String email) { - return CONTENT_URI.buildUpon().appendPath("email:" + email).build(); - } public static Uri buildGenericKeyRingUri(String masterKeyId) { return CONTENT_URI.buildUpon().appendPath(masterKeyId).build(); } + public static Uri buildUnifiedKeyRingUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_UNIFIED).build(); + } + public static Uri buildUnifiedKeyRingUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_UNIFIED).build(); + } + public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) { + return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_EMAIL).appendPath(email).build(); + } + public static Uri buildUnifiedKeyRingsFindBySubkeyUri(String subkey) { + return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_SUBKEY).appendPath(subkey).build(); + } + + } + + public static class KeyRingData implements KeyRingsColumns, BaseColumns { + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() + .appendPath(BASE_KEY_RINGS).build(); + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring_data"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring_data"; + + public static Uri buildPublicKeyRingUri() { + return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); + } public static Uri buildPublicKeyRingUri(String masterKeyId) { return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build(); } @@ -135,12 +152,6 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_SECRET).build(); } - public static Uri buildUnifiedKeyRingUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_UNIFIED).build(); - } - public static Uri buildUnifiedKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_UNIFIED).build(); - } } public static class Keys implements KeysColumns, BaseColumns { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index e0cf6b6c5..36e2a7962 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -124,7 +124,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); - // make sure this is only done once, on the first instance! boolean iAmIt = false; synchronized(apg_hack) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 39ea083fa..1dd6ab08f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -26,19 +26,15 @@ import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; -import android.provider.BaseColumns; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; @@ -46,13 +42,9 @@ import java.util.Arrays; import java.util.HashMap; public class KeychainProvider extends ContentProvider { - // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME - // + ".action.DATABASE_CHANGE"; - // - // public static final String EXTRA_BROADCAST_KEY_TYPE = "key_type"; - // public static final String EXTRA_BROADCAST_CONTENT_ITEM_TYPE = "contentItemType"; private static final int KEY_RINGS_UNIFIED = 101; + private static final int KEY_RINGS_PUBLIC = 102; private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; @@ -65,7 +57,10 @@ public class KeychainProvider extends ContentProvider { private static final int API_ACCOUNTS = 304; private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306; - // private static final int DATA_STREAM = 401; + private static final int KEY_RINGS_FIND_BY_EMAIL = 400; + private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; + + // private static final int DATA_STREAM = 501; protected UriMatcher mUriMatcher; @@ -79,18 +74,36 @@ public class KeychainProvider extends ContentProvider { String authority = KeychainContract.CONTENT_AUTHORITY; /** - * select from key_ring + * list key_rings * *
          * key_rings/unified
+         * key_rings/public
          * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_PUBLIC, + KEY_RINGS_PUBLIC); /** - * select from key_ring + * find by criteria other than master key id + * + * key_rings/find/email/_ + * key_rings/find/subkey/_ + * + */ + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_EMAIL + "/*", + KEY_RINGS_FIND_BY_EMAIL); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*", + KEY_RINGS_FIND_BY_SUBKEY); + + /** + * list key_ring specifics * *
          * key_rings/_/unified
@@ -155,10 +168,15 @@ public class KeychainProvider extends ContentProvider {
     @Override
     public boolean onCreate() {
         mUriMatcher = buildUriMatcher();
-        mKeychainDatabase = new KeychainDatabase(getContext());
         return true;
     }
 
+    public KeychainDatabase getDb() {
+        if(mKeychainDatabase == null)
+            mKeychainDatabase = new KeychainDatabase(getContext());
+        return mKeychainDatabase;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -204,7 +222,6 @@ public class KeychainProvider extends ContentProvider {
         Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
 
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        SQLiteDatabase db = mKeychainDatabase.getReadableDatabase();
 
         int match = mUriMatcher.match(uri);
 
@@ -213,98 +230,126 @@ public class KeychainProvider extends ContentProvider {
 
         switch (match) {
             case KEY_RING_UNIFIED:
-            case KEY_RINGS_UNIFIED: {
+            case KEY_RINGS_UNIFIED:
+            case KEY_RINGS_FIND_BY_EMAIL:
+            case KEY_RINGS_FIND_BY_SUBKEY: {
                 HashMap projectionMap = new HashMap();
-                projectionMap.put(BaseColumns._ID, Tables.KEYS + ".oid AS _id");
-                projectionMap.put(KeysColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.MASTER_KEY_ID);
-                projectionMap.put(KeysColumns.RANK, Tables.KEYS + "." + KeysColumns.RANK);
-                projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID);
-                projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE);
-                projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED);
-                projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY);
-                projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT);
-                projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN);
-                projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION);
-                projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY);
-                projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM);
-                projectionMap.put(KeysColumns.FINGERPRINT, KeysColumns.FINGERPRINT);
-                projectionMap.put(UserIdsColumns.USER_ID, UserIdsColumns.USER_ID);
-                projectionMap.put(Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID, Tables.KEY_RINGS_SECRET + "." + KeyRingsColumns.MASTER_KEY_ID);
+                projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
+                projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
+                projectionMap.put(KeyRings.KEY_ID, Keys.KEY_ID);
+                projectionMap.put(KeyRings.KEY_SIZE, Keys.KEY_SIZE);
+                projectionMap.put(KeyRings.IS_REVOKED, Keys.IS_REVOKED);
+                projectionMap.put(KeyRings.CAN_CERTIFY, Keys.CAN_CERTIFY);
+                projectionMap.put(KeyRings.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
+                projectionMap.put(KeyRings.CAN_SIGN, Keys.CAN_SIGN);
+                projectionMap.put(KeyRings.CREATION, Keys.CREATION);
+                projectionMap.put(KeyRings.EXPIRY, Keys.EXPIRY);
+                projectionMap.put(KeyRings.ALGORITHM, Keys.ALGORITHM);
+                projectionMap.put(KeyRings.FINGERPRINT, Keys.FINGERPRINT);
+                projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID);
+                projectionMap.put(KeyRings.HAS_SECRET, "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL) AS " + KeyRings.HAS_SECRET);
                 qb.setProjectionMap(projectionMap);
 
                 qb.setTables(
                     Tables.KEYS
                         + " INNER JOIN " + Tables.USER_IDS + " ON ("
-                                    + Tables.KEYS + "." + KeysColumns.MASTER_KEY_ID
+                                    + Tables.KEYS + "." + Keys.MASTER_KEY_ID
                                 + " = "
-                                    + Tables.USER_IDS + "." + UserIdsColumns.MASTER_KEY_ID
-                            + " AND " + Tables.USER_IDS + "." + UserIdsColumns.RANK + " = 0"
+                                    + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+                            + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0"
                         + ") LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON ("
-                            + Tables.KEYS + "." + KeysColumns.MASTER_KEY_ID
+                            + Tables.KEYS + "." + Keys.MASTER_KEY_ID
                                 + " = "
-                            + Tables.KEY_RINGS_SECRET + "." + KeyRingsColumns.MASTER_KEY_ID
+                            + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID
                         + ")"
                     );
-                qb.appendWhere(Tables.KEYS + "." + KeysColumns.RANK + " = 0");
+                qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0");
 
-                if(match == KEY_RING_UNIFIED) {
-                    qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.MASTER_KEY_ID + " = ");
-                    qb.appendWhereEscapeString(uri.getPathSegments().get(1));
-                } else if (TextUtils.isEmpty(sortOrder)) {
-                    sortOrder =
-                            Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NULL DESC"
-                            + Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+                switch(match) {
+                    case KEY_RING_UNIFIED: {
+                        qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = ");
+                        qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+                        break;
+                    }
+                    case KEY_RINGS_FIND_BY_SUBKEY: {
+                        try {
+                            String subkey = Long.valueOf(uri.getLastPathSegment()).toString();
+                            qb.appendWhere(" AND EXISTS ("
+                                    + " SELECT 1 FROM " + Tables.KEYS + " AS tmp"
+                                    + " WHERE tmp." + UserIds.MASTER_KEY_ID
+                                    + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+                                    + " AND tmp." + Keys.KEY_ID + " = " + subkey + ""
+                                    + ")");
+                        } catch(NumberFormatException e) {
+                            Log.e(Constants.TAG, "Malformed find by subkey query!", e);
+                            qb.appendWhere(" AND 0");
+                        }
+                        break;
+                    }
+                    case KEY_RINGS_FIND_BY_EMAIL: {
+                        String chunks[] = uri.getLastPathSegment().split(" *, *");
+                        boolean gotCondition = false;
+                        String emailWhere = "";
+                        // JAVA ♥
+                        for (int i = 0; i < chunks.length; ++i) {
+                            if (chunks[i].length() == 0) {
+                                continue;
+                            }
+                            if (i != 0) {
+                                emailWhere += " OR ";
+                            }
+                            emailWhere += "tmp." + UserIds.USER_ID + " LIKE ";
+                            // match '*', so it has to be at the *end* of the user id
+                            emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
+                            gotCondition = true;
+                        }
+                        if(gotCondition) {
+                            qb.appendWhere(" AND EXISTS ("
+                                + " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp"
+                                    + " WHERE tmp." + UserIds.MASTER_KEY_ID
+                                            + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+                                        + " AND (" + emailWhere + ")"
+                                + ")");
+                        } else {
+                            // TODO better way to do this?
+                            Log.e(Constants.TAG, "Malformed find by email query!");
+                            qb.appendWhere(" AND 0");
+                        }
+                        break;
+                    }
                 }
 
+                if (TextUtils.isEmpty(sortOrder)) {
+                    sortOrder =
+                            Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NULL ASC, "
+                                    + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC";
+                }
+
+                // uri to watch is all /key_rings/
+                uri = KeyRings.CONTENT_URI;
+
                 break;
             }
-            /*case SECRET_KEY_RING_BY_EMAILS:
-            case PUBLIC_KEY_RING_BY_EMAILS:
-                qb = buildKeyRingQuery(qb, match);
-
-                String emails = uri.getLastPathSegment();
-                String chunks[] = emails.split(" *, *");
-                boolean gotCondition = false;
-                String emailWhere = "";
-                for (int i = 0; i < chunks.length; ++i) {
-                    if (chunks[i].length() == 0) {
-                        continue;
-                    }
-                    if (i != 0) {
-                        emailWhere += " OR ";
-                    }
-                    emailWhere += "tmp." + UserIdsColumns.USER_ID + " LIKE ";
-                    // match '*', so it has to be at the *end* of the user id
-                    emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
-                    gotCondition = true;
-                }
-
-                if (gotCondition) {
-                    qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM "
-                            + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
-                            + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + emailWhere
-                            + "))");
-                }*/
 
             case KEY_RING_KEYS: {
                 HashMap projectionMap = new HashMap();
-                projectionMap.put(BaseColumns._ID, Tables.KEYS + ".oid AS _id");
-                projectionMap.put(KeysColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.MASTER_KEY_ID);
-                projectionMap.put(KeysColumns.RANK, Tables.KEYS + "." + KeysColumns.RANK);
-                projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID);
-                projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE);
-                projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED);
-                projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY);
-                projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT);
-                projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN);
-                projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION);
-                projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY);
-                projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM);
-                projectionMap.put(KeysColumns.FINGERPRINT, KeysColumns.FINGERPRINT);
+                projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id");
+                projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
+                projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
+                projectionMap.put(Keys.KEY_ID, Keys.KEY_ID);
+                projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
+                projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED);
+                projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY);
+                projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
+                projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN);
+                projectionMap.put(Keys.CREATION, Keys.CREATION);
+                projectionMap.put(Keys.EXPIRY, Keys.EXPIRY);
+                projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM);
+                projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT);
                 qb.setProjectionMap(projectionMap);
 
                 qb.setTables(Tables.KEYS);
-                qb.appendWhere(KeysColumns.MASTER_KEY_ID + " = ");
+                qb.appendWhere(Keys.MASTER_KEY_ID + " = ");
                 qb.appendWhereEscapeString(uri.getPathSegments().get(1));
 
                 break;
@@ -312,7 +357,7 @@ public class KeychainProvider extends ContentProvider {
 
             case KEY_RING_USER_IDS: {
                 HashMap projectionMap = new HashMap();
-                projectionMap.put(BaseColumns._ID, Tables.USER_IDS + ".oid AS _id");
+                projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id");
                 projectionMap.put(UserIds.MASTER_KEY_ID, UserIds.MASTER_KEY_ID);
                 projectionMap.put(UserIds.USER_ID, UserIds.USER_ID);
                 projectionMap.put(UserIds.RANK, UserIds.RANK);
@@ -320,36 +365,44 @@ public class KeychainProvider extends ContentProvider {
                 qb.setProjectionMap(projectionMap);
 
                 qb.setTables(Tables.USER_IDS);
-                qb.appendWhere(UserIdsColumns.MASTER_KEY_ID + " = ");
+                qb.appendWhere(UserIds.MASTER_KEY_ID + " = ");
                 qb.appendWhereEscapeString(uri.getPathSegments().get(1));
 
+                if (TextUtils.isEmpty(sortOrder)) {
+                    sortOrder = UserIds.RANK + " ASC";
+                }
+
                 break;
 
             }
 
+            case KEY_RINGS_PUBLIC:
             case KEY_RING_PUBLIC: {
                 HashMap projectionMap = new HashMap();
-                projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id");
-                projectionMap.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID);
-                projectionMap.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA);
+                projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id");
+                projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
+                projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
                 qb.setProjectionMap(projectionMap);
 
                 qb.setTables(Tables.KEY_RINGS_PUBLIC);
-                qb.appendWhere(KeyRingsColumns.MASTER_KEY_ID + " = ");
-                qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+                if(match == KEY_RING_PUBLIC) {
+                    qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
+                    qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+                }
 
                 break;
             }
 
             case KEY_RING_SECRET: {
                 HashMap projectionMap = new HashMap();
-                projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
-                projectionMap.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID);
-                projectionMap.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA);
+                projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
+                projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
+                projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
                 qb.setProjectionMap(projectionMap);
 
                 qb.setTables(Tables.KEY_RINGS_SECRET);
-                qb.appendWhere(KeyRingsColumns.MASTER_KEY_ID + " = ");
+                qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
                 qb.appendWhereEscapeString(uri.getPathSegments().get(1));
 
                 break;
@@ -381,7 +434,7 @@ public class KeychainProvider extends ContentProvider {
 
                 break;
             default:
-                throw new IllegalArgumentException("Unknown URI " + uri);
+                throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
 
         }
 
@@ -393,6 +446,7 @@ public class KeychainProvider extends ContentProvider {
             orderBy = sortOrder;
         }
 
+        SQLiteDatabase db = getDb().getReadableDatabase();
         Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
 
         // Tell the cursor what uri to watch, so it knows when its source data changes
@@ -416,7 +470,7 @@ public class KeychainProvider extends ContentProvider {
     public Uri insert(Uri uri, ContentValues values) {
         Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
 
-        final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
+        final SQLiteDatabase db = getDb().getWritableDatabase();
 
         Uri rowUri = null;
         Long keyId = null;
@@ -426,23 +480,23 @@ public class KeychainProvider extends ContentProvider {
             switch (match) {
                 case KEY_RING_PUBLIC:
                     db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
-                    keyId = values.getAsLong(KeyRingsColumns.MASTER_KEY_ID);
+                    keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
                     break;
 
                 case KEY_RING_SECRET:
                     db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values);
-                    keyId = values.getAsLong(KeyRingsColumns.MASTER_KEY_ID);
+                    keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
                     break;
 
                 case KEY_RING_KEYS:
                     Log.d(Constants.TAG, "keys");
                     db.insertOrThrow(Tables.KEYS, null, values);
-                    keyId = values.getAsLong(KeysColumns.MASTER_KEY_ID);
+                    keyId = values.getAsLong(Keys.MASTER_KEY_ID);
                     break;
 
                 case KEY_RING_USER_IDS:
                     db.insertOrThrow(Tables.USER_IDS, null, values);
-                    keyId = values.getAsLong(UserIdsColumns.MASTER_KEY_ID);
+                    keyId = values.getAsLong(UserIds.MASTER_KEY_ID);
                     break;
 
                 case API_APPS:
@@ -489,7 +543,7 @@ public class KeychainProvider extends ContentProvider {
     public int delete(Uri uri, String additionalSelection, String[] selectionArgs) {
         Log.v(Constants.TAG, "delete(uri=" + uri + ")");
 
-        final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
+        final SQLiteDatabase db = getDb().getWritableDatabase();
 
         int count;
         final int match = mUriMatcher.match(uri);
@@ -503,6 +557,7 @@ public class KeychainProvider extends ContentProvider {
                 }
                 // corresponding keys and userIds are deleted by ON DELETE CASCADE
                 count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs);
+                uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1));
                 break;
             }
             case KEY_RING_SECRET: {
@@ -512,6 +567,7 @@ public class KeychainProvider extends ContentProvider {
                     selection += " AND (" + additionalSelection + ")";
                 }
                 count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs);
+                uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1));
                 break;
             }
 
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 3f96a51ef..581ddb378 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -23,7 +23,10 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.CursorWrapper;
 import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteCursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
@@ -40,9 +43,9 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
 import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
 import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
 import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
 import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
-import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
 import org.sufficientlysecure.keychain.remote.AccountSettings;
 import org.sufficientlysecure.keychain.remote.AppSettings;
 import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -52,21 +55,81 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
 
 public class ProviderHelper {
 
-    /**
-     * Private helper method to get PGPKeyRing from database
+    // If we ever switch to api level 11, we can ditch this whole mess!
+    public static final int FIELD_TYPE_NULL = 1;
+    // this is called integer to stay coherent with the constants in Cursor (api level 11)
+    public static final int FIELD_TYPE_INTEGER = 2;
+    public static final int FIELD_TYPE_FLOAT = 3;
+    public static final int FIELD_TYPE_STRING = 4;
+    public static final int FIELD_TYPE_BLOB = 5;
+
+    public static Object getGenericData(Context context, Uri uri, String column, int type) {
+        return getGenericData(context, uri, new String[] { column }, new int[] { type }).get(column);
+    }
+    public static HashMap getGenericData(Context context, Uri uri, String[] proj, int[] types) {
+        Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
+
+        HashMap result = new HashMap(proj.length);
+        if (cursor != null && cursor.moveToFirst()) {
+            int pos = 0;
+            for(String p : proj) {
+                switch(types[pos]) {
+                    case FIELD_TYPE_NULL: result.put(p, cursor.isNull(pos)); break;
+                    case FIELD_TYPE_INTEGER: result.put(p, cursor.getLong(pos)); break;
+                    case FIELD_TYPE_FLOAT: result.put(p, cursor.getFloat(pos)); break;
+                    case FIELD_TYPE_STRING: result.put(p, cursor.getString(pos)); break;
+                    case FIELD_TYPE_BLOB: result.put(p, cursor.getBlob(pos)); break;
+                }
+                pos += 1;
+            }
+        }
+
+        if (cursor != null) {
+            cursor.close();
+        }
+
+        return result;
+    }
+
+    public static Object getUnifiedData(Context context, long masterKeyId, String column, int type) {
+        return getUnifiedData(context, masterKeyId, new String[] { column }, new int[] { type }).get(column);
+    }
+    public static HashMap getUnifiedData(Context context, long masterKeyId, String[] proj, int[] types) {
+        return getGenericData(context, KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), proj, types);
+    }
+
+    /** Find the master key id related to a given query. The id will either be extracted from the
+     * query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
      */
+    public static long getMasterKeyId(Context context, Uri queryUri) {
+        // try extracting from the uri first
+        String firstSegment = queryUri.getPathSegments().get(1);
+        if(!firstSegment.equals("find")) try {
+            return Long.parseLong(firstSegment);
+        } catch(NumberFormatException e) {
+            // didn't work? oh well.
+            Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying...");
+        }
+        Object data = getGenericData(context, queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
+        if(data != null)
+            return (Long) data;
+        // TODO better error handling?
+        return 0L;
+    }
+
     public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
         Cursor cursor = context.getContentResolver().query(queryUri,
-                new String[]{KeyRings._ID, KeyRings.KEY_RING_DATA}, null, null, null);
+                new String[]{KeyRings._ID, KeyRingData.KEY_RING_DATA}, null, null, null);
 
         PGPKeyRing keyRing = null;
         if (cursor != null && cursor.moveToFirst()) {
-            int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+            int keyRingDataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
 
             byte[] data = cursor.getBlob(keyRingDataCol);
             if (data != null) {
@@ -81,19 +144,18 @@ public class ProviderHelper {
         return keyRing;
     }
 
-    public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
-        return getPGPPublicKeyRingWithKeyId(context, keyId).getPublicKey(keyId);
-    }
     public static PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(Context context, long keyId) {
-        // todo do
+        Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId));
+        long masterKeyId = getMasterKeyId(context, uri);
+        if(masterKeyId != 0)
+            return getPGPPublicKeyRing(context, masterKeyId);
         return null;
     }
-
-    public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
-        return getPGPSecretKeyRingWithKeyId(context, keyId).getSecretKey(keyId);
-    }
     public static PGPSecretKeyRing getPGPSecretKeyRingWithKeyId(Context context, long keyId) {
-        // todo do
+        Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId));
+        long masterKeyId = getMasterKeyId(context, uri);
+        if(masterKeyId != 0)
+            return getPGPSecretKeyRing(context, masterKeyId);
         return null;
     }
 
@@ -102,7 +164,7 @@ public class ProviderHelper {
      */
     public static PGPPublicKeyRing getPGPPublicKeyRing(Context context,
                                                                     long masterKeyId) {
-        Uri queryUri = KeyRings.buildPublicKeyRingUri(Long.toString(masterKeyId));
+        Uri queryUri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
         return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
     }
 
@@ -111,7 +173,7 @@ public class ProviderHelper {
      */
     public static PGPSecretKeyRing getPGPSecretKeyRing(Context context,
                                                                     long masterKeyId) {
-        Uri queryUri = KeyRings.buildSecretKeyRingUri(Long.toString(masterKeyId));
+        Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
         return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
     }
 
@@ -124,12 +186,11 @@ public class ProviderHelper {
         long masterKeyId = masterKey.getKeyID();
 
         // IF there is a secret key, preserve it!
-        // TODO This even necessary?
-        // PGPSecretKeyRing secretRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
+        PGPSecretKeyRing secretRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
 
         // delete old version of this keyRing, which also deletes all keys and userIds on cascade
         try {
-            context.getContentResolver().delete(KeyRings.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
+            context.getContentResolver().delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
         } catch (UnsupportedOperationException e) {
             Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
         }
@@ -139,11 +200,11 @@ public class ProviderHelper {
         // NOTE: If we would not use the same _ID again,
         // getting back to the ViewKeyActivity would result in Nullpointer,
         // because the currently loaded key would be gone from the database
-        values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
-        values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+        values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
+        values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
 
         // insert new version of this keyRing
-        Uri uri = KeyRings.buildPublicKeyRingUri(Long.toString(masterKeyId));
+        Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
         Uri insertedUri = context.getContentResolver().insert(uri, values);
 
         // save all keys and userIds included in keyRing object in database
@@ -179,31 +240,43 @@ public class ProviderHelper {
         }
 
         // Save the saved keyring (if any)
-        // TODO this even necessary? see above...
-        // if(secretRing != null)
-            // saveKeyRing(context, secretRing);
+        if(secretRing != null) {
+            saveKeyRing(context, secretRing);
+        }
 
     }
 
     /**
-     * Saves PGPSecretKeyRing with its keys and userIds in DB
+     * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring
+     * is already in the database!
      */
     @SuppressWarnings("unchecked")
     public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
-        PGPSecretKey masterKey = keyRing.getSecretKey();
-        long masterKeyId = masterKey.getKeyID();
+        long masterKeyId = keyRing.getPublicKey().getKeyID();
 
-        // TODO Make sure there is a public key for this secret key in the db (create one maybe)
+        // save secret keyring
+        ContentValues values = new ContentValues();
+        values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
+        values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
+        // insert new version of this keyRing
+        Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
+        context.getContentResolver().insert(uri, values);
 
-        {
-            ContentValues values = new ContentValues();
-            values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
-            values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
-            // insert new version of this keyRing
-            Uri uri = KeyRings.buildSecretKeyRingUri(Long.toString(masterKeyId));
-            context.getContentResolver().insert(uri, values);
-        }
+    }
 
+    /**
+     * Saves (or updates) a pair of public and secret KeyRings in the database
+     */
+    @SuppressWarnings("unchecked")
+    public static void saveKeyRing(Context context, PGPPublicKeyRing pubRing, PGPSecretKeyRing privRing) throws IOException {
+        long masterKeyId = pubRing.getPublicKey().getKeyID();
+
+        // delete secret keyring (so it isn't unnecessarily saved by public-saveKeyRing below)
+        context.getContentResolver().delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null);
+
+        // save public keyring
+        saveKeyRing(context, pubRing);
+        saveKeyRing(context, privRing);
     }
 
     /**
@@ -260,190 +333,22 @@ public class ProviderHelper {
         return ContentProviderOperation.newInsert(uri).withValues(values).build();
     }
 
-    /**
-     * Private helper method
-     */
-    private static ArrayList getKeyRingsMasterKeyIds(Context context, Uri queryUri) {
-        Cursor cursor = context.getContentResolver().query(queryUri,
-                new String[]{KeyRings.MASTER_KEY_ID}, null, null, null);
-
-        ArrayList masterKeyIds = new ArrayList();
-        if (cursor != null) {
-            int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
-            if (cursor.moveToFirst()) {
-                do {
-                    masterKeyIds.add(cursor.getLong(masterKeyIdCol));
-                } while (cursor.moveToNext());
-            }
-        }
-
-        if (cursor != null) {
-            cursor.close();
-        }
-
-        return masterKeyIds;
-    }
-
-    public static void deletePublicKeyRing(Context context, long masterKeyId) {
-        ContentResolver cr = context.getContentResolver();
-        cr.delete(KeyRings.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
-    }
-
-    public static void deleteSecretKeyRing(Context context, long masterKeyId) {
-        ContentResolver cr = context.getContentResolver();
-        cr.delete(KeyRings.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null);
-    }
-
-    public static boolean getMasterKeyCanCertify(Context context, Uri queryUri) {
-        // TODO redo
-
-        return false;
-
-        /*
-            String[] projection = new String[]{
-                    KeyRings.MASTER_KEY_ID,
-                    "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
-                            + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
-                            + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
-                            + " AND sign_keys." + Keys.CAN_CERTIFY + " = '1' AND " + Keys.IS_MASTER_KEY
-                            + " = 1) AS sign",};
-
-            ContentResolver cr = context.getContentResolver();
-            Cursor cursor = cr.query(queryUri, projection, null, null, null);
-
-            long masterKeyId = -1;
-            if (cursor != null && cursor.moveToFirst()) {
-                int masterKeyIdCol = cursor.getColumnIndex("sign");
-
-                masterKeyId = cursor.getLong(masterKeyIdCol);
-            }
-
-            if (cursor != null) {
-                cursor.close();
-            }
-
-            return (masterKeyId > 0);
-        */
-    }
-
     public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) {
-        Uri queryUri = KeyRings.buildSecretKeyRingUri(Long.toString(masterKeyId));
+        Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
         // see if we can get our master key id back from the uri
         return getMasterKeyId(context, queryUri) == masterKeyId;
     }
 
-    /**
-     * Get master key id of key
-     */
-    public static long getMasterKeyId(Context context, Uri queryUri) {
-        String[] projection = new String[]{KeyRings.MASTER_KEY_ID};
-        Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
-
-        long masterKeyId = 0;
-        try {
-            if (cursor != null && cursor.moveToFirst()) {
-                int masterKeyIdCol = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
-
-                masterKeyId = cursor.getLong(masterKeyIdCol);
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        return masterKeyId;
-    }
-
-    public static long getRowId(Context context, Uri queryUri) {
-        String[] projection = new String[]{KeyRings._ID};
-        Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
-
-        long rowId = 0;
-        try {
-            if (cursor != null && cursor.moveToFirst()) {
-                int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID);
-
-                rowId = cursor.getLong(idCol);
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        return rowId;
-    }
-
-    /**
-     * Get fingerprint of key
-     */
-    public static byte[] getFingerprint(Context context, Uri queryUri) {
-        String[] projection = new String[]{Keys.FINGERPRINT};
-        Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
-
-        byte[] fingerprint = null;
-        try {
-            if (cursor != null && cursor.moveToFirst()) {
-                int col = cursor.getColumnIndexOrThrow(Keys.FINGERPRINT);
-
-                fingerprint = cursor.getBlob(col);
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        // FALLBACK: If fingerprint is not in database, get it from key blob!
-        // this could happen if the key was saved by a previous version of Keychain!
-        if (fingerprint == null) {
-            Log.d(Constants.TAG, "FALLBACK: fingerprint is not in database, get it from key blob!");
-
-            // get master key id
-            projection = new String[]{KeyRings.MASTER_KEY_ID};
-            cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
-            long masterKeyId = 0;
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    int col = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
-
-                    masterKeyId = cursor.getLong(col);
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-
-            PGPPublicKey key = ProviderHelper.getPGPPublicKeyRing(context, masterKeyId).getPublicKey();
-            // if it is no public key get it from your own keys...
-            if (key == null) {
-                PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId).getSecretKey();
-                if (secretKey == null) {
-                    Log.e(Constants.TAG, "Key could not be found!");
-                    return null;
-                }
-                key = secretKey.getPublicKey();
-            }
-
-            fingerprint = key.getFingerprint();
-        }
-
-        return fingerprint;
-    }
-
-    public static ArrayList getKeyRingsAsArmoredString(Context context, Uri uri,
-                                                               long[] masterKeyIds) {
+    public static ArrayList getKeyRingsAsArmoredString(Context context, long[] masterKeyIds) {
         ArrayList output = new ArrayList();
 
         if (masterKeyIds != null && masterKeyIds.length > 0) {
 
-            Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
+            Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, masterKeyIds);
 
             if (cursor != null) {
-                int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
-                int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+                int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID);
+                int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
                 if (cursor.moveToFirst()) {
                     do {
                         Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
@@ -493,48 +398,11 @@ public class ProviderHelper {
             return null;
         }
     }
-
-    public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-
-        if (masterKeyIds != null && masterKeyIds.length > 0) {
-
-            Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
-
-            if (cursor != null) {
-                int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
-                int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
-                if (cursor.moveToFirst()) {
-                    do {
-                        Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
-
-                        // get actual keyring data blob and write it to ByteArrayOutputStream
-                        try {
-                            bos.write(cursor.getBlob(dataCol));
-                        } catch (IOException e) {
-                            Log.e(Constants.TAG, "IOException", e);
-                        }
-                    } while (cursor.moveToNext());
-                }
-            }
-
-            if (cursor != null) {
-                cursor.close();
-            }
-
-        } else {
-            Log.e(Constants.TAG, "No master keys given!");
-        }
-
-        return bos.toByteArray();
-    }
-
-    private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, Uri baseUri,
-                                                                   long[] masterKeyIds) {
+    private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, long[] masterKeyIds) {
         Cursor cursor = null;
         if (masterKeyIds != null && masterKeyIds.length > 0) {
 
-            String inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
+            String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN (";
             for (int i = 0; i < masterKeyIds.length; ++i) {
                 if (i != 0) {
                     inMasterKeyList += ", ";
@@ -543,9 +411,9 @@ public class ProviderHelper {
             }
             inMasterKeyList += ")";
 
-            cursor = context.getContentResolver().query(baseUri,
-                    new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.KEY_RING_DATA},
-                    inMasterKeyList, null, null);
+            cursor = context.getContentResolver().query(KeyRingData.buildPublicKeyRingUri(), new String[] {
+                    KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA
+                }, inMasterKeyList, null, null);
         }
 
         return cursor;
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index e6e6ba31b..b38fea5a9 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -35,7 +35,8 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
 import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
 import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
 import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
 import org.sufficientlysecure.keychain.service.PassphraseCacheService;
@@ -61,15 +62,15 @@ public class OpenPgpService extends RemoteService {
         ArrayList keyIds = new ArrayList();
 
         boolean missingUserIdsCheck = false;
-        boolean dublicateUserIdsCheck = false;
+        boolean duplicateUserIdsCheck = false;
         ArrayList missingUserIds = new ArrayList();
-        ArrayList dublicateUserIds = new ArrayList();
+        ArrayList duplicateUserIds = new ArrayList();
 
         for (String email : encryptionUserIds) {
-            Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingsByEmailUri(email);
+            Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
             Cursor cur = getContentResolver().query(uri, null, null, null, null);
             if (cur.moveToFirst()) {
-                long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID));
+                long id = cur.getLong(cur.getColumnIndex(KeyRings.MASTER_KEY_ID));
                 keyIds.add(id);
             } else {
                 missingUserIdsCheck = true;
@@ -77,8 +78,8 @@ public class OpenPgpService extends RemoteService {
                 Log.d(Constants.TAG, "user id missing");
             }
             if (cur.moveToNext()) {
-                dublicateUserIdsCheck = true;
-                dublicateUserIds.add(email);
+                duplicateUserIdsCheck = true;
+                duplicateUserIds.add(email);
                 Log.d(Constants.TAG, "more than one user id with the same email");
             }
         }
@@ -90,13 +91,13 @@ public class OpenPgpService extends RemoteService {
         }
 
         // allow the user to verify pub key selection
-        if (missingUserIdsCheck || dublicateUserIdsCheck) {
+        if (missingUserIdsCheck || duplicateUserIdsCheck) {
             // build PendingIntent
             Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
             intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
             intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
             intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
-            intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
+            intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds);
             intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
 
             PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
@@ -351,7 +352,7 @@ public class OpenPgpService extends RemoteService {
         try {
             long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
 
-            if (ProviderHelper.getPGPPublicKeyByKeyId(this, keyId) == null) {
+            if (ProviderHelper.getPGPPublicKeyRing(this, keyId) == null) {
                 Intent result = new Intent();
 
                 // If keys are not in db we return an additional PendingIntent
@@ -462,7 +463,7 @@ public class OpenPgpService extends RemoteService {
                 String currentPkg = getCurrentCallingPackage();
                 Set allowedKeyIds =
                     ProviderHelper.getAllKeyIdsForApp(mContext,
-                        KeychainContract.ApiAccounts.buildBaseUri(currentPkg));
+                        ApiAccounts.buildBaseUri(currentPkg));
                 return decryptAndVerifyImpl(data, input, output, allowedKeyIds);
             } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
                 return getKeyImpl(data);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
index dee0dae95..992aa7c95 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
@@ -34,6 +34,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
 
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.remote.AccountSettings;
 import org.sufficientlysecure.keychain.ui.EditKeyActivity;
 import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
@@ -176,9 +177,8 @@ public class AccountSettingsFragment extends Fragment implements
             case REQUEST_CODE_CREATE_KEY: {
                 if (resultCode == Activity.RESULT_OK) {
                     // select newly created key
-                    Uri newKeyUri = data.getData();
-                    // TODO helper method for this?
-                    mSelectKeyFragment.selectKey(Long.parseLong(newKeyUri.getPathSegments().get(1)));
+                    long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData());
+                    mSelectKeyFragment.selectKey(masterKeyId);
                 }
                 break;
             }
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index aea562227..1c6aa7971 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.service;
 
 import android.app.IntentService;
 import android.content.Intent;
+import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Message;
@@ -50,6 +51,8 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
 import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
 import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
 import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
 import org.sufficientlysecure.keychain.util.HkpKeyServer;
@@ -147,7 +150,7 @@ public class KeychainIntentService extends IntentService
     // export key
     public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
     public static final String EXPORT_FILENAME = "export_filename";
-    public static final String EXPORT_KEY_TYPE = "export_key_type";
+    public static final String EXPORT_SECRET = "export_secret";
     public static final String EXPORT_ALL = "export_all";
     public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
 
@@ -508,8 +511,8 @@ public class KeychainIntentService extends IntentService
                     PgpKeyOperation.Pair pair =
                         keyOperations.buildSecretKey(privkey, pubkey, saveParams);
                     setProgress(R.string.progress_saving_key_ring, 90, 100);
-                    ProviderHelper.saveKeyRing(this, pair.first);
-                    ProviderHelper.saveKeyRing(this, pair.second);
+                    // save the pair
+                    ProviderHelper.saveKeyRing(this, pair.second, pair.first);
                     setProgress(R.string.progress_done, 100, 100);
                 }
                 PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassphrase);
@@ -635,19 +638,13 @@ public class KeychainIntentService extends IntentService
         } else if (ACTION_EXPORT_KEYRING.equals(action)) {
             try {
 
-                /* Input */
-                int keyType = Id.type.public_key;
-                if (data.containsKey(EXPORT_KEY_TYPE)) {
-                    keyType = data.getInt(EXPORT_KEY_TYPE);
-                }
+                boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
                 long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
                 String outputFile = data.getString(EXPORT_FILENAME);
 
                 // If not exporting all keys get the masterKeyIds of the keys to export from the intent
                 boolean exportAll = data.getBoolean(EXPORT_ALL);
 
-                /* Operation */
-
                 // check if storage is ready
                 if (!FileHelper.isStorageMounted(outputFile)) {
                     throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
@@ -655,31 +652,30 @@ public class KeychainIntentService extends IntentService
 
                 ArrayList publicMasterKeyIds = new ArrayList();
                 ArrayList secretMasterKeyIds = new ArrayList();
-                // TODO redo
-                ArrayList allPublicMasterKeyIds = null; // ProviderHelper.getPublicKeyRingsMasterKeyIds(this);
-                ArrayList allSecretMasterKeyIds = null; // ProviderHelper.getSecretKeyRingsMasterKeyIds(this);
 
-                if (exportAll) {
-                    // get all public key ring MasterKey ids
-                    if (keyType == Id.type.public_key || keyType == Id.type.public_secret_key) {
-                        publicMasterKeyIds = allPublicMasterKeyIds;
+                String selection = null;
+                if(!exportAll) {
+                    selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( ";
+                    for(long l : masterKeyIds) {
+                        selection += Long.toString(l) + ",";
                     }
-                    // get all secret key ring MasterKey ids
-                    if (keyType == Id.type.secret_key || keyType == Id.type.public_secret_key) {
-                        secretMasterKeyIds = allSecretMasterKeyIds;
-                    }
-                } else {
+                    selection = selection.substring(0, selection.length()-1) + " )";
+                }
 
-                    for (long masterKeyId : masterKeyIds) {
-                        if ((keyType == Id.type.public_key || keyType == Id.type.public_secret_key)
-                                && allPublicMasterKeyIds.contains(masterKeyId)) {
-                            publicMasterKeyIds.add(masterKeyId);
-                        }
-                        if ((keyType == Id.type.secret_key || keyType == Id.type.public_secret_key)
-                                && allSecretMasterKeyIds.contains(masterKeyId)) {
-                            secretMasterKeyIds.add(masterKeyId);
-                        }
-                    }
+                Cursor cursor = getContentResolver().query(KeyRings.buildUnifiedKeyRingsUri(),
+                        new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET },
+                        selection, null, null);
+                try {
+                    cursor.moveToFirst();
+                    do {
+                        // export public either way
+                        publicMasterKeyIds.add(cursor.getLong(0));
+                        // add secret if available (and requested)
+                        if(exportSecret && cursor.getInt(1) != 0)
+                            secretMasterKeyIds.add(cursor.getLong(0));
+                    } while(cursor.moveToNext());
+                } finally {
+                    cursor.close();
                 }
 
                 PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index 63703a02b..962b304c7 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.Constants;
 import org.sufficientlysecure.keychain.Id;
 import org.sufficientlysecure.keychain.helper.Preferences;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 
 import java.util.Date;
@@ -170,15 +171,11 @@ public class PassphraseCacheService extends Service {
         // try to get master key id which is used as an identifier for cached passphrases
         long masterKeyId = keyId;
         if (masterKeyId != Id.key.symmetric) {
-            PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(this, keyId);
-            if (keyRing == null) {
+            masterKeyId = ProviderHelper.getMasterKeyId(this,
+                    KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)));
+            // Failure
+            if(masterKeyId == 0)
                 return null;
-            }
-            PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing);
-            if (masterKey == null) {
-                return null;
-            }
-            masterKeyId = masterKey.getKeyID();
         }
         Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
 
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
index c44686cb1..60bababd1 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
@@ -57,6 +57,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
 import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
 import org.sufficientlysecure.keychain.provider.KeychainContract;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
 import org.sufficientlysecure.keychain.service.KeychainIntentService;
 import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
 import org.sufficientlysecure.keychain.service.PassphraseCacheService;
@@ -276,8 +277,6 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
 
             // get master key id using row id
             long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
-
-            mMasterCanSign = ProviderHelper.getMasterKeyCanCertify(this, mDataUri);
             finallyEdit(masterKeyId);
         }
     }
@@ -312,15 +311,13 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
                     Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show();
                 } else {
                     long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
-                long[] ids = new long[]{masterKeyId};
-                mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC,
-                        null);
+                    mExportHelper.showExportKeysDialog(
+                            new long[] { masterKeyId }, Constants.Path.APP_DIR_FILE_SEC, true);
                     return true;
                 }
                 return true;
             case R.id.menu_key_edit_delete:
-                long rowId= ProviderHelper.getRowId(this,mDataUri);
-                Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingUri(Long.toString(rowId));
+                Uri convertUri = KeyRingData.buildSecretKeyRingUri(mDataUri);
                     // Message is received after key is deleted
                     Handler returnHandler = new Handler() {
                         @Override
@@ -346,7 +343,8 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
             PGPSecretKey masterKey = null;
             mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
             if (mKeyRing != null) {
-                masterKey = PgpKeyHelper.getMasterKey(mKeyRing);
+                masterKey = mKeyRing.getSecretKey();
+                mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
                 for (PGPSecretKey key : new IterableIterator(mKeyRing.getSecretKeys())) {
                     mKeys.add(key);
                     mKeysUsages.add(-1); // get usage when view is created
@@ -354,6 +352,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
             } else {
                 Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
                 Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
+                // TODO
             }
             if (masterKey != null) {
                 boolean isSet = false;
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
index 7ae48f9be..6e84211cc 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
@@ -37,8 +37,10 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
 import org.sufficientlysecure.keychain.Id;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 
+import java.util.HashMap;
 import java.util.Vector;
 
 public class EncryptAsymmetricFragment extends Fragment {
@@ -145,7 +147,7 @@ public class EncryptAsymmetricFragment extends Fragment {
                     preselectedSignatureKeyId);
             PGPSecretKey masterKey;
             if (keyRing != null) {
-                masterKey = PgpKeyHelper.getMasterKey(keyRing);
+                masterKey = keyRing.getSecretKey();
                 if (masterKey != null) {
                     Vector signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing);
                     if (signKeys.size() > 0) {
@@ -158,23 +160,11 @@ public class EncryptAsymmetricFragment extends Fragment {
         if (preselectedEncryptionKeyIds != null) {
             Vector goodIds = new Vector();
             for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
-                // TODO: don't use bouncy castle objects!
-
-                PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(getActivity(),
-                        preselectedEncryptionKeyIds[i]);
-                PGPPublicKey masterKey;
-                if (keyRing == null) {
-                    continue;
-                }
-                masterKey = PgpKeyHelper.getMasterKey(keyRing);
-                if (masterKey == null) {
-                    continue;
-                }
-                Vector encryptKeys = PgpKeyHelper.getUsableEncryptKeys(keyRing);
-                if (encryptKeys.size() == 0) {
-                    continue;
-                }
-                goodIds.add(masterKey.getKeyID());
+                long id = ProviderHelper.getMasterKeyId(getActivity(),
+                        KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(preselectedEncryptionKeyIds[i]))
+                );
+                // TODO check for available encrypt keys... is this even relevant?
+                goodIds.add(id);
             }
             if (goodIds.size() > 0) {
                 long[] keyIds = new long[goodIds.size()];
@@ -202,20 +192,17 @@ public class EncryptAsymmetricFragment extends Fragment {
         } else {
             String uid = getResources().getString(R.string.user_id_no_name);
             String uidExtra = "";
-            // TODO: don't use bouncy castle objects!
-            PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(getActivity(),
-                    mSecretKeyId);
-            if (keyRing != null) {
-                PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
-                if (key != null) {
-                    String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
-                    String chunks[] = userId.split(" <", 2);
-                    uid = chunks[0];
-                    if (chunks.length > 1) {
-                        uidExtra = "<" + chunks[1];
-                    }
+            // See if we can get a user_id from a unified query
+            String user_id = (String) ProviderHelper.getUnifiedData(
+                    getActivity(), mSecretKeyId, KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING);
+            if(user_id != null) {
+                String chunks[] = user_id.split(" <", 2);
+                uid = chunks[0];
+                if (chunks.length > 1) {
+                    uidExtra = "<" + chunks[1];
                 }
             }
+
             mMainUserId.setText(uid);
             mMainUserIdRest.setText(uidExtra);
             mSign.setChecked(true);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
index 1f884d7e3..1bc6d4ee1 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
@@ -23,7 +23,6 @@ import android.view.Menu;
 import android.view.MenuItem;
 
 import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.Id;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.helper.ExportHelper;
 
@@ -55,26 +54,20 @@ public class KeyListActivity extends DrawerActivity {
         switch (item.getItemId()) {
             case R.id.menu_key_list_import:
                 callIntentForDrawerItem(Constants.DrawerItems.IMPORT_KEYS);
-
                 return true;
+
             case R.id.menu_key_list_create:
                 createKey();
-
                 return true;
+
             case R.id.menu_key_list_create_expert:
                 createKeyExpert();
-
                 return true;
-            case R.id.menu_key_list_export_public:
-                mExportHelper.showExportKeysDialog(null,
-                    Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB, null);
 
+            case R.id.menu_key_list_export:
+                mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE_PUB, true);
                 return true;
-            case R.id.menu_key_list_secret_export:
-                mExportHelper.showExportKeysDialog(null, Id.type.secret_key,
-                    Constants.Path.APP_DIR_FILE_SEC, null);
 
-                return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index 6dec5e56e..1d8f7c8ad 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -58,12 +58,8 @@ import org.sufficientlysecure.keychain.Id;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.helper.ExportHelper;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
 import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
-import org.sufficientlysecure.keychain.provider.KeychainDatabase;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
 import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
 import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
 import org.sufficientlysecure.keychain.util.Log;
@@ -71,7 +67,6 @@ import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
 import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
 import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -157,9 +152,6 @@ public class KeyListFragment extends Fragment
         } catch (ApiLevelTooLowException e) {
         }
 
-        // this view is made visible if no data is available
-        mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
-
         /*
          * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
          * available for Android >= 3.0
@@ -193,30 +185,15 @@ public class KeyListFragment extends Fragment
                             break;
                         }
                         case R.id.menu_key_list_multi_delete: {
-                            ids = mStickyList.getWrappedList().getCheckedItemIds();
+                            ids = mAdapter.getCurrentSelectedMasterKeyIds();
                             showDeleteKeyDialog(mode, ids);
                             break;
                         }
                         case R.id.menu_key_list_multi_export: {
-                            ids = mStickyList.getWrappedList().getCheckedItemIds();
-                            long[] masterKeyIds = new long[2*ids.length];
-                            /* TODO! redo
-                            ArrayList allPubRowIds =
-                                    ProviderHelper.getPublicKeyRingsRowIds(getActivity());
-                            for (int i = 0; i < ids.length; i++) {
-                                if (allPubRowIds.contains(ids[i])) {
-                                    masterKeyIds[i] =
-                                        ProviderHelper.getPublicMasterKeyId(getActivity(), ids[i]);
-                                } else {
-                                    masterKeyIds[i] =
-                                        ProviderHelper.getSecretMasterKeyId(getActivity(), ids[i]);
-                                }
-                            }*/
+                            ids = mAdapter.getCurrentSelectedMasterKeyIds();
                             ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
-                            mExportHelper
-                                    .showExportKeysDialog(masterKeyIds, Id.type.public_key,
-                                            Constants.Path.APP_DIR_FILE_PUB,
-                                            getString(R.string.also_export_secret_keys));
+                            mExportHelper.showExportKeysDialog(
+                                    ids, Constants.Path.APP_DIR_FILE_PUB, mAdapter.isAnySecretSelected());
                             break;
                         }
                         case R.id.menu_key_list_multi_select_all: {
@@ -270,11 +247,11 @@ public class KeyListFragment extends Fragment
 
     // These are the rows that we will retrieve.
     static final String[] PROJECTION = new String[]{
-            KeychainContract.KeyRings._ID,
-            KeychainContract.Keys.MASTER_KEY_ID,
-            KeychainContract.UserIds.USER_ID,
-            KeychainContract.Keys.IS_REVOKED,
-            KeychainDatabase.Tables.KEY_RINGS_SECRET + "." + KeychainContract.KeyRings.MASTER_KEY_ID
+            KeyRings._ID,
+            KeyRings.MASTER_KEY_ID,
+            KeyRings.USER_ID,
+            KeyRings.IS_REVOKED,
+            KeyRings.HAS_SECRET
     };
 
     static final int INDEX_MASTER_KEY_ID = 1;
@@ -282,12 +259,6 @@ public class KeyListFragment extends Fragment
     static final int INDEX_IS_REVOKED = 3;
     static final int INDEX_HAS_SECRET = 4;
 
-    static final String SORT_ORDER =
-            // show secret before public key
-            KeychainDatabase.Tables.KEY_RINGS_SECRET + "." + KeychainContract.KeyRings.MASTER_KEY_ID + " IS NULL ASC, " +
-                    // sort by user id otherwise
-                    UserIds.USER_ID + " ASC";
-
     @Override
     public Loader onCreateLoader(int id, Bundle args) {
         // This is called when a new Loader needs to be created. This
@@ -296,12 +267,12 @@ public class KeyListFragment extends Fragment
         String where = null;
         String whereArgs[] = null;
         if (mCurQuery != null) {
-            where = KeychainContract.UserIds.USER_ID + " LIKE ?";
+            where = KeyRings.USER_ID + " LIKE ?";
             whereArgs = new String[]{"%" + mCurQuery + "%"};
         }
         // Now create and return a CursorLoader that will take care of
         // creating a Cursor for the data being displayed.
-        return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER);
+        return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, null);
     }
 
     @Override
@@ -313,6 +284,9 @@ public class KeyListFragment extends Fragment
 
         mStickyList.setAdapter(mAdapter);
 
+        // this view is made visible if no data is available
+        mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
+
         // NOTE: Not supported by StickyListHeader, but reimplemented here
         // The list should now be shown.
         if (isResumed()) {
@@ -342,17 +316,15 @@ public class KeyListFragment extends Fragment
             viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
         }
         viewIntent.setData(
-                KeychainContract
-                        .KeyRings.buildPublicKeyRingUri(
-                                            Long.toString(mAdapter.getMasterKeyId(position))));
+                KeyRings.buildGenericKeyRingUri(Long.toString(mAdapter.getMasterKeyId(position))));
         startActivity(viewIntent);
     }
 
     @TargetApi(11)
-    protected void encrypt(ActionMode mode, long[] keyRingMasterKeyIds) {
+    protected void encrypt(ActionMode mode, long[] masterKeyIds) {
         Intent intent = new Intent(getActivity(), EncryptActivity.class);
         intent.setAction(EncryptActivity.ACTION_ENCRYPT);
-        intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingMasterKeyIds);
+        intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds);
         // used instead of startActivity set actionbar based on callingPackage
         startActivityForResult(intent, 0);
 
@@ -362,11 +334,11 @@ public class KeyListFragment extends Fragment
     /**
      * Show dialog to delete key
      *
-     * @param keyRingRowIds
+     * @param masterKeyIds
      */
     @TargetApi(11)
     // TODO: this method needs an overhaul to handle both public and secret keys gracefully!
-    public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
+    public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds) {
         // Message is received after key is deleted
         Handler returnHandler = new Handler() {
             @Override
@@ -381,7 +353,7 @@ public class KeyListFragment extends Fragment
         Messenger messenger = new Messenger(returnHandler);
 
         DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
-                keyRingRowIds);
+                masterKeyIds);
 
         deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
     }
@@ -520,7 +492,7 @@ public class KeyListFragment extends Fragment
                 Button button = (Button) view.findViewById(R.id.edit);
                 TextView revoked = (TextView) view.findViewById(R.id.revoked);
 
-                if (!cursor.isNull(KeyListFragment.INDEX_HAS_SECRET)) {
+                if (cursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
                     // this is a secret key - show the edit button
                     statusDivider.setVisibility(View.VISIBLE);
                     statusLayout.setVisibility(View.VISIBLE);
@@ -531,9 +503,7 @@ public class KeyListFragment extends Fragment
                     button.setOnClickListener(new OnClickListener() {
                         public void onClick(View view) {
                             Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
-                            editIntent.setData(
-                                    KeychainContract.KeyRings
-                                            .buildSecretKeyRingUri(Long.toString(id)));
+                            editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id)));
                             editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
                             startActivityForResult(editIntent, 0);
                         }
@@ -551,6 +521,13 @@ public class KeyListFragment extends Fragment
 
         }
 
+        public boolean isSecretAvailable(int id) {
+            if (!mCursor.moveToPosition(id)) {
+                throw new IllegalStateException("couldn't move cursor to position " + id);
+            }
+
+            return mCursor.getInt(INDEX_HAS_SECRET) != 0;
+        }
         public long getMasterKeyId(int id) {
             if (!mCursor.moveToPosition(id)) {
                 throw new IllegalStateException("couldn't move cursor to position " + id);
@@ -594,7 +571,7 @@ public class KeyListFragment extends Fragment
                 throw new IllegalStateException("couldn't move cursor to position " + position);
             }
 
-            if (!mCursor.isNull(KeyListFragment.INDEX_HAS_SECRET)) {
+            if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
                 { // set contact count
                     int num = mCursor.getCount();
                     String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num);
@@ -634,7 +611,7 @@ public class KeyListFragment extends Fragment
             }
 
             // early breakout: all secret keys are assigned id 0
-            if (!mCursor.isNull(KeyListFragment.INDEX_HAS_SECRET)) {
+            if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
                 return 1L;
             }
             // otherwise, return the first character of the name as ID
@@ -659,6 +636,14 @@ public class KeyListFragment extends Fragment
             notifyDataSetChanged();
         }
 
+        public boolean isAnySecretSelected() {
+            for (int pos : mSelection.keySet()) {
+                if(mAdapter.isSecretAvailable(pos))
+                    return true;
+            }
+            return false;
+        }
+
         public long[] getCurrentSelectedMasterKeyIds() {
             long[] ids = new long[mSelection.size()];
             int i = 0;
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index 26aeeaa22..7b9ba4b2d 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -29,6 +29,7 @@ import android.support.v7.app.ActionBar;
 import android.support.v7.app.ActionBarActivity;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.Window;
 import android.widget.Toast;
 import org.sufficientlysecure.keychain.Constants;
 import org.sufficientlysecure.keychain.Id;
@@ -41,7 +42,6 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
 import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
 import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
-import org.sufficientlysecure.keychain.util.Log;
 
 import java.util.ArrayList;
 
@@ -60,6 +60,7 @@ public class ViewKeyActivity extends ActionBarActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
         super.onCreate(savedInstanceState);
 
         mExportHelper = new ExportHelper(this);
@@ -118,10 +119,12 @@ public class ViewKeyActivity extends ActionBarActivity {
                 uploadToKeyserver(mDataUri);
                 return true;
             case R.id.menu_key_view_export_file:
-                long masterKeyId = Long.valueOf(mDataUri.getLastPathSegment());
-                long[] ids = new long[]{masterKeyId};
-                mExportHelper.showExportKeysDialog(ids, Id.type.public_key,
-                        Constants.Path.APP_DIR_FILE_PUB, null);
+                long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
+                mExportHelper.showExportKeysDialog(
+                        new long[] { masterKeyId } , Constants.Path.APP_DIR_FILE_PUB,
+                        // TODO this doesn't work?
+                        ((ViewKeyMainFragment) mTabsAdapter.getItem(0)).isSecretAvailable()
+                );
                 return true;
             case R.id.menu_key_view_share_default_fingerprint:
                 shareKey(mDataUri, true);
@@ -156,8 +159,10 @@ public class ViewKeyActivity extends ActionBarActivity {
     }
 
     private void updateFromKeyserver(Uri dataUri) {
-        byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri);
-        String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
+        byte[] blob = (byte[]) ProviderHelper.getGenericData(
+                this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
+                KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+        String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
 
         Intent queryIntent = new Intent(this, ImportKeysActivity.class);
         queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
@@ -169,15 +174,22 @@ public class ViewKeyActivity extends ActionBarActivity {
     private void shareKey(Uri dataUri, boolean fingerprintOnly) {
         String content;
         if (fingerprintOnly) {
-            byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri);
-            String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
-
-            content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+            byte[] data = (byte[]) ProviderHelper.getGenericData(
+                    this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
+                    KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+            if(data != null) {
+                String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
+                content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+            } else {
+                Toast.makeText(getApplicationContext(), "Bad key selected!",
+                        Toast.LENGTH_LONG).show();
+                return;
+            }
         } else {
             // get public keyring as ascii armored string
             long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
-            ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this,
-                    dataUri, new long[]{masterKeyId});
+            ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+                    this, new long[]{ masterKeyId });
 
             content = keyringArmored.get(0);
 
@@ -207,8 +219,8 @@ public class ViewKeyActivity extends ActionBarActivity {
     private void copyToClipboard(Uri dataUri) {
         // get public keyring as ascii armored string
         long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
-        ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri,
-                new long[]{masterKeyId});
+        ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+                this, new long[]{ masterKeyId });
 
         ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
         Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG)
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
index 997ff9c7a..6ce7d9aa8 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
@@ -34,6 +34,9 @@ import android.widget.Toast;
 import org.sufficientlysecure.keychain.Constants;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
 
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
@@ -47,26 +50,18 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        initNfc(mDataUri);
     }
 
     /**
      * NFC: Initialize NFC sharing if OS and device supports it
      */
-    private void initNfc(Uri dataUri) {
+    private void initNfc() {
         // check if NFC Beam is supported (>= Android 4.1)
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
             // Check for available NFC Adapter
             mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
             if (mNfcAdapter != null) {
                 // init nfc
-
-                // get public keyring as byte array
-                long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
-                mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri,
-                        new long[]{masterKeyId});
-
                 // Register callback to set NDEF message
                 mNfcAdapter.setNdefPushMessageCallback(this, this);
                 // Register callback to listen for message-sent success
@@ -86,9 +81,19 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess
          * guarantee that this activity starts when receiving a beamed message. For now, this code
          * uses the tag dispatch system.
          */
-        NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
-                mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
-        return msg;
+        // get public keyring as byte array
+        long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
+        try {
+            mSharedKeyringBytes = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId).getEncoded();
+
+            NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
+                    mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
+            return msg;
+        } catch(IOException e) {
+            // not much trouble, but leave a note
+            Log.e(Constants.TAG, "Error parsing keyring: ", e);
+            return null;
+        }
     }
 
     /**
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
index 703f7e861..830c5fcae 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
@@ -30,6 +30,7 @@ import android.text.format.DateFormat;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 
@@ -38,7 +39,9 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
 import org.sufficientlysecure.keychain.Constants;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
 import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
@@ -52,6 +55,7 @@ public class ViewKeyMainFragment extends Fragment implements
 
     public static final String ARG_DATA_URI = "uri";
 
+    private LinearLayout mContainer;
     private TextView mName;
     private TextView mEmail;
     private TextView mComment;
@@ -68,6 +72,7 @@ public class ViewKeyMainFragment extends Fragment implements
     private ListView mUserIds;
     private ListView mKeys;
 
+    private static final int LOADER_ID_UNIFIED = 0;
     private static final int LOADER_ID_USER_IDS = 1;
     private static final int LOADER_ID_KEYS = 2;
 
@@ -76,10 +81,14 @@ public class ViewKeyMainFragment extends Fragment implements
 
     private Uri mDataUri;
 
+    // for activity
+    private boolean mSecretAvailable = false;
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View view = inflater.inflate(R.layout.view_key_main_fragment, container, false);
 
+        mContainer = (LinearLayout) view.findViewById(R.id.container);
         mName = (TextView) view.findViewById(R.id.name);
         mEmail = (TextView) view.findViewById(R.id.email);
         mComment = (TextView) view.findViewById(R.id.comment);
@@ -118,64 +127,24 @@ public class ViewKeyMainFragment extends Fragment implements
             return;
         }
 
+        getActivity().setProgressBarIndeterminateVisibility(Boolean.TRUE);
+        mContainer.setVisibility(View.GONE);
+
         mDataUri = dataUri;
 
         Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
 
-        { // label whether secret key is available, and edit button if it is
-            final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri);
-            if (ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) {
-                // set this attribute. this is a LITTLE unclean, but we have the info available
-                // right here, so why not.
-                mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
-                mSecretKey.setText(R.string.secret_key_yes);
-
-                // certify button
-                // TODO this button MIGHT be useful if the user wants to
-                // certify a private key with another...
-                // mActionCertify.setVisibility(View.GONE);
-
-                // edit button
-                mActionEdit.setVisibility(View.VISIBLE);
-                mActionEdit.setOnClickListener(new View.OnClickListener() {
-                    public void onClick(View view) {
-                        Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
-                        editIntent.setData(
-                                KeychainContract
-                                        .KeyRings.buildSecretKeyRingUri(
-                                        Long.toString(masterKeyId)));
-                        editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
-                        startActivityForResult(editIntent, 0);
-                    }
-                });
-            } else {
-                mSecretKey.setTextColor(Color.BLACK);
-                mSecretKey.setText(getResources().getString(R.string.secret_key_no));
-
-                // certify button
-                mActionCertify.setVisibility(View.VISIBLE);
-                // edit button
-                mActionEdit.setVisibility(View.GONE);
-            }
-
-            // TODO see todo note above, doing this here for now
-            mActionCertify.setOnClickListener(new View.OnClickListener() {
-                public void onClick(View view) {
-                    certifyKey(KeychainContract.KeyRings.buildGenericKeyRingUri(
-                            Long.toString(masterKeyId)
-                    ));
-                }
-            });
-
-        }
-
         mActionEncrypt.setOnClickListener(new View.OnClickListener() {
-
             @Override
             public void onClick(View v) {
                 encryptToContact(mDataUri);
             }
         });
+        mActionCertify.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View view) {
+                certifyKey(mDataUri);
+            }
+        });
 
         mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
         mUserIds.setAdapter(mUserIdsAdapter);
@@ -185,56 +154,51 @@ public class ViewKeyMainFragment extends Fragment implements
 
         // Prepare the loaders. Either re-connect with an existing ones,
         // or start new ones.
+        getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
         getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
         getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
     }
 
-    static final String[] USER_IDS_PROJECTION =
-            new String[]{
-                    KeychainContract.UserIds._ID,
-                    KeychainContract.UserIds.USER_ID,
-                    KeychainContract.UserIds.RANK,
-            };
-    static final int INDEX_UID_UID = 1;
-    static final String USER_IDS_SORT_ORDER =
-            KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC";
+    static final String[] UNIFIED_PROJECTION = new String[] {
+        KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET,
+            KeyRings.USER_ID, KeyRings.FINGERPRINT,
+            KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
 
-    static final String[] KEYS_PROJECTION =
-            new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID,
-                    KeychainContract.Keys.ALGORITHM, KeychainContract.Keys.RANK,
-                    KeychainContract.Keys.KEY_SIZE, KeychainContract.Keys.CAN_CERTIFY,
-                    KeychainContract.Keys.CAN_SIGN, KeychainContract.Keys.CAN_ENCRYPT,
-                    KeychainContract.Keys.IS_REVOKED, KeychainContract.Keys.CREATION,
-                    KeychainContract.Keys.EXPIRY, KeychainContract.Keys.FINGERPRINT};
-    static final String KEYS_SORT_ORDER = KeychainContract.Keys.RANK + " ASC";
-    static final int KEYS_INDEX_KEY_ID = 1;
-    static final int KEYS_INDEX_ALGORITHM = 2;
-    static final int KEYS_INDEX_RANK = 3;
-    static final int KEYS_INDEX_KEY_SIZE = 4;
-    static final int KEYS_INDEX_CAN_CERTIFY = 5;
-    static final int KEYS_INDEX_CAN_SIGN = 6;
-    static final int KEYS_INDEX_CAN_ENCRYPT = 7;
-    static final int KEYS_INDEX_IS_REVOKED = 8;
-    static final int KEYS_INDEX_CREATION = 9;
-    static final int KEYS_INDEX_EXPIRY = 10;
-    static final int KEYS_INDEX_FINGERPRINT = 11;
+    };
+    static final int INDEX_UNIFIED_MKI = 1;
+    static final int INDEX_UNIFIED_HAS_SECRET = 2;
+    static final int INDEX_UNIFIED_UID = 3;
+    static final int INDEX_UNIFIED_FINGERPRINT = 4;
+    static final int INDEX_UNIFIED_ALGORITHM = 5;
+    static final int INDEX_UNIFIED_KEY_SIZE = 6;
+    static final int INDEX_UNIFIED_CREATION = 7;
+    static final int INDEX_UNIFIED_EXPIRY = 8;
+
+    static final String[] USER_IDS_PROJECTION = new String[] {
+        UserIds._ID, UserIds.USER_ID, UserIds.RANK,
+    };
+
+    static final String[] KEYS_PROJECTION = new String[] {
+            Keys._ID,
+            Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE,
+            Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
+            Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
+    };
+    static final int KEYS_INDEX_CAN_ENCRYPT = 6;
 
     public Loader onCreateLoader(int id, Bundle args) {
         switch (id) {
+            case LOADER_ID_UNIFIED: {
+                Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
+                return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
+            }
             case LOADER_ID_USER_IDS: {
-                Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
-
-                // Now create and return a CursorLoader that will take care of
-                // creating a Cursor for the data being displayed.
-                return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null,
-                        USER_IDS_SORT_ORDER);
+                Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
+                return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, null);
             }
             case LOADER_ID_KEYS: {
-                Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri);
-
-                // Now create and return a CursorLoader that will take care of
-                // creating a Cursor for the data being displayed.
-                return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER);
+                Uri baseUri = Keys.buildKeysUri(mDataUri);
+                return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
             }
 
             default:
@@ -243,14 +207,19 @@ public class ViewKeyMainFragment extends Fragment implements
     }
 
     public void onLoadFinished(Loader loader, Cursor data) {
+        /* TODO better error handling? May cause problems when a key is deleted,
+         * because the notification triggers faster than the activity closes.
+         */
+        // Avoid NullPointerExceptions...
+        if(data.getCount() == 0)
+            return;
         // Swap the new cursor in. (The framework will take care of closing the
         // old cursor once we return.)
         switch (loader.getId()) {
-            case LOADER_ID_USER_IDS:
+            case LOADER_ID_UNIFIED: {
                 if (data.moveToFirst()) {
                     // get name, email, and comment from USER_ID
-                    String[] mainUserId = PgpKeyHelper.splitUserId(data
-                            .getString(INDEX_UID_UID));
+                    String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID));
                     if (mainUserId[0] != null) {
                         getActivity().setTitle(mainUserId[0]);
                         mName.setText(mainUserId[0]);
@@ -260,22 +229,45 @@ public class ViewKeyMainFragment extends Fragment implements
                     }
                     mEmail.setText(mainUserId[1]);
                     mComment.setText(mainUserId[2]);
-                }
-                mUserIdsAdapter.swapCursor(data);
-                break;
-            case LOADER_ID_KEYS:
-                // the first key here is our master key
-                if (data.moveToFirst()) {
+
+                    if (data.getInt(INDEX_UNIFIED_HAS_SECRET) != 0) {
+                        mSecretAvailable = true;
+
+                        mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
+                        mSecretKey.setText(R.string.secret_key_yes);
+
+                        // edit button
+                        mActionEdit.setVisibility(View.VISIBLE);
+                        mActionEdit.setOnClickListener(new View.OnClickListener() {
+                            public void onClick(View view) {
+                                Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
+                                editIntent.setData(mDataUri);
+                                editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
+                                startActivityForResult(editIntent, 0);
+                            }
+                        });
+                    } else {
+                        mSecretAvailable = false;
+
+                        mSecretKey.setTextColor(Color.BLACK);
+                        mSecretKey.setText(getResources().getString(R.string.secret_key_no));
+
+                        // certify button
+                        mActionCertify.setVisibility(View.VISIBLE);
+                        // edit button
+                        mActionEdit.setVisibility(View.GONE);
+                    }
+
                     // get key id from MASTER_KEY_ID
-                    long keyId = data.getLong(KEYS_INDEX_KEY_ID);
-                    String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
+                    long masterKeyId = data.getLong(INDEX_UNIFIED_MKI);
+                    String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
                     mKeyId.setText(keyIdStr);
 
                     // get creation date from CREATION
-                    if (data.isNull(KEYS_INDEX_CREATION)) {
+                    if (data.isNull(INDEX_UNIFIED_CREATION)) {
                         mCreation.setText(R.string.none);
                     } else {
-                        Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000);
+                        Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000);
 
                         mCreation.setText(
                                 DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
@@ -283,10 +275,10 @@ public class ViewKeyMainFragment extends Fragment implements
                     }
 
                     // get expiry date from EXPIRY
-                    if (data.isNull(KEYS_INDEX_EXPIRY)) {
+                    if (data.isNull(INDEX_UNIFIED_EXPIRY)) {
                         mExpiry.setText(R.string.none);
                     } else {
-                        Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000);
+                        Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
 
                         mExpiry.setText(
                                 DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
@@ -294,19 +286,22 @@ public class ViewKeyMainFragment extends Fragment implements
                     }
 
                     String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
-                            data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE));
+                            data.getInt(INDEX_UNIFIED_ALGORITHM), data.getInt(INDEX_UNIFIED_KEY_SIZE));
                     mAlgorithm.setText(algorithmStr);
 
-                    byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT);
-                    if (fingerprintBlob == null) {
-                        // FALLBACK for old database entries
-                        fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri);
-                    }
+                    byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
                     String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
-
                     mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
-                }
 
+                    break;
+                }
+            }
+
+            case LOADER_ID_USER_IDS:
+                mUserIdsAdapter.swapCursor(data);
+                break;
+
+            case LOADER_ID_KEYS:
                 // hide encrypt button if no encryption key is available
                 boolean canEncrypt = false;
                 data.moveToFirst();
@@ -322,10 +317,9 @@ public class ViewKeyMainFragment extends Fragment implements
 
                 mKeysAdapter.swapCursor(data);
                 break;
-
-            default:
-                break;
         }
+        getActivity().setProgressBarIndeterminateVisibility(Boolean.FALSE);
+        mContainer.setVisibility(View.VISIBLE);
     }
 
     /**
@@ -340,14 +334,17 @@ public class ViewKeyMainFragment extends Fragment implements
             case LOADER_ID_KEYS:
                 mKeysAdapter.swapCursor(null);
                 break;
-            default:
-                break;
         }
     }
 
+    /** Returns true if the key current displayed is known to have a secret key. */
+    public boolean isSecretAvailable() {
+        return mSecretAvailable;
+    }
+
     private void encryptToContact(Uri dataUri) {
         // TODO preselect from uri? should be feasible without trivial query
-        long keyId = Long.parseLong(dataUri.getPathSegments().get(1));
+        long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
 
         long[] encryptionKeyIds = new long[]{ keyId };
         Intent intent = new Intent(getActivity(), EncryptActivity.class);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
index 9427ce0db..72ea4c013 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
@@ -20,8 +20,6 @@ package org.sufficientlysecure.keychain.ui.dialog;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
-import android.database.Cursor;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Message;
 import android.os.Messenger;
@@ -35,18 +33,18 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.Id;
 import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
 import org.sufficientlysecure.keychain.provider.KeychainDatabase;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.util.Log;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 
 public class DeleteKeyDialogFragment extends DialogFragment {
     private static final String ARG_MESSENGER = "messenger";
-    private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_key_ring_row_ids";
+    private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
 
     public static final int MESSAGE_OKAY = 1;
     public static final int MESSAGE_ERROR = 0;
@@ -63,13 +61,13 @@ public class DeleteKeyDialogFragment extends DialogFragment {
     /**
      * Creates new instance of this delete file dialog fragment
      */
-    public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds
-    ) {
+    public static DeleteKeyDialogFragment newInstance(Messenger messenger,
+                                                      long[] masterKeyIds) {
         DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
         Bundle args = new Bundle();
 
         args.putParcelable(ARG_MESSENGER, messenger);
-        args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds);
+        args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
         //We don't need the key type
 
         frag.setArguments(args);
@@ -77,14 +75,13 @@ public class DeleteKeyDialogFragment extends DialogFragment {
         return frag;
     }
 
-
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
 
         final FragmentActivity activity = getActivity();
         mMessenger = getArguments().getParcelable(ARG_MESSENGER);
 
-        final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS);
+        final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
 
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
 
@@ -98,112 +95,50 @@ public class DeleteKeyDialogFragment extends DialogFragment {
         mCheckDeleteSecret = (CheckBox) mInflateView.findViewById(R.id.checkDeleteSecret);
 
         builder.setTitle(R.string.warning);
-        /* TODO! redo
 
-        //If only a single key has been selected
-        if (keyRingRowIds.length == 1) {
-            Uri dataUri;
-            ArrayList publicKeyRings; //Any one will do
+        // If only a single key has been selected
+        if (masterKeyIds.length == 1) {
             mIsSingleSelection = true;
 
-            long selectedRow = keyRingRowIds[0];
-            long keyType;
-            publicKeyRings = ProviderHelper.getPublicKeyRingsRowIds(activity);
+            long masterKeyId = masterKeyIds[0];
 
-            if (publicKeyRings.contains(selectedRow)) {
-                //TODO Should be a better method to do this other than getting all the KeyRings
-                dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(selectedRow));
-                keyType = Id.type.public_key;
-            } else {
-                dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(selectedRow));
-                keyType = Id.type.secret_key;
-            }
+            HashMap data = ProviderHelper.getUnifiedData(activity, masterKeyId, new String[]{
+                    KeyRings.USER_ID,
+                    KeyRings.HAS_SECRET
+            }, new int[] { ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER });
+            String userId = (String) data.get(KeyRings.USER_ID);
+            boolean hasSecret = ((Long) data.get(KeyRings.HAS_SECRET)) == 1;
 
-            String userId = ProviderHelper.getUserId(activity, dataUri);
-            // Hide the Checkbox and TextView since this is a single selection,
-            // user will be notified thru message
+            // Hide the Checkbox and TextView since this is a single selection,user will be notified through message
             mDeleteSecretKeyView.setVisibility(View.GONE);
             // Set message depending on which key it is.
-            mMainMessage.setText(getString(keyType == Id.type.secret_key ?
-                    R.string.secret_key_deletion_confirmation :
-                    R.string.public_key_deletetion_confirmation, userId));
+            mMainMessage.setText(getString(
+                    hasSecret ? R.string.secret_key_deletion_confirmation
+                              : R.string.public_key_deletetion_confirmation,
+                    userId));
         } else {
             mDeleteSecretKeyView.setVisibility(View.VISIBLE);
             mMainMessage.setText(R.string.key_deletion_confirmation_multi);
         }
 
-
         builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
         builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                Uri queryUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
-                String[] projection = new String[]{
-                        KeychainContract.KeyRings.MASTER_KEY_ID, // 0
-                        KeychainContract.KeyRings.TYPE// 1
-                };
 
-                // make selection with all entries where _ID is one of the given row ids
-                String selection = KeychainDatabase.Tables.KEY_RINGS + "." +
-                        KeychainContract.KeyRings._ID + " IN(";
-                String selectionIDs = "";
-                for (int i = 0; i < keyRingRowIds.length; i++) {
-                    selectionIDs += "'" + String.valueOf(keyRingRowIds[i]) + "'";
-                    if (i + 1 < keyRingRowIds.length) {
-                        selectionIDs += ",";
-                    }
+                boolean success = false;
+                for(long masterKeyId : masterKeyIds) {
+                    int count = activity.getContentResolver().delete(
+                            KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
+                        );
+                    success = count > 0;
                 }
-                selection += selectionIDs + ")";
-
-                Cursor cursor = activity.getContentResolver().query(queryUri, projection,
-                        selection, null, null);
-
-
-                long masterKeyId;
-                long keyType;
-                boolean isSuccessfullyDeleted;
-                try {
-                    isSuccessfullyDeleted = false;
-                    while (cursor != null && cursor.moveToNext()) {
-                        masterKeyId = cursor.getLong(0);
-                        keyType = cursor.getLong(1);
-
-                        Log.d(Constants.TAG, "masterKeyId: " + masterKeyId +
-                                ", keyType:" +
-                                    (keyType == KeychainContract.KeyTypes.PUBLIC ?
-                                        "Public" : "Private"));
-
-                        if (keyType == KeychainContract.KeyTypes.SECRET) {
-                            if (mCheckDeleteSecret.isChecked() || mIsSingleSelection) {
-                                ProviderHelper.deleteUnifiedKeyRing(activity,
-                                    String.valueOf(masterKeyId), true);
-                            }
-                        } else {
-                            ProviderHelper.deleteUnifiedKeyRing(activity,
-                                String.valueOf(masterKeyId), false);
-                        }
-                    }
-
-                    //Check if the selected rows have actually been deleted
-                    cursor = activity.getContentResolver().query(
-                                queryUri, projection, selection, null, null);
-                    if (cursor == null || cursor.getCount() == 0 ||
-                        !mCheckDeleteSecret.isChecked()) {
-                        isSuccessfullyDeleted = true;
-                    }
-                } finally {
-                    if (cursor != null) {
-                        cursor.close();
-                    }
-                }
-
-                dismiss();
-
-                if (isSuccessfullyDeleted) {
+                if (success) {
                     sendMessageToHandler(MESSAGE_OKAY, null);
                 } else {
                     sendMessageToHandler(MESSAGE_ERROR, null);
                 }
+                dismiss();
             }
         });
         builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -213,7 +148,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
                 dismiss();
             }
         });
-        */
+
         return builder.create();
     }
 
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
index 60dd98c18..a3feab959 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
@@ -139,7 +139,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
             secretKey = null;
             alert.setMessage(R.string.passphrase_for_symmetric_encryption);
         } else {
-            secretKey = ProviderHelper.getPGPSecretKeyByKeyId(activity, secretKeyId);
+            secretKey = ProviderHelper.getPGPSecretKeyRing(activity, secretKeyId).getSecretKey();
 
             if (secretKey == null) {
                 alert.setTitle(R.string.title_key_not_found);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
index 18403837a..b6ff139df 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
@@ -31,6 +31,7 @@ import android.widget.TextView;
 import org.sufficientlysecure.keychain.Constants;
 import org.sufficientlysecure.keychain.R;
 import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
 import org.sufficientlysecure.keychain.provider.ProviderHelper;
 import org.sufficientlysecure.keychain.util.QrCodeUtils;
 
@@ -89,22 +90,26 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
         if (mFingerprintOnly) {
             alert.setPositiveButton(R.string.btn_okay, null);
 
-            byte[] fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), dataUri);
-            String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
+            byte[] blob = (byte[]) ProviderHelper.getGenericData(
+                    getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri),
+                    KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+            if(blob == null) {
+                // TODO error handling?!
+                return null;
+            }
 
+            String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
             mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
-
             content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
             setQrCode(content);
         } else {
             mText.setText(R.string.share_qr_code_dialog_start);
 
-            // TODO
+            // TODO works, but
             long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
-
             // get public keyring as ascii armored string
             ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
-                    getActivity(), dataUri, new long[]{masterKeyId});
+                    getActivity(), new long[] { masterKeyId });
 
             // TODO: binary?
 
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml
index 6d2bc8874..aa48252ce 100644
--- a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml
@@ -12,7 +12,8 @@
         android:descendantFocusability="beforeDescendants"
         android:orientation="vertical"
         android:paddingLeft="16dp"
-        android:paddingRight="16dp">
+        android:paddingRight="16dp"
+        android:id="@+id/container">
 
         
-        
-            
-
-            
-