diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 2ddef4cd1..5353af100 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -880,13 +880,10 @@ android:name=".service.KeychainService" android:exported="false" /> - + android:exported="false" /> List mapAllRows(SupportSQLiteQuery query, Mapper mapper) { + List mapAllRows(SupportSQLiteQuery query, RowMapper mapper) { ArrayList result = new ArrayList<>(); try (Cursor cursor = getReadableDb().query(query)) { while (cursor.moveToNext()) { @@ -44,7 +45,7 @@ class AbstractDao { return result; } - T mapSingleRowOrThrow(SupportSQLiteQuery query, Mapper mapper) throws NotFoundException { + T mapSingleRowOrThrow(SupportSQLiteQuery query, RowMapper mapper) throws NotFoundException { T result = mapSingleRow(query, mapper); if (result == null) { throw new NotFoundException(); @@ -52,7 +53,7 @@ class AbstractDao { return result; } - T mapSingleRow(SupportSQLiteQuery query, Mapper mapper) { + T mapSingleRow(SupportSQLiteQuery query, RowMapper mapper) { try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToNext()) { return mapper.map(cursor); @@ -60,8 +61,4 @@ class AbstractDao { } return null; } - - interface Mapper { - T map(Cursor cursor); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseBatchInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseBatchInteractor.java new file mode 100644 index 000000000..151e2b346 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseBatchInteractor.java @@ -0,0 +1,100 @@ +package org.sufficientlysecure.keychain.daos; + + +import java.util.List; + +import android.arch.persistence.db.SupportSQLiteDatabase; + +import org.sufficientlysecure.keychain.CertsModel.InsertCert; +import org.sufficientlysecure.keychain.KeyRingsPublicModel.InsertKeyRingPublic; +import org.sufficientlysecure.keychain.KeySignaturesModel.InsertKeySignature; +import org.sufficientlysecure.keychain.KeysModel.InsertKey; +import org.sufficientlysecure.keychain.UserPacketsModel.InsertUserPacket; +import org.sufficientlysecure.keychain.model.Certification; +import org.sufficientlysecure.keychain.model.KeyRingPublic; +import org.sufficientlysecure.keychain.model.KeySignature; +import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.UserPacket; + + +public class DatabaseBatchInteractor { + private final SupportSQLiteDatabase db; + + private final InsertKeyRingPublic insertKeyRingPublicStatement; + private final InsertKey insertSubKeyStatement; + private final InsertUserPacket insertUserPacketStatement; + private final InsertCert insertCertificationStatement; + private final InsertKeySignature insertKeySignerStatement; + + DatabaseBatchInteractor(SupportSQLiteDatabase db) { + this.db = db; + + insertKeyRingPublicStatement = KeyRingPublic.createInsertStatement(db); + insertSubKeyStatement = SubKey.createInsertStatement(db); + insertUserPacketStatement = UserPacket.createInsertStatement(db); + insertCertificationStatement = Certification.createInsertStatement(db); + insertKeySignerStatement = KeySignature.createInsertStatement(db); + } + + public SupportSQLiteDatabase getDb() { + return db; + } + + public void applyBatch(List operations) { + for (BatchOp op : operations) { + if (op.keyRingPublic != null) { + op.keyRingPublic.bindTo(insertKeyRingPublicStatement); + insertKeyRingPublicStatement.executeInsert(); + } else if (op.subKey != null) { + op.subKey.bindTo(insertSubKeyStatement); + insertSubKeyStatement.executeInsert(); + } else if (op.userPacket != null) { + op.userPacket.bindTo(insertUserPacketStatement); + insertUserPacketStatement.executeInsert(); + } else if (op.certification != null) { + op.certification.bindTo(insertCertificationStatement); + insertCertificationStatement.executeInsert(); + } else if (op.keySignature != null) { + op.keySignature.bindTo(insertKeySignerStatement); + insertKeySignerStatement.executeInsert(); + } + } + } + + public static BatchOp createInsertKeyRingPublic(KeyRingPublic keyRingPublic) { + return new BatchOp(keyRingPublic, null, null, null, null); + } + + static BatchOp createInsertSubKey(SubKey subKey) { + return new BatchOp(null, subKey, null, null, null); + } + + public static BatchOp createInsertUserPacket(UserPacket userPacket) { + return new BatchOp(null, null, userPacket, null, null); + } + + public static BatchOp createInsertCertification(Certification certification) { + return new BatchOp(null, null, null, certification, null); + } + + static BatchOp createInsertSignerKey(KeySignature keySignature) { + return new BatchOp(null, null, null, null, keySignature); + } + + static class BatchOp { + final KeyRingPublic keyRingPublic; + final SubKey subKey; + final UserPacket userPacket; + final Certification certification; + final KeySignature keySignature; + + BatchOp(KeyRingPublic keyRingPublic, SubKey subKey, UserPacket userPacket, + Certification certification, KeySignature keySignature) { + this.subKey = subKey; + this.keyRingPublic = keyRingPublic; + this.userPacket = userPacket; + this.certification = certification; + this.keySignature = keySignature; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java index ee81a5f14..ed62c880e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java @@ -23,6 +23,11 @@ public class DatabaseNotifyManager { this.contentResolver = contentResolver; } + public void notifyAllKeysChange() { + Uri uri = getNotifyUriAllKeys(); + contentResolver.notifyChange(uri, null); + } + public void notifyKeyChange(long masterKeyId) { Uri uri = getNotifyUriMasterKeyId(masterKeyId); contentResolver.notifyChange(uri, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java index d31340d4d..b5e1cc71d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.daos; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import android.content.ContentResolver; @@ -27,6 +28,7 @@ import android.content.Context; import android.database.Cursor; import android.support.annotation.WorkerThread; +import com.squareup.sqldelight.RowMapper; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.KeychainDatabase; @@ -129,69 +131,133 @@ public class KeyRepository extends AbstractDao { public List getAllMasterKeyIds() { SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds(); - return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + Long item = KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper().map(cursor); + result.add(item); + } + } + return result; } public List getMasterKeyIdsBySigner(List signerMasterKeyIds) { long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds); SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); - return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + Long item = KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper().map(cursor); + result.add(item); + } + } + return result; } public Long getMasterKeyIdBySubkeyId(long subKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId); - return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map); + return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()); } public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId); - return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER); } public List getUnifiedKeyInfo(long... masterKeyIds) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getUnifiedKeyInfosByMailAddress(String mailAddress) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%'); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getAllUnifiedKeyInfo() { SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getAllUnifiedKeyInfoWithSecret() { SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret(); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getUserIds(long... masterKeyIds) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); - return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UserId item = UserPacket.USER_ID_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getConfirmedUserIds(long masterKeyId) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification( Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET); - return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()); + ArrayList result = new ArrayList<>(); + try (Cursor cursor1 = getReadableDb().query(query)) { + while (cursor1.moveToNext()) { + String item = ((RowMapper) (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()) + .map(cursor1); + result.add(item); + } + } + return result; } public List getSubKeysByMasterKeyId(long masterKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId); - return mapAllRows(query, SubKey.SUBKEY_MAPPER::map); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + SubKey item = SubKey.SUBKEY_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId); - return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map); + return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER); } public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId); - return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map); + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()); } private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException { @@ -247,12 +313,12 @@ public class KeyRepository extends AbstractDao { public long getSecretSignId(long masterKeyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId); - return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map); + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()); } public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId); - return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map); + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()); } public static class NotFoundException extends Exception { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java index 4a4d2a58f..1f46bca1d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java @@ -25,21 +25,24 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import android.content.ContentProviderOperation; -import android.content.ContentValues; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; -import android.content.OperationApplicationException; -import android.net.Uri; -import android.os.RemoteException; import android.support.annotation.NonNull; import android.support.v4.util.LongSparseArray; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.KeysModel.UpdateHasSecretByKeyId; +import org.sufficientlysecure.keychain.KeysModel.UpdateHasSecretByMasterKeyId; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.CustomColumnAdapters; +import org.sufficientlysecure.keychain.daos.DatabaseBatchInteractor.BatchOp; +import org.sufficientlysecure.keychain.model.Certification; +import org.sufficientlysecure.keychain.model.KeyRingPublic; +import org.sufficientlysecure.keychain.model.KeySignature; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -58,12 +61,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Preferences; @@ -87,6 +84,7 @@ public class KeyWritableRepository extends KeyRepository { private final Context context; private final DatabaseNotifyManager databaseNotifyManager; private AutocryptPeerDao autocryptPeerDao; + private DatabaseBatchInteractor databaseBatchInteractor; public static KeyWritableRepository create(Context context) { LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); @@ -116,6 +114,7 @@ public class KeyWritableRepository extends KeyRepository { this.context = context; this.databaseNotifyManager = databaseNotifyManager; this.autocryptPeerDao = autocryptPeerDao; + this.databaseBatchInteractor = new DatabaseBatchInteractor(getWritableDb()); } private LongSparseArray getTrustedMasterKeys() { @@ -177,27 +176,29 @@ public class KeyWritableRepository extends KeyRepository { long masterKeyId = keyRing.getMasterKeyId(); UncachedPublicKey masterKey = keyRing.getPublicKey(); - ArrayList operations; + log(LogType.MSG_IP_PREPARE); + mIndent += 1; + + byte[] encodedKeyRing; try { + encodedKeyRing = keyRing.getEncoded(); + } catch (IOException e) { + log(LogType.MSG_IP_ENCODE_FAIL); + return SaveKeyringResult.RESULT_ERROR; + } - log(LogType.MSG_IP_PREPARE); - mIndent += 1; - - // save all keys and userIds included in keyRing object in database - operations = new ArrayList<>(); + ArrayList operations = new ArrayList<>(); + try { log(LogType.MSG_IP_INSERT_KEYRING); - try { - writePublicKeyRing(keyRing, masterKeyId, operations); - } catch (IOException e) { - log(LogType.MSG_IP_ENCODE_FAIL); - return SaveKeyringResult.RESULT_ERROR; - } + + byte[] encodedRingIfDbCachable = encodedKeyRing.length < MAX_CACHED_KEY_SIZE ? encodedKeyRing : null; + KeyRingPublic keyRingPublic = KeyRingPublic.create(masterKeyId, encodedRingIfDbCachable); + operations.add(DatabaseBatchInteractor.createInsertKeyRingPublic(keyRingPublic)); log(LogType.MSG_IP_INSERT_SUBKEYS); mIndent += 1; { // insert subkeys - Uri uri = Keys.buildKeysUri(masterKeyId); int rank = 0; for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) { long keyId = key.getKeyId(); @@ -206,23 +207,7 @@ public class KeyWritableRepository extends KeyRepository { ); mIndent += 1; - ContentValues values = new ContentValues(); - values.put(Keys.MASTER_KEY_ID, masterKeyId); - values.put(Keys.RANK, rank); - - values.put(Keys.KEY_ID, key.getKeyId()); - values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.KEY_CURVE_OID, key.getCurveOid()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); - values.put(Keys.FINGERPRINT, key.getFingerprint()); - boolean c = key.canCertify(), e = key.canEncrypt(), s = key.canSign(), a = key.canAuthenticate(); - values.put(Keys.CAN_CERTIFY, c); - values.put(Keys.CAN_ENCRYPT, e); - values.put(Keys.CAN_SIGN, s); - values.put(Keys.CAN_AUTHENTICATE, a); - values.put(Keys.IS_REVOKED, key.isRevoked()); - values.put(Keys.IS_SECURE, key.isSecure()); // see above if (masterKeyId == keyId) { @@ -240,22 +225,24 @@ public class KeyWritableRepository extends KeyRepository { } Date creation = key.getCreationTime(); - values.put(Keys.CREATION, creation.getTime() / 1000); - Date expiryDate = key.getExpiryTime(); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + Date expiry = key.getExpiryTime(); + if (expiry != null) { if (key.isExpired()) { log(keyId == masterKeyId ? LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED, - expiryDate.toString()); + expiry.toString()); } else { log(keyId == masterKeyId ? LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES, - expiryDate.toString()); + expiry.toString()); } } - operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + SubKey subKey = SubKey.create(masterKeyId, rank, key.getKeyId(), + key.getBitStrength(), key.getCurveOid(), key.getAlgorithm(), key.getFingerprint(), + c, s, e, a, key.isRevoked(), SecretKeyType.UNAVAILABLE, key.isSecure(), creation, expiry); + operations.add(DatabaseBatchInteractor.createInsertSubKey(subKey)); + ++rank; mIndent -= 1; } @@ -312,10 +299,8 @@ public class KeyWritableRepository extends KeyRepository { // keep a note about the issuer of this key signature if (!signerKeyIds.contains(certId)) { - operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI) - .withValue(KeySignatures.MASTER_KEY_ID, masterKeyId) - .withValue(KeySignatures.SIGNER_KEY_ID, certId) - .build()); + KeySignature keySignature = KeySignature.create(masterKeyId, certId); + operations.add(DatabaseBatchInteractor.createInsertSignerKey(keySignature)); signerKeyIds.add(certId); } @@ -481,7 +466,10 @@ public class KeyWritableRepository extends KeyRepository { // iterate and put into db for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { UserPacketItem item = uids.get(userIdRank); - operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); + Long type = item.type != null ? item.type.longValue() : null; + UserPacket userPacket = UserPacket.create(masterKeyId, userIdRank, type, item.userId, item.name, item.email, + item.comment, item.attributeData, item.isPrimary, item.selfRevocation != null); + operations.add(DatabaseBatchInteractor.createInsertUserPacket(userPacket)); if (item.selfRevocation != null) { operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation, @@ -519,9 +507,12 @@ public class KeyWritableRepository extends KeyRepository { mIndent -= 1; } + SupportSQLiteDatabase db = databaseBatchInteractor.getDb(); try { + db.beginTransaction(); + // delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade - DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb()); + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db); deleteStatement.bind(masterKeyId); int deletedRows = deleteStatement.executeUpdateDelete(); @@ -533,41 +524,25 @@ public class KeyWritableRepository extends KeyRepository { } log(LogType.MSG_IP_APPLY_BATCH); - contentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + databaseBatchInteractor.applyBatch(operations); + if (encodedKeyRing.length >= MAX_CACHED_KEY_SIZE) { + mLocalPublicKeyStorage.writePublicKey(masterKeyId, encodedKeyRing); + } databaseNotifyManager.notifyKeyChange(masterKeyId); + db.setTransactionSuccessful(); log(LogType.MSG_IP_SUCCESS); return result; - - } catch (RemoteException e) { - log(LogType.MSG_IP_ERROR_REMOTE_EX); - Timber.e(e, "RemoteException during import"); - return SaveKeyringResult.RESULT_ERROR; - } catch (OperationApplicationException e) { + } catch (IOException e) { log(LogType.MSG_IP_ERROR_OP_EXC); Timber.e(e, "OperationApplicationException during import"); return SaveKeyringResult.RESULT_ERROR; + } finally { + db.endTransaction(); } } - private void writePublicKeyRing(CanonicalizedPublicKeyRing keyRing, long masterKeyId, - ArrayList operations) throws IOException { - byte[] encodedKey = keyRing.getEncoded(); - mLocalPublicKeyStorage.writePublicKey(masterKeyId, encodedKey); - - ContentValues values = new ContentValues(); - values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); - if (encodedKey.length < MAX_CACHED_KEY_SIZE) { - values.put(KeyRingData.KEY_RING_DATA, encodedKey); - } else { - values.put(KeyRingData.KEY_RING_DATA, (byte[]) null); - } - - Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId); - operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); - } - private void writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException { byte[] encodedKey = keyRing.getEncoded(); localSecretKeyStorage.writeSecretKey(masterKeyId, encodedKey); @@ -655,12 +630,12 @@ public class KeyWritableRepository extends KeyRepository { } { - Uri uri = Keys.buildKeysUri(masterKeyId); + UpdateHasSecretByMasterKeyId resetStatement = + SubKey.createUpdateHasSecretByMasterKeyIdStatement(getWritableDb()); + resetStatement.bind(masterKeyId, SecretKeyType.GNU_DUMMY); + resetStatement.executeUpdateDelete(); - // first, mark all keys as not available - ContentValues values = new ContentValues(); - values.put(Keys.HAS_SECRET, SecretKeyType.GNU_DUMMY.getNum()); - contentResolver.update(uri, values, null, null); + UpdateHasSecretByKeyId updateStatement = SubKey.createUpdateHasSecretByKeyId(getWritableDb()); // then, mark exactly the keys we have available log(LogType.MSG_IS_IMPORTING_SUBKEYS); @@ -668,36 +643,25 @@ public class KeyWritableRepository extends KeyRepository { for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) { long id = sub.getKeyId(); SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive(); - values.put(Keys.HAS_SECRET, mode.getNum()); - int upd = contentResolver.update(uri, values, Keys.KEY_ID + " = ?", - new String[]{Long.toString(id)}); + updateStatement.bind(id, mode); + int upd = updateStatement.executeUpdateDelete(); if (upd == 1) { switch (mode) { case PASSPHRASE: - log(LogType.MSG_IS_SUBKEY_OK, - KeyFormattingUtils.convertKeyIdToHex(id) - ); + log(LogType.MSG_IS_SUBKEY_OK, KeyFormattingUtils.convertKeyIdToHex(id)); break; case PASSPHRASE_EMPTY: - log(LogType.MSG_IS_SUBKEY_EMPTY, - KeyFormattingUtils.convertKeyIdToHex(id) - ); + log(LogType.MSG_IS_SUBKEY_EMPTY, KeyFormattingUtils.convertKeyIdToHex(id)); break; case GNU_DUMMY: - log(LogType.MSG_IS_SUBKEY_STRIPPED, - KeyFormattingUtils.convertKeyIdToHex(id) - ); + log(LogType.MSG_IS_SUBKEY_STRIPPED, KeyFormattingUtils.convertKeyIdToHex(id)); break; case DIVERT_TO_CARD: - log(LogType.MSG_IS_SUBKEY_DIVERT, - KeyFormattingUtils.convertKeyIdToHex(id) - ); + log(LogType.MSG_IS_SUBKEY_DIVERT, KeyFormattingUtils.convertKeyIdToHex(id)); break; } } else { - log(LogType.MSG_IS_SUBKEY_NONEXISTENT, - KeyFormattingUtils.convertKeyIdToHex(id) - ); + log(LogType.MSG_IS_SUBKEY_NONEXISTENT, KeyFormattingUtils.convertKeyIdToHex(id)); } } mIndent -= 1; @@ -744,7 +708,7 @@ public class KeyWritableRepository extends KeyRepository { // If there is an old keyring, merge it try { - UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing(); + UncachedKeyRing oldPublicRing = UncachedKeyRing.decodeFromData(loadPublicKeyRingData(masterKeyId)); alreadyExists = true; // Merge data from new public ring into the old one @@ -769,7 +733,7 @@ public class KeyWritableRepository extends KeyRepository { log(LogType.MSG_IP_SUCCESS_IDENTICAL); return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, canPublicRing); } - } catch (NotFoundException e) { + } catch (PgpGeneralException | NotFoundException e) { // Not an issue, just means we are dealing with a new keyring. // Canonicalize this keyring, to assert a number of assumptions made about it. @@ -1038,54 +1002,20 @@ public class KeyWritableRepository extends KeyRepository { } } - if (!isTrustDbInitialized) { - preferences.setKeySignaturesTableInitialized(); - } + preferences.setKeySignaturesTableInitialized(); log.add(LogType.MSG_TRUST_OK, 1); return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log); } - /** - * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing - */ - private ContentProviderOperation - buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) - throws IOException { - ContentValues values = new ContentValues(); - values.put(Certs.MASTER_KEY_ID, masterKeyId); - values.put(Certs.RANK, rank); - values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId()); - values.put(Certs.TYPE, cert.getSignatureType()); - values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); - values.put(Certs.VERIFIED, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.encode(verificationStatus)); - values.put(Certs.DATA, cert.getEncoded()); - - Uri uri = Certs.buildCertsUri(masterKeyId); - - return ContentProviderOperation.newInsert(uri).withValues(values).build(); - } - - /** - * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing - */ - private ContentProviderOperation - buildUserIdOperations(long masterKeyId, UserPacketItem item, int rank) { - ContentValues values = new ContentValues(); - values.put(UserPackets.MASTER_KEY_ID, masterKeyId); - values.put(UserPackets.TYPE, item.type); - values.put(UserPackets.USER_ID, item.userId); - values.put(UserPackets.NAME, item.name); - values.put(UserPackets.EMAIL, item.email); - values.put(UserPackets.COMMENT, item.comment); - values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData); - values.put(UserPackets.IS_PRIMARY, item.isPrimary); - values.put(UserPackets.IS_REVOKED, item.selfRevocation != null); - values.put(UserPackets.RANK, rank); - - Uri uri = UserPackets.buildUserIdsUri(masterKeyId); - - return ContentProviderOperation.newInsert(uri).withValues(values).build(); + private BatchOp buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) { + try { + Certification certification = Certification.create(masterKeyId, rank, cert.getKeyId(), + cert.getSignatureType(), verificationStatus, cert.getCreationTime(), cert.getEncoded()); + return DatabaseBatchInteractor.createInsertCertification(certification); + } catch (IOException e) { + throw new AssertionError(e); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java new file mode 100644 index 000000000..3a7bb8863 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java @@ -0,0 +1,44 @@ +package org.sufficientlysecure.keychain.daos; + + +import java.util.HashMap; +import java.util.Map; + +import android.content.Context; +import android.database.Cursor; + +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.model.UserPacket; +import org.sufficientlysecure.keychain.model.UserPacket.UidStatus; + + +public class UserIdDao extends AbstractDao { + public static UserIdDao getInstance(Context context) { + KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new UserIdDao(keychainDatabase, databaseNotifyManager); + } + + private UserIdDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) { + super(db, databaseNotifyManager); + } + + public UidStatus getUidStatusByEmailLike(String emailLike) { + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmailLike(emailLike); + return mapSingleRow(query, UserPacket.UID_STATUS_MAPPER); + } + + public Map getUidStatusByEmail(String... emails) { + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmail(emails); + Map result = new HashMap<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UidStatus item = UserPacket.UID_STATUS_MAPPER.map(cursor); + result.put(item.email(), item); + } + } + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java index ee42c460e..391af0cc7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java @@ -1,8 +1,13 @@ package org.sufficientlysecure.keychain.model; +import java.util.Date; + +import android.arch.persistence.db.SupportSQLiteDatabase; + import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.CertsModel; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; @AutoValue @@ -13,6 +18,20 @@ public abstract class Certification implements CertsModel { public static final SelectVerifyingCertDetailsMapper CERT_DETAILS_MAPPER = new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new); + public static Certification create(long masterKeyId, long rank, long keyIdCertifier, long type, + VerificationStatus verified, Date creation, byte[] data) { + long creationUnixTime = creation.getTime() / 1000; + return new AutoValue_Certification(masterKeyId, rank, keyIdCertifier, type, verified, creationUnixTime, data); + } + + public static InsertCert createInsertStatement(SupportSQLiteDatabase db) { + return new InsertCert(db, FACTORY); + } + + public void bindTo(InsertCert statement) { + statement.bind(master_key_id(), rank(), key_id_certifier(), type(), verified(), creation(), data()); + } + @AutoValue public static abstract class CertDetails implements CertsModel.SelectVerifyingCertDetailsModel { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java index 65a7cf6dd..4f42685cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java @@ -1,12 +1,28 @@ package org.sufficientlysecure.keychain.model; +import android.arch.persistence.db.SupportSQLiteDatabase; + import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.KeyRingsPublicModel; +import org.sufficientlysecure.keychain.KeysModel.InsertKey; + @AutoValue public abstract class KeyRingPublic implements KeyRingsPublicModel { public static final Factory FACTORY = new Factory<>(AutoValue_KeyRingPublic::new); public static final Mapper MAPPER = new Mapper<>(FACTORY); + + public static KeyRingPublic create(long masterKeyId, byte[] keyRingData) { + return new AutoValue_KeyRingPublic(masterKeyId, keyRingData); + } + + public static InsertKeyRingPublic createInsertStatement(SupportSQLiteDatabase db) { + return new InsertKeyRingPublic(db); + } + + public void bindTo(InsertKeyRingPublic statement) { + statement.bind(master_key_id(), key_ring_data()); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java index ed3caa1bc..c093d2336 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java @@ -1,6 +1,8 @@ package org.sufficientlysecure.keychain.model; +import android.arch.persistence.db.SupportSQLiteDatabase; + import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.KeySignaturesModel; @@ -10,4 +12,16 @@ public abstract class KeySignature implements KeySignaturesModel { public static final Factory FACTORY = new Factory<>(AutoValue_KeySignature::new); public static final Mapper MAPPER = new Mapper<>(FACTORY); + + public static InsertKeySignature createInsertStatement(SupportSQLiteDatabase db) { + return new InsertKeySignature(db); + } + + public void bindTo(InsertKeySignature statement) { + statement.bind(master_key_id(), signer_key_id()); + } + + public static KeySignature create(long masterKeyId, long certId) { + return null; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 5937ab318..088166146 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -3,8 +3,11 @@ package org.sufficientlysecure.keychain.model; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; + import com.google.auto.value.AutoValue; import com.squareup.sqldelight.RowMapper; import org.sufficientlysecure.keychain.KeysModel; @@ -26,6 +29,33 @@ public abstract class SubKey implements KeysModel { return expiry() != null; } + public static SubKey create(long masterKeyId, long rank, long keyId, Integer keySize, String keyCurveOid, + int algorithm, byte[] fingerprint, boolean canCertify, boolean canSign, boolean canEncrypt, boolean canAuth, + boolean isRevoked, SecretKeyType hasSecret, boolean isSecure, Date creation, Date expiry) { + long creationUnixTime = creation.getTime() / 1000; + Long expiryUnixTime = expiry != null ? expiry.getTime() / 1000 : null; + return new AutoValue_SubKey(masterKeyId, rank, keyId, keySize, keyCurveOid, algorithm, fingerprint, canCertify, + canSign, canEncrypt, canAuth, isRevoked, hasSecret, isSecure, creationUnixTime, expiryUnixTime); + } + + public static InsertKey createInsertStatement(SupportSQLiteDatabase db) { + return new InsertKey(db, FACTORY); + } + + public static UpdateHasSecretByMasterKeyId createUpdateHasSecretByMasterKeyIdStatement(SupportSQLiteDatabase db) { + return new UpdateHasSecretByMasterKeyId(db, FACTORY); + } + + public static UpdateHasSecretByKeyId createUpdateHasSecretByKeyId(SupportSQLiteDatabase db) { + return new UpdateHasSecretByKeyId(db, FACTORY); + } + + public void bindTo(InsertKey statement) { + statement.bind(master_key_id(), rank(), key_id(), key_size(), key_curve_oid(), algorithm(), fingerprint(), + can_certify(), can_sign(), can_encrypt(), can_authenticate(), is_revoked(), has_secret(), is_secure(), + creation(), expiry()); + } + @AutoValue public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel { private List autocryptPackageNames; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java index 1b4e81c3e..6bd833a81 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java @@ -1,12 +1,12 @@ package org.sufficientlysecure.keychain.model; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.support.annotation.NonNull; import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; @AutoValue @@ -16,6 +16,23 @@ public abstract class UserPacket implements UserPacketsModel { FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new); public static final SelectUserAttributesByTypeAndMasterKeyIdMapper USER_ATTRIBUTE_MAPPER = FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new); + public static final UidStatusMapper UID_STATUS_MAPPER = + FACTORY.selectUserIdStatusByEmailMapper(AutoValue_UserPacket_UidStatus::new); + + public static UserPacket create(long masterKeyId, int rank, Long type, String userId, String name, String email, + String comment, byte[] attribute_data, boolean isPrimary, boolean isRevoked) { + return new AutoValue_UserPacket(masterKeyId, rank, type, + userId, name, email, comment, attribute_data, isPrimary, isRevoked); + } + + public static InsertUserPacket createInsertStatement(SupportSQLiteDatabase db) { + return new InsertUserPacket(db); + } + + public void bindTo(InsertUserPacket statement) { + statement.bind(master_key_id(), rank(), type(), user_id(), name(), email(), comment(), attribute_data(), + is_primary(), is_revoked()); + } @AutoValue public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel { @@ -40,4 +57,11 @@ public abstract class UserPacket implements UserPacketsModel { return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified_int()); } } + + @AutoValue + public static abstract class UidStatus implements UidStatusModel { + public VerificationStatus keyStatus() { + return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(key_status_int()); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f6426bd8d..c0e0eef32 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -339,7 +339,6 @@ public abstract class OperationResult implements Parcelable { MSG_IP_ENCODE_FAIL (LogLevel.DEBUG, R.string.msg_ip_encode_fail), MSG_IP_ERROR_IO_EXC (LogLevel.ERROR, R.string.msg_ip_error_io_exc), MSG_IP_ERROR_OP_EXC (LogLevel.ERROR, R.string.msg_ip_error_op_exc), - MSG_IP_ERROR_REMOTE_EX (LogLevel.ERROR, R.string.msg_ip_error_remote_ex), MSG_IP_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_ip_fingerprint_error), MSG_IP_FINGERPRINT_OK (LogLevel.INFO, R.string.msg_ip_fingerprint_ok), MSG_IP_INSERT_KEYRING (LogLevel.DEBUG, R.string.msg_ip_insert_keyring), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 0602d0b70..c93f574f2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -17,18 +17,11 @@ package org.sufficientlysecure.keychain.provider; -import android.net.Uri; + import android.provider.BaseColumns; -import org.sufficientlysecure.keychain.Constants; - public class KeychainContract { - public interface KeyRingsColumns { - String MASTER_KEY_ID = "master_key_id"; // not a database id - String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob - } - public interface KeysColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id String RANK = "rank"; @@ -51,11 +44,6 @@ public class KeychainContract { String EXPIRY = "expiry"; } - public interface KeySignaturesColumns { - String MASTER_KEY_ID = "master_key_id"; // not a database id - String SIGNER_KEY_ID = "signer_key_id"; - } - public interface UserPacketsColumns { String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String TYPE = "type"; // not a database id @@ -79,78 +67,15 @@ public class KeychainContract { String DATA = "data"; } - public interface ApiAppsAllowedKeysColumns { - String KEY_ID = "key_id"; // not a database id - String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name - } - - public interface OverriddenWarnings { - String IDENTIFIER = "identifier"; - } - - public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; - - private static final Uri BASE_CONTENT_URI_INTERNAL = Uri - .parse("content://" + CONTENT_AUTHORITY); - - public static final String BASE_KEY_RINGS = "key_rings"; - - public static final String BASE_KEY_SIGNATURES = "key_signatures"; - - public static final String PATH_PUBLIC = "public"; - public static final String PATH_USER_IDS = "user_ids"; - public static final String PATH_KEYS = "keys"; - public static final String PATH_CERTS = "certs"; - - public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_KEY_RINGS).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 Uri buildPublicKeyRingUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build(); - } - } - public static class Keys implements KeysColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_KEY_RINGS).build(); - - public static Uri buildKeysUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build(); - } - - } - - public static class KeySignatures implements KeySignaturesColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_KEY_SIGNATURES).build(); } public static class UserPackets implements UserPacketsColumns, BaseColumns { - public static final String VERIFIED = "verified"; - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_KEY_RINGS).build(); - - public static Uri buildUserIdsUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build(); - } } public static class Certs implements CertsColumns, BaseColumns { public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SELF = 2; - - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_KEY_RINGS).build(); - - public static Uri buildCertsUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build(); - } } private KeychainContract() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index 4802b667e..d0a3058a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -38,19 +38,6 @@ public class KeychainExternalContract { public static final int KEY_STATUS_UNVERIFIED = 1; public static final int KEY_STATUS_VERIFIED = 2; - public static class EmailStatus implements BaseColumns { - public static final String EMAIL_ADDRESS = "email_address"; - public static final String USER_ID = "user_id"; - public static final String USER_ID_STATUS = "email_status"; - public static final String MASTER_KEY_ID = "master_key_id"; - - public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() - .appendPath(BASE_EMAIL_STATUS).build(); - - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; - } - public static class AutocryptStatus implements BaseColumns { public static final String ADDRESS = "address"; @@ -72,9 +59,6 @@ public class KeychainExternalContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_STATUS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; } private KeychainExternalContract() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 6efac8a72..dde5ef6ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1,225 +1,48 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package org.sufficientlysecure.keychain.provider; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; -import android.content.UriMatcher; import android.database.Cursor; -import android.database.sqlite.SQLiteConstraintException; -import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.support.annotation.NonNull; -import android.text.TextUtils; - -import org.sufficientlysecure.keychain.KeychainDatabase; -import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; -import org.sufficientlysecure.keychain.KeychainDatabase.Tables; -import timber.log.Timber; +import android.support.annotation.Nullable; public class KeychainProvider extends ContentProvider { - private static final int KEY_RING_KEYS = 201; - private static final int KEY_RING_USER_IDS = 202; - private static final int KEY_RING_PUBLIC = 203; - private static final int KEY_RING_CERTS = 205; - - private static final int KEY_SIGNATURES = 700; - - protected UriMatcher mUriMatcher; - - /** - * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by - * this {@link ContentProvider}. - */ - protected UriMatcher buildUriMatcher() { - final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); - - String authority = KeychainContract.CONTENT_AUTHORITY; - - /* - * list key_ring specifics - * - *
-         * key_rings/_/keys
-         * key_rings/_/user_ids
-         * key_rings/_/public
-         * key_rings/_/certs
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_KEYS, - KEY_RING_KEYS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_USER_IDS, - KEY_RING_USER_IDS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_PUBLIC, - KEY_RING_PUBLIC); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_CERTS, - KEY_RING_CERTS); - - matcher.addURI(authority, KeychainContract.BASE_KEY_SIGNATURES, KEY_SIGNATURES); - - - return matcher; - } - - private KeychainDatabase mKeychainDatabase; @Override public boolean onCreate() { - mUriMatcher = buildUriMatcher(); - return true; + return false; } - public KeychainDatabase getDb() { - if(mKeychainDatabase == null) { - mKeychainDatabase = KeychainDatabase.getInstance(getContext()); - } - return mKeychainDatabase; + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String sortOrder) { + throw new UnsupportedOperationException(); } + @Nullable @Override public String getType(@NonNull Uri uri) { throw new UnsupportedOperationException(); } + @Nullable @Override - public Cursor query( - @NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - */ - @Override - public Uri insert(@NonNull Uri uri, ContentValues values) { - Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")"); - - final SupportSQLiteDatabase db = getDb().getWritableDatabase(); - - Uri rowUri = null; - Long keyId = null; - try { - final int match = mUriMatcher.match(uri); - - switch (match) { - case KEY_RING_PUBLIC: { - db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values); - keyId = values.getAsLong(Keys.MASTER_KEY_ID); - break; - } - case KEY_RING_KEYS: { - db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values); - keyId = values.getAsLong(Keys.MASTER_KEY_ID); - break; - } - case KEY_RING_USER_IDS: { - // iff TYPE is null, user_id MUST be null as well - if (!(values.get(UserPacketsColumns.TYPE) == null - ? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null) - : (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null) - )) { - throw new AssertionError("Incorrect type for user packet! This is a bug!"); - } - if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { - throw new AssertionError("Rank 0 user packet must be a user id!"); - } - db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values); - keyId = values.getAsLong(UserPackets.MASTER_KEY_ID); - break; - } - case KEY_RING_CERTS: { - // we replace here, keeping only the latest signature - // TODO this would be better handled in savePublicKeyRing directly! - db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values); - keyId = values.getAsLong(Certs.MASTER_KEY_ID); - break; - } - case KEY_SIGNATURES: { - db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values); - rowUri = KeySignatures.CONTENT_URI; - break; - } - default: { - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - - if (keyId != null) { - uri = DatabaseNotifyManager.getNotifyUriMasterKeyId(keyId); - rowUri = uri; - } - - } catch (SQLiteConstraintException e) { - Timber.d(e, "Constraint exception on insert! Entry already existing?"); - } - - return rowUri; - } - - @Override - public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { throw new UnsupportedOperationException(); } @Override - public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { - Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); + public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException(); + } - final SupportSQLiteDatabase db = getDb().getWritableDatabase(); - - int count = 0; - try { - final int match = mUriMatcher.match(uri); - switch (match) { - case KEY_RING_KEYS: { - if (values.size() != 1 || !values.containsKey(Keys.HAS_SECRET)) { - throw new UnsupportedOperationException( - "Only has_secret column may be updated!"); - } - // make sure we get a long value here - Long mkid = Long.parseLong(uri.getPathSegments().get(1)); - String actualSelection = Keys.MASTER_KEY_ID + " = " + Long.toString(mkid); - if (!TextUtils.isEmpty(selection)) { - actualSelection += " AND (" + selection + ")"; - } - count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs); - break; - } - default: { - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - } catch (SQLiteConstraintException e) { - Timber.d(e, "Constraint exception on update! Entry already existing?"); - } - - return count; + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, + @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index 6dfa1e143..db6aaa94c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -3,8 +3,11 @@ package org.sufficientlysecure.keychain.remote; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import android.content.Context; import android.support.annotation.Nullable; @@ -146,15 +149,15 @@ public class AutocryptInteractor { return uncachedKeyRing; } - public List determineAutocryptRecommendations(String... autocryptIds) { - List result = new ArrayList<>(autocryptIds.length); + public Map determineAutocryptRecommendations(String... autocryptIds) { + Map result = new HashMap<>(autocryptIds.length); for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) { AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus); - result.add(peerResult); + result.put(peerResult.peerId, peerResult); } - return result; + return Collections.unmodifiableMap(result); } /** Determines Autocrypt "ui-recommendation", according to spec. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 753f198a2..f3a6fca2e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -20,34 +20,28 @@ package org.sufficientlysecure.keychain.remote; import java.security.AccessControlException; import java.util.Arrays; -import java.util.HashMap; +import java.util.Collections; import java.util.List; +import java.util.Map; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; +import android.database.MatrixCursor; import android.net.Uri; import android.support.annotation.NonNull; -import android.text.TextUtils; +import android.widget.Toast; import org.sufficientlysecure.keychain.BuildConfig; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.daos.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.KeychainDatabase; -import org.sufficientlysecure.keychain.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.daos.UserIdDao; +import org.sufficientlysecure.keychain.model.UserPacket.UidStatus; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState; import timber.log.Timber; @@ -59,39 +53,23 @@ public class KeychainExternalProvider extends ContentProvider { private static final int AUTOCRYPT_STATUS = 201; private static final int AUTOCRYPT_STATUS_INTERNAL = 202; - public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses"; - public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; - private UriMatcher uriMatcher; private ApiPermissionHelper apiPermissionHelper; - /** - * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by - * this {@link ContentProvider}. - */ protected UriMatcher buildUriMatcher() { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL; - /* - * list email_status - * - *
-         * email_status/
-         * 
- */ matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); - matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS, AUTOCRYPT_STATUS); matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS + "/*", AUTOCRYPT_STATUS_INTERNAL); return matcher; } - /** {@inheritDoc} */ @Override public boolean onCreate() { uriMatcher = buildUriMatcher(); @@ -105,104 +83,22 @@ public class KeychainExternalProvider extends ContentProvider { return true; } - /** - * {@inheritDoc} - */ - @Override - public String getType(@NonNull Uri uri) { - final int match = uriMatcher.match(uri); - switch (match) { - case EMAIL_STATUS: - return EmailStatus.CONTENT_TYPE; - default: - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - - /** - * {@inheritDoc} - */ @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); - long startTime = System.currentTimeMillis(); - - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - int match = uriMatcher.match(uri); - - String groupBy = null; - - SupportSQLiteDatabase db = KeychainDatabase.getTemporaryInstance(getContext()).getReadableDatabase(); + Context context = getContext(); + if (context == null) { + throw new IllegalStateException(); + } String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); + int match = uriMatcher.match(uri); switch (match) { case EMAIL_STATUS: { - boolean callerIsAllowed = apiPermissionHelper.isAllowedIgnoreErrors(); - if (!callerIsAllowed) { - throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); - } - - db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + TEMP_TABLE_COLUMN_ADDRES + " TEXT);"); - ContentValues cv = new ContentValues(); - for (String address : selectionArgs) { - cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); - } - - HashMap projectionMap = new HashMap<>(); - projectionMap.put(EmailStatus._ID, "email AS _id"); - projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address - TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS); - projectionMap.put(EmailStatus.USER_ID, - Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); - // we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified" - projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) " - // remap to keep this provider contract independent from our internal representation - + " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED - + " WHEN NULL THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " END AS " + EmailStatus.USER_ID_STATUS); - projectionMap.put(EmailStatus.MASTER_KEY_ID, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID); - qb.setProjectionMap(projectionMap); - - if (projection == null) { - throw new IllegalArgumentException("Please provide a projection!"); - } - - qb.setTables( - TEMP_TABLE_QUERIED_ADDRESSES - + " LEFT JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL" - + " AND " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " LIKE " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - + ")" - + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + Certs.RANK - + ")" - ); - // in case there are multiple verifying certificates - groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES; - List plist = Arrays.asList(projection); - if (plist.contains(EmailStatus.USER_ID)) { - groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID; - } - - // verified == 0 has no self-cert, which is basically an error case. never return that! - // verified == null is fine, because it means there was no join partner - qb.appendWhere(Tables.CERTS + "." + Certs.VERIFIED + " IS NULL OR " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = EmailStatus.EMAIL_ADDRESS; - } - - // uri to watch is all /key_rings/ - uri = KeyRings.CONTENT_URI; - - break; + Toast.makeText(context, "This API is no longer supported by OpenKeychain!", Toast.LENGTH_SHORT).show(); + return new MatrixCursor(projection); } case AUTOCRYPT_STATUS_INTERNAL: @@ -223,164 +119,127 @@ public class KeychainExternalProvider extends ContentProvider { throw new IllegalArgumentException("Please provide a projection!"); } - db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + - TEMP_TABLE_COLUMN_ADDRES + " TEXT NOT NULL PRIMARY KEY, " + - AutocryptStatus.UID_KEY_STATUS + " INT, " + - AutocryptStatus.UID_ADDRESS + " TEXT, " + - AutocryptStatus.UID_MASTER_KEY_ID + " INT, " + - AutocryptStatus.UID_CANDIDATES + " INT, " + - AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT DEFAULT " + AutocryptStatus.AUTOCRYPT_PEER_DISABLED + ", " + - AutocryptStatus.AUTOCRYPT_KEY_STATUS + " INT, " + - AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" + - ");"); - ContentValues cv = new ContentValues(); - for (String address : selectionArgs) { - cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); - } - - boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); - List plist = Arrays.asList(projection); - + boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); boolean queriesUidResult = plist.contains(AutocryptStatus.UID_KEY_STATUS) || plist.contains(AutocryptStatus.UID_ADDRESS) || plist.contains(AutocryptStatus.UID_MASTER_KEY_ID) || plist.contains(AutocryptStatus.UID_CANDIDATES); - if (queriesUidResult) { - fillTempTableWithUidResult(db, isWildcardSelector); - } - boolean queriesAutocryptResult = plist.contains(AutocryptStatus.AUTOCRYPT_PEER_STATE) || plist.contains(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID) || plist.contains(AutocryptStatus.AUTOCRYPT_KEY_STATUS); if (isWildcardSelector && queriesAutocryptResult) { throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); } - if (!isWildcardSelector && queriesAutocryptResult) { - AutocryptInteractor autocryptInteractor = - AutocryptInteractor.getInstance(getContext(), callingPackageName); - fillTempTableWithAutocryptRecommendations(db, autocryptInteractor, selectionArgs); - } - HashMap projectionMap = new HashMap<>(); - projectionMap.put(AutocryptStatus._ID, AutocryptStatus._ID); - projectionMap.put(AutocryptStatus.ADDRESS, AutocryptStatus.ADDRESS); - projectionMap.put(AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_KEY_STATUS); - projectionMap.put(AutocryptStatus.UID_ADDRESS, AutocryptStatus.UID_ADDRESS); - projectionMap.put(AutocryptStatus.UID_MASTER_KEY_ID, AutocryptStatus.UID_MASTER_KEY_ID); - projectionMap.put(AutocryptStatus.UID_CANDIDATES, AutocryptStatus.UID_CANDIDATES); - projectionMap.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_PEER_STATE); - projectionMap.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_KEY_STATUS); - projectionMap.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID); - qb.setProjectionMap(projectionMap); - qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES); + Map uidStatuses = queriesUidResult ? + loadUidStatusMap(selectionArgs, isWildcardSelector) : Collections.emptyMap(); + Map autocryptStates = queriesAutocryptResult ? + loadAutocryptRecommendationMap(selectionArgs, callingPackageName) : Collections.emptyMap(); - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = AutocryptStatus.ADDRESS; - } + MatrixCursor cursor = + mapResultsToProjectedMatrixCursor(projection, selectionArgs, uidStatuses, autocryptStates); - // uri to watch is all /key_rings/ - uri = KeyRings.CONTENT_URI; - break; + uri = DatabaseNotifyManager.getNotifyUriAllKeys(); + cursor.setNotificationUri(context.getContentResolver(), uri); + + return cursor; } default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } - } + } - // If no sort order is specified use the default - String orderBy; - if (TextUtils.isEmpty(sortOrder)) { - orderBy = null; - } else { - orderBy = sortOrder; - } + @NonNull + private MatrixCursor mapResultsToProjectedMatrixCursor(String[] projection, String[] selectionArgs, + Map uidStatuses, Map autocryptStates) { + MatrixCursor cursor = new MatrixCursor(projection); + for (String selectionArg : selectionArgs) { + AutocryptRecommendationResult autocryptResult = autocryptStates.get(selectionArg); + UidStatus uidStatus = uidStatuses.get(selectionArg); - qb.setStrict(true); - String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null); - Cursor cursor = db.query(query); - if (cursor != null) { - // Tell the cursor what uri to watch, so it knows when its source data changes - cursor.setNotificationUri(getContext().getContentResolver(), uri); - if (Constants.DEBUG_LOG_DB_QUERIES) { - DatabaseUtils.dumpCursor(cursor); + Object[] row = new Object[projection.length]; + for (int i = 0; i < projection.length; i++) { + if (AutocryptStatus.ADDRESS.equals(projection[i]) || AutocryptStatus._ID.equals(projection[i])) { + row[i] = selectionArg; + } else { + row[i] = columnNameToRowContent(projection[i], autocryptResult, uidStatus); + } } + cursor.addRow(row); } - - Timber.d("Query: " + qb.buildQuery(projection, selection, groupBy, null, orderBy, null)); - Timber.d(Constants.TAG, "Query took %s ms", (System.currentTimeMillis() - startTime)); - return cursor; } - private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, - AutocryptInteractor autocryptInteractor, String[] peerIds) { - List autocryptStates = - autocryptInteractor.determineAutocryptRecommendations(peerIds); - - fillTempTableWithAutocryptRecommendations(db, autocryptStates); - } - - private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, - List autocryptRecommendations) { - ContentValues cv = new ContentValues(); - for (AutocryptRecommendationResult peerResult : autocryptRecommendations) { - cv.clear(); - - cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, getPeerStateValue(peerResult.autocryptState)); - if (peerResult.masterKeyId != null) { - cv.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, peerResult.masterKeyId); - cv.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, peerResult.isVerified ? + private Object columnNameToRowContent( + String columnName, AutocryptRecommendationResult autocryptResult, UidStatus uidStatus) { + switch (columnName) { + case AutocryptStatus.UID_KEY_STATUS: { + if (uidStatus == null) { + return null; + } + return uidStatus.keyStatus() == VerificationStatus.VERIFIED_SECRET ? KeychainExternalContract.KEY_STATUS_VERIFIED : - KeychainExternalContract.KEY_STATUS_UNVERIFIED); + KeychainExternalContract.KEY_STATUS_UNVERIFIED; } + case AutocryptStatus.UID_ADDRESS: + if (uidStatus == null) { + return null; + } + return uidStatus.user_id(); - db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", - new String[] { peerResult.peerId }); + case AutocryptStatus.UID_MASTER_KEY_ID: + if (uidStatus == null) { + return null; + } + return uidStatus.master_key_id(); + + case AutocryptStatus.UID_CANDIDATES: + if (uidStatus == null) { + return null; + } + return uidStatus.candidates(); + + case AutocryptStatus.AUTOCRYPT_PEER_STATE: + if (autocryptResult == null) { + return null; + } + return getPeerStateValue(autocryptResult.autocryptState); + + case AutocryptStatus.AUTOCRYPT_KEY_STATUS: + if (autocryptResult == null) { + return null; + } + return autocryptResult.isVerified ? + KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_UNVERIFIED; + + case AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID: + if (autocryptResult == null) { + return null; + } + return autocryptResult.masterKeyId; + + default: + throw new IllegalArgumentException("Unhandled case " + columnName); } } - private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) { - String cmpOperator = isWildcardSelector ? " LIKE " : " = "; - long unixSeconds = System.currentTimeMillis() / 1000; - db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + - "(" + TEMP_TABLE_COLUMN_ADDRES + ", " + AutocryptStatus.UID_KEY_STATUS + ", " + - AutocryptStatus.UID_ADDRESS + ", " + AutocryptStatus.UID_MASTER_KEY_ID - + ", " + AutocryptStatus.UID_CANDIDATES + ")" + - " SELECT " + TEMP_TABLE_COLUMN_ADDRES + ", " + - "CASE ( MIN (" + Tables.CERTS + "." + Certs.VERIFIED + " ) ) " - // remap to keep this provider contract independent from our internal representation - + " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED - + " END AS " + AutocryptStatus.UID_KEY_STATUS - + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID - + ", " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + ", COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ")" - + " FROM " + TEMP_TABLE_QUERIED_ADDRESSES - + " LEFT JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.EMAIL + cmpOperator + - TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - + ")" - + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.USER_PACKETS + "." + - UserPackets.MASTER_KEY_ID - + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_PACKETS + "." + - UserPackets.RANK - + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" - + ")" - + " WHERE (EXISTS (SELECT 1 FROM " + Tables.KEYS + " WHERE " - + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.USER_PACKETS + "." + - UserPackets.MASTER_KEY_ID - + " AND " + Tables.KEYS + "." + Keys.RANK + " = 0" - + " AND " + Tables.KEYS + "." + Keys.IS_REVOKED + " = 0" - + " AND NOT " + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + - "." + Keys.EXPIRY - + " < " + unixSeconds + ")" - + ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL" - + " GROUP BY " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES); + private Map loadUidStatusMap(String[] selectionArgs, boolean isWildcardSelector) { + UserIdDao userIdDao = UserIdDao.getInstance(getContext()); + if (isWildcardSelector) { + UidStatus uidStatus = userIdDao.getUidStatusByEmailLike(selectionArgs[0]); + return Collections.singletonMap(selectionArgs[0], uidStatus); + } else { + return userIdDao.getUidStatusByEmail(selectionArgs); + } + } + + private Map loadAutocryptRecommendationMap( + String[] selectionArgs, String callingPackageName) { + AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getContext(), callingPackageName); + return autocryptInteractor.determineAutocryptRecommendations(selectionArgs); } private int getPeerStateValue(AutocryptState autocryptState) { @@ -394,6 +253,11 @@ public class KeychainExternalProvider extends ContentProvider { throw new IllegalStateException("Unhandled case!"); } + @Override + public String getType(@NonNull Uri uri) { + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + @Override public Uri insert(@NonNull Uri uri, ContentValues values) { throw new UnsupportedOperationException(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index d40cbfe12..5e7519a7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -52,8 +52,11 @@ import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener; import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener; import eu.davidea.flexibleadapter.SelectableAdapter.Mode; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.KeySyncParcel; @@ -61,9 +64,6 @@ import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem; @@ -492,7 +492,7 @@ public class KeyListFragment extends RecyclerFragment= strftime('%s', 'now')); + unifiedKeyView: CREATE VIEW unifiedKeyView AS SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.user_id, user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, keys.can_certify, certs.verified, @@ -86,11 +105,11 @@ SELECT fingerprint selectEffectiveSignKeyIdByMasterKeyId: SELECT key_id FROM keys - WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) + WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= strftime('%s', 'now') ) AND can_sign = 1 AND master_key_id = ?; selectEffectiveAuthKeyIdByMasterKeyId: SELECT key_id FROM keys - WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) + WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= strftime('%s', 'now') ) AND can_authenticate = 1 AND master_key_id = ?; diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq index 81b5d94e9..e152d7fed 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq @@ -16,6 +16,9 @@ CREATE TABLE IF NOT EXISTS user_packets( keyrings_public(master_key_id) ON DELETE CASCADE ); +insertUserPacket: +INSERT INTO user_packets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + selectUserIdsByMasterKeyId: SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, MIN(certs.verified) AS verified_int FROM user_packets @@ -46,3 +49,23 @@ SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) WHERE user_packets.type = ? AND user_packets.master_key_id = ? AND user_packets.rank = ? GROUP BY user_packets.master_key_id, user_packets.rank; + + +uidStatus: +CREATE VIEW uidStatus AS + SELECT user_packets.email, MIN(certs.verified) AS key_status_int, user_packets.user_id, user_packets.master_key_id, COUNT(DISTINCT user_packets.master_key_id) AS candidates + FROM user_packets + JOIN validMasterKeys USING (master_key_id) + LEFT JOIN certs ON (certs.master_key_id = user_packets.master_key_id AND certs.rank = user_packets.rank AND certs.verified > 0) + WHERE user_packets.email IS NOT NULL + GROUP BY user_packets.email; + +selectUserIdStatusByEmail: +SELECT * +FROM uidStatus + WHERE email IN ?; + +selectUserIdStatusByEmailLike: +SELECT * +FROM uidStatus + WHERE email LIKE ?; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index 27ffb3ffd..327145607 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -18,19 +18,18 @@ import org.robolectric.shadows.ShadowBinder; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowPackageManager; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.daos.ApiAppDao; -import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest; -import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -49,10 +48,7 @@ public class KeychainExternalProviderTest { static final String PACKAGE_NAME = "test.package"; static final byte[] PACKAGE_SIGNATURE = new byte[] { 1, 2, 3 }; static final String MAIL_ADDRESS_1 = "twi@openkeychain.org"; - static final String MAIL_ADDRESS_2 = "pink@openkeychain.org"; - static final String MAIL_ADDRESS_SEC_1 = "twi-sec@openkeychain.org"; static final String USER_ID_1 = "twi "; - static final String USER_ID_SEC_1 = "twi "; static final long KEY_ID_SECRET = 0x5D4DA4423C39122FL; static final long KEY_ID_PUBLIC = 0x9A282CE2AB44A382L; public static final String AUTOCRYPT_PEER = "tid"; @@ -92,8 +88,8 @@ public class KeychainExternalProviderTest { apiAppDao.deleteApiApp(PACKAGE_NAME); contentResolver.query( - EmailStatus.CONTENT_URI, - new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, + AutocryptStatus.CONTENT_URI, + new String[] { AutocryptStatus.ADDRESS }, null, new String [] { }, null ); } @@ -104,106 +100,12 @@ public class KeychainExternalProviderTest { apiAppDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 })); contentResolver.query( - EmailStatus.CONTENT_URI, - new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, + AutocryptStatus.CONTENT_URI, + new String[] { AutocryptStatus.ADDRESS }, null, new String [] { }, null ); } - @Test - public void testEmailStatus_withNonExistentAddress() throws Exception { - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); - assertTrue(cursor.isNull(2)); - } - - @Test - public void testEmailStatus() throws Exception { - insertPublicKeyringFrom("/test-keys/testring.pub"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1)); - assertEquals("twi ", cursor.getString(2)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_multiple() throws Exception { - insertPublicKeyringFrom("/test-keys/testring.pub"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1, MAIL_ADDRESS_2 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToNext()); - assertEquals(MAIL_ADDRESS_2, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); - assertTrue(cursor.isNull(2)); - assertTrue(cursor.moveToNext()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1)); - assertEquals("twi ", cursor.getString(2)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_withSecretKey() throws Exception { - insertSecretKeyringFrom("/test-keys/testring.sec"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_SEC_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_SEC_1, cursor.getString(0)); - assertEquals(USER_ID_SEC_1, cursor.getString(2)); - assertEquals(2, cursor.getInt(1)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_withConfirmedKey() throws Exception { - insertSecretKeyringFrom("/test-keys/testring.sec"); - insertPublicKeyringFrom("/test-keys/testring.pub"); - - certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID, EmailStatus.USER_ID_STATUS}, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(USER_ID_1, cursor.getString(1)); - assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(2)); - assertFalse(cursor.moveToNext()); - } - @Test public void testAutocryptStatus_autocryptPeer_withUnconfirmedKey() throws Exception { insertSecretKeyringFrom("/test-keys/testring.sec");