From d4731f68bd30a762d8ab8ea4b53976dbf76704b3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 21 Nov 2016 15:37:24 +0100 Subject: [PATCH 01/33] introduce minimize extra to ACTION_GET_KEY --- .../keychain/pgp/CanonicalizedKeyRing.java | 13 +++ .../pgp/CanonicalizedPublicKeyRing.java | 72 +++++++++++++- .../keychain/pgp/PGPPublicKeyUtils.java | 99 +++++++++++++++++++ .../keychain/pgp/UncachedPublicKey.java | 24 ++++- .../keychain/remote/OpenPgpService.java | 5 + extern/openpgp-api-lib | 2 +- 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 8f026561c..9f7c80ffc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -62,6 +62,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getRing().getPublicKey().getFingerprint(); } + public byte[] getRawPrimaryUserId() throws PgpKeyNotFoundException { + return getPublicKey().getRawPrimaryUserId(); + } + public String getPrimaryUserId() throws PgpKeyNotFoundException { return getPublicKey().getPrimaryUserId(); } @@ -136,6 +140,15 @@ public abstract class CanonicalizedKeyRing extends KeyRing { } } + public long getSigningId() throws PgpKeyNotFoundException { + for(CanonicalizedPublicKey key : publicKeyIterator()) { + if (key.canSign() && key.isValid()) { + return key.getKeyId(); + } + } + throw new PgpKeyNotFoundException("No valid signing key found!"); + } + public void encode(OutputStream stream) throws IOException { getRing().encode(stream); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 1868b27d5..93b819af6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -18,6 +18,13 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import android.support.annotation.Nullable; + import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -27,8 +34,6 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; -import java.io.IOException; -import java.util.Iterator; public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { @@ -84,6 +89,69 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { }); } + /** Returns a minimized version of this key. + * + * The minimized version includes: + * - the master key + * - the current best signing key (if any) + * - one encryption key (if any) + * - the user id that matches the userIdToKeep parameter, or the primary user id if none matches + * each with their most recent binding certificates + */ + public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException, PgpKeyNotFoundException { + CanonicalizedPublicKey masterKey = getPublicKey(); + PGPPublicKey masterPubKey = masterKey.getPublicKey(); + boolean userIdStrippedOk = false; + if (userIdToKeep != null) { + try { + masterPubKey = PGPPublicKeyUtils.keepOnlyUserId(masterPubKey, userIdToKeep); + userIdStrippedOk = true; + } catch (NoSuchElementException e) { + // will be handled because userIdStrippedOk is false + } + } + + if (!userIdStrippedOk) { + byte[] rawPrimaryUserId = getRawPrimaryUserId(); + masterPubKey = PGPPublicKeyUtils.keepOnlyRawUserId(masterPubKey, rawPrimaryUserId); + } + + masterPubKey = PGPPublicKeyUtils.keepOnlySelfCertsForUserIds(masterPubKey); + masterPubKey = PGPPublicKeyUtils.removeAllUserAttributes(masterPubKey); + masterPubKey = PGPPublicKeyUtils.removeAllDirectKeyCerts(masterPubKey); + + PGPPublicKeyRing resultRing = new PGPPublicKeyRing(masterPubKey.getEncoded(), new JcaKeyFingerprintCalculator()); + + Long encryptId; + try { + encryptId = getEncryptId(); + // only add if this key doesn't coincide with master key + if (encryptId != getMasterKeyId()) { + CanonicalizedPublicKey encryptKey = getPublicKey(encryptId); + PGPPublicKey encryptPubKey = encryptKey.getPublicKey(); + resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, encryptPubKey); + } + } catch (PgpKeyNotFoundException e) { + // no encryption key: can't be reasonably minimized + return null; + } + + try { + long signingId = getSigningId(); + // only add if this key doesn't coincide with master or encryption key + if (signingId != encryptId && signingId != getMasterKeyId()) { + CanonicalizedPublicKey signingKey = getPublicKey(signingId); + PGPPublicKey signingPubKey = signingKey.getPublicKey(); + resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, signingPubKey); + } + } catch (PgpKeyNotFoundException e) { + // no signing key: can't be reasonably minimized + return null; + } + + return new CanonicalizedPublicKeyRing(resultRing, getVerified()); + } + /** Create a dummy secret ring from this key */ public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) { PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java new file mode 100644 index 000000000..a3909aff8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java @@ -0,0 +1,99 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.sufficientlysecure.keychain.util.Utf8Util; + + +@SuppressWarnings("unchecked") // BouncyCastle doesn't do generics here :( +class PGPPublicKeyUtils { + + static PGPPublicKey keepOnlyRawUserId(PGPPublicKey masterPublicKey, byte[] rawUserIdToKeep) { + boolean elementToKeepFound = false; + + Iterator it = masterPublicKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + if (Arrays.equals(rawUserId, rawUserIdToKeep)) { + elementToKeepFound = true; + } else { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId); + } + } + + if (!elementToKeepFound) { + throw new NoSuchElementException(); + } + return masterPublicKey; + } + + static PGPPublicKey keepOnlyUserId(PGPPublicKey masterPublicKey, String userIdToKeep) { + boolean elementToKeepFound = false; + + Iterator it = masterPublicKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); + if (userId.contains(userIdToKeep)) { + elementToKeepFound = true; + } else { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId); + } + } + + if (!elementToKeepFound) { + throw new NoSuchElementException(); + } + return masterPublicKey; + } + + static PGPPublicKey keepOnlySelfCertsForUserIds(PGPPublicKey masterPubKey) { + long masterKeyId = masterPubKey.getKeyID(); + + Iterator it = masterPubKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + masterPubKey = keepOnlySelfCertsForRawUserId(masterPubKey, masterKeyId, rawUserId); + } + + return masterPubKey; + } + + private static PGPPublicKey keepOnlySelfCertsForRawUserId( + PGPPublicKey masterPubKey, long masterKeyId, byte[] rawUserId) { + Iterator it = masterPubKey.getSignaturesForID(rawUserId); + while (it.hasNext()) { + PGPSignature sig = it.next(); + if (sig.getKeyID() != masterKeyId) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, rawUserId, sig); + } + } + return masterPubKey; + } + + static PGPPublicKey removeAllUserAttributes(PGPPublicKey masterPubKey) { + Iterator it = masterPubKey.getUserAttributes(); + + while (it.hasNext()) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next()); + } + + return masterPubKey; + } + + static PGPPublicKey removeAllDirectKeyCerts(PGPPublicKey masterPubKey) { + Iterator it = masterPubKey.getSignaturesOfType(PGPSignature.DIRECT_KEY); + + while (it.hasNext()) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next()); + } + + return masterPubKey; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 77ce0dc90..4305d8a12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -105,6 +105,24 @@ public class UncachedPublicKey { * */ public String getPrimaryUserId() { + byte[] found = getRawPrimaryUserId(); + if (found != null) { + return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found); + } else { + return null; + } + } + + /** Returns the primary user id, as indicated by the public key's self certificates. + * + * This is an expensive operation, since potentially a lot of certificates (and revocations) + * have to be checked, and even then the result is NOT guaranteed to be constant through a + * canonicalization operation. + * + * Returns null if there is no primary user id (as indicated by certificates) + * + */ + public byte[] getRawPrimaryUserId() { byte[] found = null; PGPSignature foundSig = null; // noinspection unchecked @@ -161,11 +179,7 @@ public class UncachedPublicKey { } } } - if (found != null) { - return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found); - } else { - return null; - } + return found; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 42d0b1490..bec9ef501 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -544,6 +544,11 @@ public class OpenPgpService extends Service { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + if (data.getBooleanExtra(OpenPgpApi.EXTRA_MINIMIZE, false)) { + String userIdToKeep = data.getStringExtra(OpenPgpApi.EXTRA_MINIMIZE_USER_ID); + keyRing = keyRing.minimize(userIdToKeep); + } + boolean requestedKeyData = outputStream != null; if (requestedKeyData) { boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false); diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 395fe837a..182852fce 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 395fe837a23fc33c7a4abbb0625cb584c5dbb176 +Subproject commit 182852fcec5d62024e8e5fb2c84272cae63a8ba0 From 55bffbbcbe7daf9132e87227b0ce5b856c4424f6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 28 Nov 2016 08:11:34 +0100 Subject: [PATCH 02/33] add trust id database table --- .../keychain/provider/KeychainContract.java | 7 ++++ .../keychain/provider/KeychainDatabase.java | 37 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) 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 79bef905c..87fb5992f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -95,6 +95,13 @@ public class KeychainContract { String IDENTIFIER = "identifier"; } + interface ApiTrustIdentityColumns { + String PACKAGE_NAME = "package_name"; + String IDENTIFIER = "identifier"; + String LAST_UPDATED = "last_updated"; + String MASTER_KEY_ID = "master_key_id"; + } + public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; private static final Uri BASE_CONTENT_URI_INTERNAL = Uri diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 8c45264ab..983b4fab2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -33,6 +33,7 @@ import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentityColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; @@ -52,7 +53,7 @@ import org.sufficientlysecure.keychain.util.Log; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 22; + private static final int DATABASE_VERSION = 23; private Context mContext; public interface Tables { @@ -65,6 +66,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { String API_APPS = "api_apps"; String API_ALLOWED_KEYS = "api_allowed_keys"; String OVERRIDDEN_WARNINGS = "overridden_warnings"; + String API_TRUST_IDENTITIES = "api_trust_identities"; } private static final String CREATE_KEYRINGS_PUBLIC = @@ -156,6 +158,20 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; + private static final String CREATE_API_TRUST_IDENTITIES = + "CREATE TABLE IF NOT EXISTS " + Tables.API_TRUST_IDENTITIES + " (" + + ApiTrustIdentityColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.IDENTIFIER + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.LAST_UPDATED + " INTEGER NOT NULL, " + + ApiTrustIdentityColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " + + "PRIMARY KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ", " + + ApiTrustIdentityColumns.IDENTIFIER + "), " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.MASTER_KEY_ID + ") REFERENCES " + + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE, " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ") REFERENCES " + + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + ")"; + private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " @@ -199,6 +215,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); + db.execSQL(CREATE_API_TRUST_IDENTITIES); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " @@ -318,7 +335,23 @@ public class KeychainDatabase extends SQLiteOpenHelper { case 21: db.execSQL("ALTER TABLE updated_keys ADD COLUMN seen_on_keyservers INTEGER;"); - if (oldVersion == 18 || oldVersion == 19 || oldVersion == 20 || oldVersion == 21) { + case 22: + db.execSQL( + "CREATE TABLE IF NOT EXISTS " + Tables.API_TRUST_IDENTITIES + " (" + + ApiTrustIdentityColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.IDENTIFIER + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.LAST_UPDATED + " INTEGER NOT NULL, " + + ApiTrustIdentityColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " + + "PRIMARY KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ", " + + ApiTrustIdentityColumns.IDENTIFIER + "), " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.MASTER_KEY_ID + ") REFERENCES " + + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE, " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ") REFERENCES " + + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + ")" + ); + + if (oldVersion == 18 || oldVersion == 19 || oldVersion == 20 || oldVersion == 21 || oldVersion == 22) { // no consolidate for now, often crashes! return; } From 1b50dbd83117fdbc4e9e8cf85e66972392551882 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 28 Nov 2016 08:13:23 +0100 Subject: [PATCH 03/33] add trust id internal access methods --- .../keychain/provider/KeychainContract.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 87fb5992f..b5a400408 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -128,6 +128,8 @@ public class KeychainContract { public static final String BASE_API_APPS = "api_apps"; public static final String PATH_ALLOWED_KEYS = "allowed_keys"; + public static final String BASE_TRUST_IDENTITIES = "trust_ids"; + public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; public static final String IS_REVOKED = KeysColumns.IS_REVOKED; @@ -339,6 +341,31 @@ public class KeychainContract { } } + public static class ApiTrustIdentity implements ApiTrustIdentityColumns, BaseColumns { + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() + .appendPath(BASE_TRUST_IDENTITIES).build(); + + /** + * Use if multiple items get returned + */ + public static final String CONTENT_TYPE + = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.trust_ids"; + + /** + * Use if a single item is returned + */ + public static final String CONTENT_ITEM_TYPE + = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.trust_ids"; + + public static Uri buildByPackageNameUri(String packageName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); + } + + public static Uri buildByPackageNameAndTrustIdUri(String packageName, String trustId) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendEncodedPath(trustId).build(); + } + } + public static class Certs implements CertsColumns, BaseColumns { public static final String USER_ID = UserPacketsColumns.USER_ID; public static final String NAME = UserPacketsColumns.NAME; From d5eb90f06750ed38e06ad7826928291bd32d7896 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 28 Nov 2016 08:13:37 +0100 Subject: [PATCH 04/33] add trust id external access methods --- .../provider/KeychainExternalContract.java | 24 +++- .../remote/KeychainExternalProvider.java | 107 ++++++++++++++++-- 2 files changed, 119 insertions(+), 12 deletions(-) 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 f4d9d657b..d09046831 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -22,6 +22,7 @@ import android.net.Uri; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentityColumns; public class KeychainExternalContract { @@ -31,26 +32,39 @@ public class KeychainExternalContract { // this is in KeychainExternalContract already, but we want to be double // sure this isn't mixed up with the internal one! public static final String CONTENT_AUTHORITY_EXTERNAL = Constants.PROVIDER_AUTHORITY + ".exported"; - private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri .parse("content://" + CONTENT_AUTHORITY_EXTERNAL); - public static final String BASE_EMAIL_STATUS = "email_status"; + public static final String BASE_TRUST_IDENTITIES = "trust_ids"; + 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 String TRUST_ID_LAST_UPDATE = "trust_id_last_update"; 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 final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; + } + + public static class ApiTrustIdentity implements ApiTrustIdentityColumns, BaseColumns { + public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() + .appendPath(BASE_TRUST_IDENTITIES).build(); + + public static Uri buildByPackageNameUri(String packageName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); + } + + public static Uri buildByPackageNameAndTrustIdUri(String packageName, String trustId) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendEncodedPath(trustId).build(); + } } private KeychainExternalContract() { } - } 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 29d874f9a..82fa1a4f2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.remote; import java.security.AccessControlException; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -45,6 +46,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; +import org.sufficientlysecure.keychain.provider.KeychainExternalContract.ApiTrustIdentity; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.util.Log; @@ -52,6 +54,7 @@ import org.sufficientlysecure.keychain.util.Log; public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface { private static final int EMAIL_STATUS = 101; private static final int EMAIL_STATUS_INTERNAL = 102; + private static final int TRUST_IDENTITY = 201; private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 302; @@ -82,6 +85,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS + "/*", EMAIL_STATUS_INTERNAL); + matcher.addURI(authority, KeychainExternalContract.BASE_TRUST_IDENTITIES + "/*", TRUST_IDENTITY); + // can only query status of calling app - for internal use only! matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); @@ -174,9 +179,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC + " 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.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); projectionMap.put(EmailStatus.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID); + projectionMap.put(EmailStatus.USER_ID, + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); + projectionMap.put(EmailStatus.TRUST_ID_LAST_UPDATE, Tables.API_TRUST_IDENTITIES + "." + + ApiTrustIdentity.LAST_UPDATED + " AS " + EmailStatus.TRUST_ID_LAST_UPDATE); qb.setProjectionMap(projectionMap); if (projection == null) { @@ -189,13 +197,18 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC + 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.API_TRUST_IDENTITIES + " ON (" + + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " LIKE queried_addresses.address" + + " AND " + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + " = \"" + callingPackageName + "\"" + + ")" + " 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 + + "(" + 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; + groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + + ", " + Tables.CERTS + "." + UserPackets.MASTER_KEY_ID; List plist = Arrays.asList(projection); if (plist.contains(EmailStatus.USER_ID)) { groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID; @@ -211,6 +224,34 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC // uri to watch is all /key_rings/ uri = KeyRings.CONTENT_URI; + break; + } + + case TRUST_IDENTITY: { + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + if (projection == null) { + throw new IllegalArgumentException("Please provide a projection!"); + } + + HashMap projectionMap = new HashMap<>(); + projectionMap.put(ApiTrustIdentity._ID, "oid AS " + ApiTrustIdentity._ID); + projectionMap.put(ApiTrustIdentity.IDENTIFIER, ApiTrustIdentity.IDENTIFIER); + projectionMap.put(ApiTrustIdentity.MASTER_KEY_ID, ApiTrustIdentity.MASTER_KEY_ID); + projectionMap.put(ApiTrustIdentity.LAST_UPDATED, ApiTrustIdentity.LAST_UPDATED); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.API_TRUST_IDENTITIES); + + // allow access to columns of the calling package exclusively! + qb.appendWhere(Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + + " = " + mApiPermissionHelper.getCurrentCallingPackage()); + + qb.appendWhere(Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " = "); + qb.appendWhereEscapeString(uri.getLastPathSegment()); break; } @@ -273,12 +314,64 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC @Override public Uri insert(@NonNull Uri uri, ContentValues values) { - throw new UnsupportedOperationException(); + Log.v(Constants.TAG, "insert(uri=" + uri + ")"); + + int match = mUriMatcher.match(uri); + if (match != TRUST_IDENTITY) { + throw new UnsupportedOperationException(); + } + + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + Long masterKeyId = values.getAsLong(ApiTrustIdentity.MASTER_KEY_ID); + if (masterKeyId == null) { + throw new IllegalArgumentException("master_key_id must be a non-null value!"); + } + + ContentValues actualValues = new ContentValues(); + actualValues.put(ApiTrustIdentity.PACKAGE_NAME, mApiPermissionHelper.getCurrentCallingPackage()); + actualValues.put(ApiTrustIdentity.IDENTIFIER, uri.getLastPathSegment()); + actualValues.put(ApiTrustIdentity.MASTER_KEY_ID, masterKeyId); + actualValues.put(ApiTrustIdentity.LAST_UPDATED, new Date().getTime() / 1000); + + SQLiteDatabase db = getDb().getWritableDatabase(); + try { + db.insert(Tables.API_TRUST_IDENTITIES, null, actualValues); + return uri; + } finally { + db.close(); + } } @Override - public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { - throw new UnsupportedOperationException(); + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + Log.v(Constants.TAG, "delete(uri=" + uri + ")"); + + int match = mUriMatcher.match(uri); + if (match != TRUST_IDENTITY || selection != null || selectionArgs != null) { + throw new UnsupportedOperationException(); + } + + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + String actualSelection = ApiTrustIdentity.PACKAGE_NAME + " = ? AND " + ApiTrustIdentity.IDENTIFIER + " = ?"; + String[] actualSelectionArgs = new String[] { + mApiPermissionHelper.getCurrentCallingPackage(), + uri.getLastPathSegment() + }; + + SQLiteDatabase db = getDb().getWritableDatabase(); + try { + return db.delete(Tables.API_TRUST_IDENTITIES, actualSelection, actualSelectionArgs); + } finally { + db.close(); + } } @Override From c15762c5cf8c227c34b964858f70fe55125fda3a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 28 Nov 2016 08:14:02 +0100 Subject: [PATCH 05/33] service: add trust id result to decryptVerify operation --- .../TrustIdentityDataAccessObject.java | 86 +++++++++++++++++++ .../keychain/remote/OpenPgpService.java | 40 ++++++++- extern/openpgp-api-lib | 2 +- 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java new file mode 100644 index 000000000..9eaba736f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2014-2016 Vincent Breitmoser + * + * 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.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentity; + + +public class TrustIdentityDataAccessObject { + private final SimpleContentResolverInterface mQueryInterface; + private final String packageName; + + + public TrustIdentityDataAccessObject(Context context, String packageName) { + this.packageName = packageName; + + final ContentResolver contentResolver = context.getContentResolver(); + mQueryInterface = new SimpleContentResolverInterface() { + @Override + public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); + } + + @Override + public Uri insert(Uri contentUri, ContentValues values) { + return contentResolver.insert(contentUri, values); + } + + @Override + public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { + return contentResolver.update(contentUri, values, where, selectionArgs); + } + + @Override + public int delete(Uri contentUri, String where, String[] selectionArgs) { + return contentResolver.delete(contentUri, where, selectionArgs); + } + }; + } + + public TrustIdentityDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName) { + mQueryInterface = queryInterface; + this.packageName = packageName; + } + + public Long getMasterKeyIdForTrustId(String trustId) { + Cursor cursor = mQueryInterface.query( + ApiTrustIdentity.buildByPackageNameAndTrustId(packageName, trustId), null, null, null, null); + + try { + if (cursor != null && cursor.moveToFirst()) { + int masterKeyIdColumn = cursor.getColumnIndex(ApiTrustIdentity.MASTER_KEY_ID); + return cursor.getLong(masterKeyIdColumn); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index bec9ef501..c514bc3f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -46,6 +46,7 @@ import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.OpenPgpSignatureResult.TrustIdentityResult; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.BackupOperation; @@ -68,6 +69,7 @@ import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; +import org.sufficientlysecure.keychain.provider.TrustIdentityDataAccessObject; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; @@ -82,23 +84,26 @@ public class OpenPgpService extends Service { public static final int API_VERSION_WITHOUT_SIGNATURE_ONLY_FLAG = 8; public static final int API_VERSION_WITH_DECRYPTION_RESULT = 8; public static final int API_VERSION_WITH_RESULT_NO_SIGNATURE = 8; + public static final int API_VERSION_WITH_TRUST_IDENTITIES = 12; public static final List SUPPORTED_VERSIONS = - Collections.unmodifiableList(Arrays.asList(7, 8, 9, 10, 11)); + Collections.unmodifiableList(Arrays.asList(7, 8, 9, 10, 11, 12)); private ApiPermissionHelper mApiPermissionHelper; private KeyRepository mKeyRepository; private ApiDataAccessObject mApiDao; private OpenPgpServiceKeyIdExtractor mKeyIdExtractor; private ApiPendingIntentFactory mApiPendingIntentFactory; + private TrustIdentityDataAccessObject mTrustIdentityDao; @Override public void onCreate() { super.onCreate(); - mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this)); mKeyRepository = KeyRepository.createDatabaseInteractor(this); mApiDao = new ApiDataAccessObject(this); - + mApiPermissionHelper = new ApiPermissionHelper(this, mApiDao); + mTrustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), + mApiPermissionHelper.getCurrentCallingPackage()); mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext()); mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory); } @@ -528,9 +533,38 @@ public class OpenPgpService extends Service { } } + String trustIdentity = data.getStringExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY); + if (trustIdentity != null) { + if (targetApiVersion < API_VERSION_WITH_TRUST_IDENTITIES) { + throw new IllegalStateException("API version conflict, trust identities are supported v12 and up!"); + } + signatureResult = addTrustIdentityInfoToSignatureResult(signatureResult, trustIdentity); + } + result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult); } + private OpenPgpSignatureResult addTrustIdentityInfoToSignatureResult(OpenPgpSignatureResult signatureResult, + String trustIdentity) { + boolean hasValidSignature = + signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED || + signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED; + if (!hasValidSignature) { + return signatureResult; + } + + Long tofuTrustedMasterKeyId = mTrustIdentityDao.getMasterKeyIdForTrustId(trustIdentity); + + long masterKeyId = signatureResult.getKeyId(); + if (tofuTrustedMasterKeyId == null) { + return signatureResult.withTrustIdentityResult(TrustIdentityResult.NEW); + } else if (masterKeyId == tofuTrustedMasterKeyId) { + return signatureResult.withTrustIdentityResult(TrustIdentityResult.OK); + } else { + return signatureResult.withTrustIdentityResult(TrustIdentityResult.MISMATCH); + } + } + private Intent getKeyImpl(Intent data, OutputStream outputStream) { try { long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 182852fce..fde49c854 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 182852fcec5d62024e8e5fb2c84272cae63a8ba0 +Subproject commit fde49c854fd7aa217169061b35b97c9231119c18 From 84d5ca7cd9e9b4724756065ed5895eea92ff2d66 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 25 Nov 2016 20:44:14 +0100 Subject: [PATCH 06/33] wip: trust id logic --- .../pgp/PgpDecryptVerifyInputParcel.java | 2 - .../keychain/provider/KeychainProvider.java | 22 +++++ .../TrustIdentityDataAccessObject.java | 31 ++++++ .../keychain/remote/OpenPgpService.java | 99 +++++++++++++++++-- 4 files changed, 146 insertions(+), 8 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java index 767bf1e9d..ae538637a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.pgp; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; 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 2e8177d88..672a00134 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentity; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -929,6 +930,27 @@ public class KeychainProvider extends ContentProvider { db.update(Tables.UPDATED_KEYS, values, null, null); break; } + case TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID: { + Long masterKeyId = values.getAsLong(ApiTrustIdentity.MASTER_KEY_ID); + long updateTime = values.getAsLong(KeychainContract.ApiTrustIdentity.LAST_UPDATED); + if (masterKeyId == null) { + throw new IllegalArgumentException("master_key_id must be a non-null value!"); + } + + ContentValues actualValues = new ContentValues(); + String packageName = uri.getPathSegments().get(2); + actualValues.put(ApiTrustIdentity.PACKAGE_NAME, packageName); + actualValues.put(ApiTrustIdentity.IDENTIFIER, uri.getLastPathSegment()); + actualValues.put(ApiTrustIdentity.MASTER_KEY_ID, masterKeyId); + actualValues.put(ApiTrustIdentity.LAST_UPDATED, updateTime); + + try { + db.replace(Tables.API_TRUST_IDENTITIES, null, actualValues); + } finally { + db.close(); + } + break; + } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java index 9eaba736f..d804d3734 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TrustIdentityDataAccessObject.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.provider; +import java.util.Date; + import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -83,4 +85,33 @@ public class TrustIdentityDataAccessObject { return null; } + + public Date getLastUpdateForTrustId(String trustId) { + Cursor cursor = mQueryInterface.query(ApiTrustIdentity.buildByPackageNameAndTrustId(packageName, trustId), + null, null, null, null); + + try { + if (cursor != null && cursor.moveToFirst()) { + long lastUpdated = cursor.getColumnIndex(ApiTrustIdentity.LAST_UPDATED); + return new Date(lastUpdated); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + public void setMasterKeyIdForTrustId(String trustId, long masterKeyId, Date date) { + Date lastUpdated = getLastUpdateForTrustId(trustId); + if (lastUpdated != null && lastUpdated.after(date)) { + throw new IllegalArgumentException("Database entry was newer than the one to be inserted! Cannot backdate"); + } + + ContentValues cv = new ContentValues(); + cv.put(ApiTrustIdentity.MASTER_KEY_ID, masterKeyId); + cv.put(ApiTrustIdentity.LAST_UPDATED, date.getTime()); + mQueryInterface.update(ApiTrustIdentity.buildByPackageNameAndTrustId(packageName, trustId), cv, null, null); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index c514bc3f6..193964007 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -44,6 +44,7 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.OpenPgpInlineKeyUpdate; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult.TrustIdentityResult; @@ -63,9 +64,11 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; @@ -94,7 +97,6 @@ public class OpenPgpService extends Service { private ApiDataAccessObject mApiDao; private OpenPgpServiceKeyIdExtractor mKeyIdExtractor; private ApiPendingIntentFactory mApiPendingIntentFactory; - private TrustIdentityDataAccessObject mTrustIdentityDao; @Override public void onCreate() { @@ -102,8 +104,6 @@ public class OpenPgpService extends Service { mKeyRepository = KeyRepository.createDatabaseInteractor(this); mApiDao = new ApiDataAccessObject(this); mApiPermissionHelper = new ApiPermissionHelper(this, mApiDao); - mTrustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), - mApiPermissionHelper.getCurrentCallingPackage()); mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext()); mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory); } @@ -366,6 +366,36 @@ public class OpenPgpService extends Service { byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); String senderAddress = data.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS); + String trustId = data.getStringExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY); + OpenPgpInlineKeyUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA); + + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData()); + long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId(); + // this will merge if the key already exists - no worries! + KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing); + + TrustIdentityDataAccessObject trustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), + mApiPermissionHelper.getCurrentCallingPackage()); + + Date lastUpdate = trustIdentityDao.getLastUpdateForTrustId(trustId); + + Date updateTimestamp = inlineKeyUpdate.getTimestamp(); + boolean updateIsNewerThanLastUpdate = lastUpdate == null || lastUpdate.before(updateTimestamp); + if (updateIsNewerThanLastUpdate) { + Log.d(Constants.TAG, "Key for trust id is newer"); + + Long trustedMasterKeyId = trustIdentityDao.getMasterKeyIdForTrustId(trustId); + if (trustedMasterKeyId == null) { + Log.d(Constants.TAG, "No binding for trust id, pinning key"); + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } else if (inlineMasterKeyId == trustedMasterKeyId) { + Log.d(Constants.TAG, "Key id is the same - doing nothing"); + } else { + // TODO danger in result intent! + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } + } + PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable); long inputLength = data.getLongExtra(OpenPgpApi.EXTRA_DATA_LENGTH, InputData.UNKNOWN_FILESIZE); @@ -538,13 +568,13 @@ public class OpenPgpService extends Service { if (targetApiVersion < API_VERSION_WITH_TRUST_IDENTITIES) { throw new IllegalStateException("API version conflict, trust identities are supported v12 and up!"); } - signatureResult = addTrustIdentityInfoToSignatureResult(signatureResult, trustIdentity); + signatureResult = processTrustIdentityInfoToSignatureResult(signatureResult, trustIdentity); } result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult); } - private OpenPgpSignatureResult addTrustIdentityInfoToSignatureResult(OpenPgpSignatureResult signatureResult, + private OpenPgpSignatureResult processTrustIdentityInfoToSignatureResult(OpenPgpSignatureResult signatureResult, String trustIdentity) { boolean hasValidSignature = signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED || @@ -553,10 +583,13 @@ public class OpenPgpService extends Service { return signatureResult; } - Long tofuTrustedMasterKeyId = mTrustIdentityDao.getMasterKeyIdForTrustId(trustIdentity); + TrustIdentityDataAccessObject trustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), + mApiPermissionHelper.getCurrentCallingPackage()); + Long tofuTrustedMasterKeyId = trustIdentityDao.getMasterKeyIdForTrustId(trustIdentity); long masterKeyId = signatureResult.getKeyId(); if (tofuTrustedMasterKeyId == null) { + trustIdentityDao.setMasterKeyIdForTrustId(trustIdentity, masterKeyId, new Date()); return signatureResult.withTrustIdentityResult(TrustIdentityResult.NEW); } else if (masterKeyId == tofuTrustedMasterKeyId) { return signatureResult.withTrustIdentityResult(TrustIdentityResult.OK); @@ -708,6 +741,57 @@ public class OpenPgpService extends Service { } } + private Intent updateTrustIdKeyImpl(Intent data) { + try { + Intent result = new Intent(); + + String trustId = data.getStringExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY); + OpenPgpInlineKeyUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA); + if (inlineKeyUpdate == null || trustId == null) { + throw new IllegalArgumentException("need to specify both trust_id and inline_key_data!"); + } + + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData()); + long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId(); + // this will merge if the key already exists - no worries! + KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing); + + TrustIdentityDataAccessObject trustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), + mApiPermissionHelper.getCurrentCallingPackage()); + + Date lastUpdate = trustIdentityDao.getLastUpdateForTrustId(trustId); + + Date updateTimestamp = inlineKeyUpdate.getTimestamp(); + boolean updateIsNewerThanLastUpdate = lastUpdate == null || lastUpdate.before(updateTimestamp); + if (!updateIsNewerThanLastUpdate) { + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } + Log.d(Constants.TAG, "Key for trust id is newer"); + + Long trustedMasterKeyId = trustIdentityDao.getMasterKeyIdForTrustId(trustId); + if (trustedMasterKeyId == null) { + Log.d(Constants.TAG, "No binding for trust id, pinning key"); + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } else if (inlineMasterKeyId == trustedMasterKeyId) { + Log.d(Constants.TAG, "Key id is the same - doing nothing"); + } else { + // TODO danger in result intent! + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } + + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } catch (Exception e) { + Log.d(Constants.TAG, "exception in updateTrustIdKeyImpl", e); + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_ERROR, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + return result; + } + } + private Intent checkPermissionImpl(@NonNull Intent data) { Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data); if (permissionIntent != null) { @@ -879,6 +963,9 @@ public class OpenPgpService extends Service { case OpenPgpApi.ACTION_BACKUP: { return backupImpl(data, outputStream); } + case OpenPgpApi.ACTION_UPDATE_TRUST_ID: { + return updateTrustIdKeyImpl(data); + } default: { return null; } From c7bb6a7bc0f056fd9d795904414e82dcafe8e767 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 1 Feb 2017 10:20:21 +0100 Subject: [PATCH 07/33] extract trust id handling into method --- .../pgp/OpenPgpSignatureResultBuilder.java | 37 +++++----- .../keychain/remote/OpenPgpService.java | 72 +++++++++++-------- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 9c04c5394..80c2176c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -125,32 +125,37 @@ public class OpenPgpSignatureResultBuilder { } setSignatureKeyCertified(signingRing.getVerified() > 0); + ArrayList allUserIds = signingRing.getUnorderedUserIds(); + ArrayList confirmedUserIds; try { - ArrayList allUserIds = signingRing.getUnorderedUserIds(); - ArrayList confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); - setUserIds(allUserIds, confirmedUserIds); - - if (mSenderAddress != null) { - if (userIdListContainsAddress(mSenderAddress, confirmedUserIds)) { - mSenderStatusResult = SenderStatusResult.USER_ID_CONFIRMED; - } else if (userIdListContainsAddress(mSenderAddress, allUserIds)) { - mSenderStatusResult = SenderStatusResult.USER_ID_UNCONFIRMED; - } else { - mSenderStatusResult = SenderStatusResult.USER_ID_MISSING; - } - } else { - mSenderStatusResult = SenderStatusResult.UNKNOWN; - } - + confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); } catch (NotFoundException e) { throw new IllegalStateException("Key didn't exist anymore for user id query!", e); } + setUserIds(allUserIds, confirmedUserIds); + + mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds); // either master key is expired/revoked or this specific subkey is expired/revoked setKeyExpired(signingRing.isExpired() || signingKey.isExpired()); setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked()); } + private SenderStatusResult processSenderStatusResult( + ArrayList allUserIds, ArrayList confirmedUserIds) { + if (mSenderAddress == null) { + return SenderStatusResult.UNKNOWN; + } + + if (userIdListContainsAddress(mSenderAddress, confirmedUserIds)) { + return SenderStatusResult.USER_ID_CONFIRMED; + } else if (userIdListContainsAddress(mSenderAddress, allUserIds)) { + return SenderStatusResult.USER_ID_UNCONFIRMED; + } else { + return SenderStatusResult.USER_ID_MISSING; + } + } + private static boolean userIdListContainsAddress(String senderAddress, ArrayList confirmedUserIds) { for (String rawUserId : confirmedUserIds) { UserId userId = OpenPgpUtils.splitUserId(rawUserId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 193964007..667dbdc7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -65,6 +65,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeyRepository; @@ -366,35 +367,9 @@ public class OpenPgpService extends Service { byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); String senderAddress = data.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS); - String trustId = data.getStringExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY); - OpenPgpInlineKeyUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA); - - UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData()); - long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId(); - // this will merge if the key already exists - no worries! - KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing); - - TrustIdentityDataAccessObject trustIdentityDao = new TrustIdentityDataAccessObject(getBaseContext(), - mApiPermissionHelper.getCurrentCallingPackage()); - - Date lastUpdate = trustIdentityDao.getLastUpdateForTrustId(trustId); - - Date updateTimestamp = inlineKeyUpdate.getTimestamp(); - boolean updateIsNewerThanLastUpdate = lastUpdate == null || lastUpdate.before(updateTimestamp); - if (updateIsNewerThanLastUpdate) { - Log.d(Constants.TAG, "Key for trust id is newer"); - - Long trustedMasterKeyId = trustIdentityDao.getMasterKeyIdForTrustId(trustId); - if (trustedMasterKeyId == null) { - Log.d(Constants.TAG, "No binding for trust id, pinning key"); - trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); - } else if (inlineMasterKeyId == trustedMasterKeyId) { - Log.d(Constants.TAG, "Key id is the same - doing nothing"); - } else { - // TODO danger in result intent! - trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); - } - } + TrustIdentityDataAccessObject trustIdentityDao = new TrustIdentityDataAccessObject( + getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); + String senderTrustId = updateTrustIdStateFromIntent(data, trustIdentityDao); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable); @@ -471,7 +446,7 @@ public class OpenPgpService extends Service { if (prioritySecurityProblem.isIdentifiable()) { String identifier = prioritySecurityProblem.getIdentifier(); boolean isOverridden = OverriddenWarningsRepository.createOverriddenWarningsRepository(this) - .isWarningOverridden(identifier); + .isWarningOverridden(identifier); result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden); } } @@ -481,6 +456,43 @@ public class OpenPgpService extends Service { mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride)); } + private String updateTrustIdStateFromIntent(Intent data, TrustIdentityDataAccessObject trustIdentityDao) + throws PgpGeneralException, IOException { + String trustId = data.getStringExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY); + OpenPgpInlineKeyUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA); + if (inlineKeyUpdate == null) { + return null; + } + + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData()); + if (uncachedKeyRing.isSecret()) { + Log.e(Constants.TAG, "Found secret key in trust id! - Ignoring"); + return null; + } + // this will merge if the key already exists - no worries! + KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing); + long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId(); + + Date lastUpdate = trustIdentityDao.getLastUpdateForTrustId(trustId); + Date updateTimestamp = inlineKeyUpdate.getTimestamp(); + Long trustedMasterKeyId = trustIdentityDao.getMasterKeyIdForTrustId(trustId); + + if (lastUpdate != null && lastUpdate.after(updateTimestamp)) { + Log.d(Constants.TAG, "Key for trust id is newer, ignoring other"); + return trustId; + } else if (trustedMasterKeyId == null) { + Log.d(Constants.TAG, "No binding for trust id, pinning key"); + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } else if (inlineMasterKeyId == trustedMasterKeyId) { + Log.d(Constants.TAG, "Key id is the same - doing nothing"); + } else { + // TODO danger in result intent! + trustIdentityDao.setMasterKeyIdForTrustId(trustId, inlineMasterKeyId, updateTimestamp); + } + + return trustId; + } + private void processDecryptionResultForResultIntent(int targetApiVersion, Intent result, OpenPgpDecryptionResult decryptionResult) { if (targetApiVersion < API_VERSION_WITH_DECRYPTION_RESULT) { From 31ef4c4789704ac0c2b9ad1d1c3f39d1dab0f878 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Nov 2016 15:52:40 +0100 Subject: [PATCH 08/33] list trust ids in ShowKeyFragment --- .../keychain/provider/KeychainContract.java | 23 ++-- .../keychain/provider/KeychainProvider.java | 59 ++++++++++ .../remote/KeychainExternalProvider.java | 4 + .../keychain/ui/adapter/TrustIdsAdapter.java | 101 ++++++++++++++++++ .../keychain/ui/keyview/ViewKeyFragment.java | 13 +++ .../keyview/presenter/TrustIdsPresenter.java | 92 ++++++++++++++++ .../ui/keyview/view/TrustIdsIdCardView.java | 78 ++++++++++++++ .../src/main/res/layout/trust_ids_card.xml | 23 ++++ .../src/main/res/layout/view_key_fragment.xml | 11 ++ .../res/layout/view_key_trust_id_item.xml | 30 ++++++ 10 files changed, 418 insertions(+), 16 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java create mode 100644 OpenKeychain/src/main/res/layout/trust_ids_card.xml create mode 100644 OpenKeychain/src/main/res/layout/view_key_trust_id_item.xml 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 b5a400408..276598f4b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -128,6 +128,9 @@ public class KeychainContract { public static final String BASE_API_APPS = "api_apps"; public static final String PATH_ALLOWED_KEYS = "allowed_keys"; + public static final String PATH_BY_PACKAGE_NAME = "by_package_name"; + public static final String PATH_BY_KEY_ID = "by_key_id"; + public static final String BASE_TRUST_IDENTITIES = "trust_ids"; public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { @@ -345,24 +348,12 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_TRUST_IDENTITIES).build(); - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.trust_ids"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.trust_ids"; - - public static Uri buildByPackageNameUri(String packageName) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); + public static Uri buildByKeyUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build(); } - public static Uri buildByPackageNameAndTrustIdUri(String packageName, String trustId) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendEncodedPath(trustId).build(); + public static Uri buildByPackageNameAndTrustId(String packageName, String trustId) { + return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).appendPath(trustId).build(); } } 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 672a00134..fc7c93eea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -79,6 +79,10 @@ public class KeychainProvider extends ContentProvider { private static final int UPDATED_KEYS = 500; private static final int UPDATED_KEYS_SPECIFIC = 501; + private static final int TRUST_IDS_BY_MASTER_KEY_ID = 601; + private static final int TRUST_IDS_BY_PACKAGE_NAME = 602; + private static final int TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID = 603; + protected UriMatcher mUriMatcher; /** @@ -191,6 +195,22 @@ public class KeychainProvider extends ContentProvider { matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS); + /** + * Trust Identity access + * + *
+         * trust_ids/by_key_id/_
+         *
+         * 
+ */ + matcher.addURI(authority, KeychainContract.BASE_TRUST_IDENTITIES + "/" + + KeychainContract.PATH_BY_KEY_ID + "/*", TRUST_IDS_BY_MASTER_KEY_ID); + matcher.addURI(authority, KeychainContract.BASE_TRUST_IDENTITIES + "/" + + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", TRUST_IDS_BY_PACKAGE_NAME); + matcher.addURI(authority, KeychainContract.BASE_TRUST_IDENTITIES + "/" + + KeychainContract.PATH_BY_PACKAGE_NAME + "/*/*", TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID); + + /** * to access table containing last updated dates of keys */ @@ -636,6 +656,45 @@ public class KeychainProvider extends ContentProvider { break; } + case TRUST_IDS_BY_MASTER_KEY_ID: + case TRUST_IDS_BY_PACKAGE_NAME: + case TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID: { + if (selection != null || selectionArgs != null) { + throw new UnsupportedOperationException(); + } + + HashMap projectionMap = new HashMap<>(); + projectionMap.put(ApiTrustIdentity._ID, "oid AS " + ApiTrustIdentity._ID); + projectionMap.put(ApiTrustIdentity.PACKAGE_NAME, ApiTrustIdentity.PACKAGE_NAME); + projectionMap.put(ApiTrustIdentity.IDENTIFIER, ApiTrustIdentity.IDENTIFIER); + projectionMap.put(ApiTrustIdentity.MASTER_KEY_ID, ApiTrustIdentity.MASTER_KEY_ID); + projectionMap.put(ApiTrustIdentity.LAST_UPDATED, ApiTrustIdentity.LAST_UPDATED); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.API_TRUST_IDENTITIES); + + if (match == TRUST_IDS_BY_MASTER_KEY_ID) { + long masterKeyId = Long.parseLong(uri.getLastPathSegment()); + + selection = Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.MASTER_KEY_ID + " = ?"; + selectionArgs = new String[] { Long.toString(masterKeyId) }; + } else if (match == TRUST_IDS_BY_PACKAGE_NAME) { + String packageName = uri.getPathSegments().get(2); + + selection = Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + " = ?"; + selectionArgs = new String[] { packageName }; + } else { // TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID + String packageName = uri.getPathSegments().get(2); + String trustId = uri.getPathSegments().get(3); + + selection = Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + " = ? AND " + + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " = ?"; + selectionArgs = new String[] { packageName, trustId }; + } + + break; + } + case UPDATED_KEYS: case UPDATED_KEYS_SPECIFIC: { HashMap projectionMap = new HashMap<>(); 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 82fa1a4f2..ff441abe0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -28,6 +28,7 @@ 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.net.Uri; @@ -285,6 +286,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC 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); + } } Log.d(Constants.TAG, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java new file mode 100644 index 000000000..8fd427b31 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Vincent Breitmoser + * + * 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.ui.adapter; + + +import java.util.HashMap; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentity; + + +public class TrustIdsAdapter extends CursorAdapter { + private static final String[] TRUST_IDS_PROJECTION = new String[] { + ApiTrustIdentity._ID, + ApiTrustIdentity.PACKAGE_NAME, + ApiTrustIdentity.IDENTIFIER, + }; + private static final int INDEX_PACKAGE_NAME = 1; + private static final int INDEX_TRUST_ID = 2; + + + protected LayoutInflater mInflater; + private HashMap appIconCache = new HashMap<>(); + + + public TrustIdsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mInflater = LayoutInflater.from(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + String packageName = cursor.getString(INDEX_PACKAGE_NAME); + String trustId = cursor.getString(INDEX_TRUST_ID); + + TextView vTrustId = (TextView) view.findViewById(R.id.trust_id_name); + ImageView vAppIcon = (ImageView) view.findViewById(R.id.trust_id_app_icon); + + Drawable drawable = getDrawableForPackageName(packageName); + vTrustId.setText(trustId); + vAppIcon.setImageDrawable(drawable); + } + + private Drawable getDrawableForPackageName(String packageName) { + if (appIconCache.containsKey(packageName)) { + return appIconCache.get(packageName); + } + + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); + + Drawable appIcon = pm.getApplicationIcon(ai); + appIconCache.put(packageName, appIcon); + + return appIcon; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + public static CursorLoader createLoader(Context context, Uri dataUri) { + Uri baseUri = ApiTrustIdentity.buildByKeyUri(dataUri); + return new CursorLoader(context, baseUri, TrustIdsAdapter.TRUST_IDS_PROJECTION, null, null, null); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.view_key_trust_id_item, parent, false); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index a5e6af591..f6629bd1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -37,11 +37,14 @@ import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter; import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter; import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter; import org.sufficientlysecure.keychain.ui.keyview.presenter.SystemContactPresenter; +import org.sufficientlysecure.keychain.ui.keyview.presenter.TrustIdsPresenter; import org.sufficientlysecure.keychain.ui.keyview.presenter.ViewKeyMvpView; import org.sufficientlysecure.keychain.ui.keyview.view.IdentitiesCardView; import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView; import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView; import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView; +import org.sufficientlysecure.keychain.ui.keyview.view.TrustIdsIdCardView; +import org.sufficientlysecure.keychain.util.Preferences; public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { @@ -54,10 +57,14 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { private static final int LOADER_ID_LINKED_CONTACT = 2; private static final int LOADER_ID_SUBKEY_STATUS = 3; private static final int LOADER_ID_KEYSERVER_STATUS = 4; + private static final int LOADER_ID_TRUST_IDS = 5; private IdentitiesCardView mIdentitiesCardView; private IdentitiesPresenter mIdentitiesPresenter; + private TrustIdsIdCardView mTrustIdsCard; + private TrustIdsPresenter mTrustIdsPresenter; + SystemContactCardView mSystemContactCard; SystemContactPresenter mSystemContactPresenter; @@ -88,6 +95,8 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { mIdentitiesCardView = (IdentitiesCardView) view.findViewById(R.id.card_identities); + mTrustIdsCard = (TrustIdsIdCardView) view.findViewById(R.id.view_key_card_trust_ids); + mSystemContactCard = (SystemContactCardView) view.findViewById(R.id.linked_system_contact_card); mKeyStatusHealth = (KeyHealthView) view.findViewById(R.id.key_status_health); mKeyStatusKeyserver = (KeyserverStatusView) view.findViewById(R.id.key_status_keyserver); @@ -117,6 +126,10 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { mKeyserverStatusPresenter = new KeyserverStatusPresenter( getContext(), mKeyStatusKeyserver, LOADER_ID_KEYSERVER_STATUS, masterKeyId, mIsSecret); mKeyserverStatusPresenter.startLoader(getLoaderManager()); + + mTrustIdsPresenter = new TrustIdsPresenter( + getContext(), mTrustIdsCard, LOADER_ID_TRUST_IDS, masterKeyId, false); + mTrustIdsPresenter.startLoader(getLoaderManager()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java new file mode 100644 index 000000000..b33497958 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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.ui.keyview.presenter; + + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.Loader; + +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.ui.adapter.TrustIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; + + +public class TrustIdsPresenter implements LoaderCallbacks { + private final Context context; + private final TrustIdsMvpView view; + private final int loaderId; + + private final TrustIdsAdapter trustIdsAdapter; + + private final long masterKeyId; + private final boolean isSecret; + + public TrustIdsPresenter(Context context, TrustIdsMvpView view, int loaderId, long masterKeyId, boolean isSecret) { + this.context = context; + this.view = view; + this.loaderId = loaderId; + + this.masterKeyId = masterKeyId; + this.isSecret = isSecret; + + trustIdsAdapter = new TrustIdsAdapter(context, null, 0); + view.setTrustIdAdapter(trustIdsAdapter); + + view.setTrustIdClickListener(new TrustIdsClickListener() { + @Override + public void onTrustIdItemClick(int position) { + + } + }); + } + + public void startLoader(LoaderManager loaderManager) { + loaderManager.restartLoader(loaderId, null, this); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return TrustIdsAdapter.createLoader(context, KeyRings.buildUnifiedKeyRingUri(masterKeyId)); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + trustIdsAdapter.swapCursor(data); + view.showCard(trustIdsAdapter.getCount() > 0); + } + + @Override + public void onLoaderReset(Loader loader) { + trustIdsAdapter.swapCursor(null); + } + + public interface TrustIdsMvpView { + void setTrustIdAdapter(TrustIdsAdapter trustIdsAdapter); + void showCard(boolean show); + + void setTrustIdClickListener(TrustIdsClickListener trustIdsClickListener); + } + + public interface TrustIdsClickListener { + void onTrustIdItemClick(int position); + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java new file mode 100644 index 000000000..93fa03750 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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.ui.keyview.view; + + +import android.content.Context; +import android.support.v7.widget.CardView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.TrustIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesCardListener; +import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesMvpView; +import org.sufficientlysecure.keychain.ui.keyview.presenter.LinkedIdentitiesPresenter.LinkedIdsClickListener; +import org.sufficientlysecure.keychain.ui.keyview.presenter.LinkedIdentitiesPresenter.LinkedIdsMvpView; +import org.sufficientlysecure.keychain.ui.keyview.presenter.TrustIdsPresenter.TrustIdsClickListener; +import org.sufficientlysecure.keychain.ui.keyview.presenter.TrustIdsPresenter.TrustIdsMvpView; + + +public class TrustIdsIdCardView extends CardView implements TrustIdsMvpView { + private ListView vTrustIds; + + private TrustIdsClickListener trustIdsClickListener; + + public TrustIdsIdCardView(Context context, AttributeSet attrs) { + super(context, attrs); + + View view = LayoutInflater.from(context).inflate(R.layout.trust_ids_card, this, true); + + vTrustIds = (ListView) view.findViewById(R.id.view_key_trust_ids); + vTrustIds.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (trustIdsClickListener != null) { + trustIdsClickListener.onTrustIdItemClick(position); + } + } + }); + } + + @Override + public void setTrustIdAdapter(TrustIdsAdapter trustIdsAdapter) { + vTrustIds.setAdapter(trustIdsAdapter); + } + + @Override + public void showCard(boolean show) { + setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void setTrustIdClickListener(TrustIdsClickListener trustIdsClickListener) { + this.trustIdsClickListener = trustIdsClickListener; + } +} diff --git a/OpenKeychain/src/main/res/layout/trust_ids_card.xml b/OpenKeychain/src/main/res/layout/trust_ids_card.xml new file mode 100644 index 000000000..f99d73c10 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/trust_ids_card.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_fragment.xml index 16fa99c97..8f2b443b5 100644 --- a/OpenKeychain/src/main/res/layout/view_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_fragment.xml @@ -51,6 +51,17 @@ + + + + + + + + + From 930d2c6e3e5a8017167978acfae8ca635b7dea72 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 28 Nov 2016 08:02:22 +0100 Subject: [PATCH 09/33] trustid: show intent icon for trust ids --- .../keychain/ui/adapter/TrustIdsAdapter.java | 29 +++++++++++++++++-- .../main/res/drawable/ic_chat_black_24dp.xml | 9 ++++++ .../res/layout/view_key_trust_id_item.xml | 10 +++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 OpenKeychain/src/main/res/drawable/ic_chat_black_24dp.xml diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java index 8fd427b31..f5560ac15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java @@ -21,7 +21,9 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.util.HashMap; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.Cursor; @@ -31,10 +33,12 @@ import android.support.v4.content.CursorLoader; import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentity; @@ -59,16 +63,35 @@ public class TrustIdsAdapter extends CursorAdapter { } @Override - public void bindView(View view, Context context, Cursor cursor) { - String packageName = cursor.getString(INDEX_PACKAGE_NAME); - String trustId = cursor.getString(INDEX_TRUST_ID); + public void bindView(View view, final Context context, Cursor cursor) { + final String packageName = cursor.getString(INDEX_PACKAGE_NAME); + final String trustId = cursor.getString(INDEX_TRUST_ID); TextView vTrustId = (TextView) view.findViewById(R.id.trust_id_name); ImageView vAppIcon = (ImageView) view.findViewById(R.id.trust_id_app_icon); + ImageView vActionIcon = (ImageView) view.findViewById(R.id.trust_id_action); Drawable drawable = getDrawableForPackageName(packageName); vTrustId.setText(trustId); vAppIcon.setImageDrawable(drawable); + vActionIcon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + launchTrustIdActivity(packageName, trustId, context); + } + }); + } + + private void launchTrustIdActivity(String packageName, String trustId, Context context) { + try { + Intent intent = new Intent(); + intent.setAction(packageName + ".TRUST_ID_ACTION"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY, trustId); + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + // can't help it + } } private Drawable getDrawableForPackageName(String packageName) { diff --git a/OpenKeychain/src/main/res/drawable/ic_chat_black_24dp.xml b/OpenKeychain/src/main/res/drawable/ic_chat_black_24dp.xml new file mode 100644 index 000000000..e3489bdea --- /dev/null +++ b/OpenKeychain/src/main/res/drawable/ic_chat_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/OpenKeychain/src/main/res/layout/view_key_trust_id_item.xml b/OpenKeychain/src/main/res/layout/view_key_trust_id_item.xml index 97b7d2968..1c155d56e 100644 --- a/OpenKeychain/src/main/res/layout/view_key_trust_id_item.xml +++ b/OpenKeychain/src/main/res/layout/view_key_trust_id_item.xml @@ -27,4 +27,14 @@ tools:text="alice@example.com" /> + + From f1723dc454eb4f97bf4a6484a973b0d8aaf5e887 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 18 Dec 2016 13:05:37 +0100 Subject: [PATCH 10/33] show trust id package icon in key list --- .../keychain/provider/KeychainContract.java | 1 + .../keychain/provider/KeychainProvider.java | 8 ++ .../ui/adapter/KeySectionedListAdapter.java | 77 +++++++++++++++++-- .../ui/util/adapter/CursorAdapter.java | 3 +- .../src/main/res/layout/key_list_item.xml | 22 +++++- .../src/main/res/layout/trust_id_icon.xml | 8 ++ 6 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 OpenKeychain/src/main/res/layout/trust_id_icon.xml 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 276598f4b..0b4100de1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -145,6 +145,7 @@ public class KeychainContract { public static final String HAS_CERTIFY = "has_certify"; public static final String HAS_AUTHENTICATE = "has_authenticate"; public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id"; + public static final String API_KNOWN_TO_PACKAGE_NAMES = "known_to_apps"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); 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 fc7c93eea..99b583ed2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -342,6 +342,9 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.IS_EXPIRED, "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); + projectionMap.put(KeyRings.API_KNOWN_TO_PACKAGE_NAMES, + "GROUP_CONCAT(aTI." + ApiTrustIdentity.PACKAGE_NAME + ") AS " + + KeyRings.API_KNOWN_TO_PACKAGE_NAMES); qb.setProjectionMap(projectionMap); if (projection == null) { @@ -410,6 +413,11 @@ public class KeychainProvider extends ContentProvider { + " AND ( kC." + Keys.EXPIRY + " IS NULL OR kC." + Keys.EXPIRY + " >= " + new Date().getTime() / 1000 + " )" + ")" : "") + + (plist.contains(KeyRings.API_KNOWN_TO_PACKAGE_NAMES) ? + " LEFT JOIN " + Tables.API_TRUST_IDENTITIES + " AS aTI ON (" + +"aTI." + Keys.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + ")" : "") ); qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); // in case there are multiple verifying certificates diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java index da7612143..1c5f266b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java @@ -19,10 +19,13 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.text.format.DateUtils; @@ -38,6 +41,7 @@ import com.futuremind.recyclerviewfastscroll.SectionTitleProvider; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -47,6 +51,8 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; public class KeySectionedListAdapter extends SectionCursorAdapter packageNames = keyItem.getTrustIdPackages(); + + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + while (mTrustIdIcons.getChildCount() < packageNames.size()) { + layoutInflater.inflate(R.layout.trust_id_icon, mTrustIdIcons, true); + } + + int visibleIcons = 0; + for (int i = 0; i < packageNames.size(); i++) { + ImageView imageView = (ImageView) mTrustIdIcons.getChildAt(i); + Drawable drawable = getDrawableForPackageName(packageNames.get(i)); + if (drawable == null) { + continue; + } + + imageView.setImageDrawable(drawable); + imageView.setVisibility(View.VISIBLE); + visibleIcons += 1; + } + for (int i = visibleIcons; i < mTrustIdIcons.getChildCount(); i++) { + mTrustIdIcons.getChildAt(i).setVisibility(View.GONE); + } } } @@ -562,7 +595,8 @@ public class KeySectionedListAdapter extends SectionCursorAdapter 0; } + + public List getTrustIdPackages() { + int index = getColumnIndexOrThrow(KeyRings.API_KNOWN_TO_PACKAGE_NAMES); + String packageNames = getString(index); + if (packageNames == null) { + return Collections.EMPTY_LIST; + } + return Arrays.asList(packageNames.split(",")); + } } public interface KeyListListener { @@ -614,4 +657,24 @@ public class KeySectionedListAdapter extends SectionCursorAdapter appIconCache = new HashMap<>(); + + private Drawable getDrawableForPackageName(String packageName) { + if (appIconCache.containsKey(packageName)) { + return appIconCache.get(packageName); + } + + PackageManager pm = getContext().getPackageManager(); + try { + ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); + + Drawable appIcon = pm.getApplicationIcon(ai); + appIconCache.put(packageName, appIcon); + + return appIcon; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java index 75b1c9ff0..e9fb2f035 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java @@ -392,7 +392,8 @@ public abstract class CursorAdapter + android:focusable="false" + tools:layout_marginTop="30dp"> + android:paddingBottom="4dp"> + + + + + + + android:orientation="horizontal" + tools:visibility="gone"> + \ No newline at end of file From a211e527823499debb5b6d8dc69679c40ebab255 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Feb 2017 18:01:42 +0100 Subject: [PATCH 11/33] fix database migration and setup steps --- .../keychain/provider/KeychainDatabase.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 983b4fab2..517717867 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -336,20 +336,18 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("ALTER TABLE updated_keys ADD COLUMN seen_on_keyservers INTEGER;"); case 22: - db.execSQL( - "CREATE TABLE IF NOT EXISTS " + Tables.API_TRUST_IDENTITIES + " (" - + ApiTrustIdentityColumns.PACKAGE_NAME + " TEXT NOT NULL, " - + ApiTrustIdentityColumns.IDENTIFIER + " TEXT NOT NULL, " - + ApiTrustIdentityColumns.LAST_UPDATED + " INTEGER NOT NULL, " - + ApiTrustIdentityColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " - + "PRIMARY KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ", " - + ApiTrustIdentityColumns.IDENTIFIER + "), " - + "FOREIGN KEY(" + ApiTrustIdentityColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE, " - + "FOREIGN KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ") REFERENCES " - + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" - + ")" - ); + db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.API_TRUST_IDENTITIES + " (" + + ApiTrustIdentityColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.IDENTIFIER + " TEXT NOT NULL, " + + ApiTrustIdentityColumns.LAST_UPDATED + " INTEGER NOT NULL, " + + ApiTrustIdentityColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " + + "PRIMARY KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ", " + + ApiTrustIdentityColumns.IDENTIFIER + "), " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.MASTER_KEY_ID + ") REFERENCES " + + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE, " + + "FOREIGN KEY(" + ApiTrustIdentityColumns.PACKAGE_NAME + ") REFERENCES " + + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + ")"); if (oldVersion == 18 || oldVersion == 19 || oldVersion == 20 || oldVersion == 21 || oldVersion == 22) { // no consolidate for now, often crashes! From d0580dfafb8a5846acdc817aa1f402c3cff71d90 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 27 Feb 2017 18:02:12 +0100 Subject: [PATCH 12/33] return both trust ids and user ids in key queries --- .../keychain/remote/KeychainExternalProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ff441abe0..83eb7af4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -202,9 +202,10 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " LIKE queried_addresses.address" + " AND " + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + " = \"" + callingPackageName + "\"" + ")" - + " LEFT JOIN " + Tables.CERTS + " ON (" + + " 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 + ")" + + " OR " + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + ")" ); // in case there are multiple verifying certificates From ea953db5217bb574b4c20fbf9768921f82ba58d0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 3 Mar 2017 16:52:06 +0100 Subject: [PATCH 13/33] extract creation of error result intent into method --- .../keychain/remote/OpenPgpService.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 667dbdc7d..2f44423ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -305,19 +305,12 @@ public class OpenPgpService extends Service { private Intent getDryRunStatusResult(KeyIdResult keyIdResult) { switch (keyIdResult.getStatus()) { case MISSING: { - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_ERROR, - new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode")); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - return result; + return createErrorResultIntent(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, + "missing keys in opportunistic mode"); } case NO_KEYS: case NO_KEYS_ERROR: { - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_ERROR, - new OpenPgpError(OpenPgpError.NO_USER_IDS, "empty recipient list")); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - return result; + return createErrorResultIntent(OpenPgpError.NO_USER_IDS, "empty recipient list"); } case DUPLICATE: { Intent result = new Intent(); @@ -796,11 +789,7 @@ public class OpenPgpService extends Service { return result; } catch (Exception e) { Log.d(Constants.TAG, "exception in updateTrustIdKeyImpl", e); - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_ERROR, - new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - return result; + return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage()); } } From 3bf7fd54b0a04b4543aeabd43aea7800233ba3c3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 4 Mar 2017 12:46:58 +0100 Subject: [PATCH 14/33] show only a single trust id icon --- .../ui/adapter/KeySectionedListAdapter.java | 33 +++++++------------ .../src/main/res/layout/key_list_item.xml | 20 ++++------- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java index 1c5f266b0..53ad5f416 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java @@ -354,7 +354,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter packageNames = keyItem.getTrustIdPackages(); - LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - while (mTrustIdIcons.getChildCount() < packageNames.size()) { - layoutInflater.inflate(R.layout.trust_id_icon, mTrustIdIcons, true); - } - - int visibleIcons = 0; - for (int i = 0; i < packageNames.size(); i++) { - ImageView imageView = (ImageView) mTrustIdIcons.getChildAt(i); - Drawable drawable = getDrawableForPackageName(packageNames.get(i)); - if (drawable == null) { - continue; + if (!keyItem.isSecret() && !packageNames.isEmpty()) { + String packageName = packageNames.get(0); + Drawable drawable = getDrawableForPackageName(packageName); + if (drawable != null) { + mTrustIdIcon.setImageDrawable(drawable); + mTrustIdIcon.setVisibility(View.VISIBLE); + } else { + mTrustIdIcon.setVisibility(View.GONE); } - - imageView.setImageDrawable(drawable); - imageView.setVisibility(View.VISIBLE); - visibleIcons += 1; + } else { + mTrustIdIcon.setVisibility(View.GONE); } - for (int i = visibleIcons; i < mTrustIdIcons.getChildCount(); i++) { - mTrustIdIcons.getChildAt(i).setVisibility(View.GONE); - } - } } diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index dba6801ed..a49a42f9d 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -14,6 +14,13 @@ android:focusable="false" tools:layout_marginTop="30dp"> + + - - - - - - Date: Sat, 4 Mar 2017 12:47:14 +0100 Subject: [PATCH 15/33] show trust id activity button only if available --- .../keychain/ui/adapter/TrustIdsAdapter.java | 39 ++++++++++++++----- .../src/main/res/layout/key_list_item.xml | 1 - .../res/layout/view_key_trust_id_item.xml | 2 + 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java index f5560ac15..5b25538c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.util.HashMap; +import java.util.List; import android.app.Activity; import android.content.ActivityNotFoundException; @@ -26,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -74,26 +76,43 @@ public class TrustIdsAdapter extends CursorAdapter { Drawable drawable = getDrawableForPackageName(packageName); vTrustId.setText(trustId); vAppIcon.setImageDrawable(drawable); - vActionIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - launchTrustIdActivity(packageName, trustId, context); - } - }); + + if (isTrustIdActivityAvailable(packageName, trustId, context)) { + vActionIcon.setVisibility(View.VISIBLE); + vActionIcon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + launchTrustIdActivity(packageName, trustId, context); + } + }); + } else { + vActionIcon.setVisibility(View.GONE); + } } private void launchTrustIdActivity(String packageName, String trustId, Context context) { try { - Intent intent = new Intent(); - intent.setAction(packageName + ".TRUST_ID_ACTION"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY, trustId); + Intent intent = createTrustIdActivityIntent(packageName, trustId); context.startActivity(intent); } catch (ActivityNotFoundException e) { // can't help it } } + private Intent createTrustIdActivityIntent(String packageName, String trustId) { + Intent intent = new Intent(); + intent.setAction(packageName + ".TRUST_ID_ACTION"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(OpenPgpApi.EXTRA_TRUST_IDENTITY, trustId); + return intent; + } + + private boolean isTrustIdActivityAvailable(String packageName, String trustId, Context context) { + Intent intent = createTrustIdActivityIntent(packageName, trustId); + List resolveInfos = context.getPackageManager().queryIntentActivities(intent, 0); + return resolveInfos != null && !resolveInfos.isEmpty(); + } + private Drawable getDrawableForPackageName(String packageName) { if (appIconCache.containsKey(packageName)) { return appIconCache.get(packageName); diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index a49a42f9d..d106a29e5 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -2,7 +2,6 @@ From 12dec8cba8190a7951291ee9d40e08768853bd0d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 9 Mar 2017 18:57:56 +0100 Subject: [PATCH 16/33] preserve trust ids over key updates --- .../keychain/provider/KeyWritableRepository.java | 2 ++ .../keychain/provider/KeychainContract.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index abea30df4..739fc214a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -62,6 +62,7 @@ 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.ApiTrustIdentity; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -602,6 +603,7 @@ public class KeyWritableRepository extends KeyRepository { android.util.Log.e(Constants.TAG, "Could not delete file!", e); return false; } + mContentResolver.delete(ApiTrustIdentity.buildByMasterKeyId(masterKeyId),null, null); int deletedRows = mContentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); return deletedRows > 0; } 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 0b4100de1..645566c35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -356,6 +356,10 @@ public class KeychainContract { public static Uri buildByPackageNameAndTrustId(String packageName, String trustId) { return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).appendPath(trustId).build(); } + + public static Uri buildByMasterKeyId(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(Long.toString(masterKeyId)).build(); + } } public static class Certs implements CertsColumns, BaseColumns { From 846692e8ba497ae2154616abec05b5fe4ebf0c9f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 9 Mar 2017 18:58:20 +0100 Subject: [PATCH 17/33] add "forget" button to trust ids --- .../ui/adapter/CertSectionedListAdapter.java | 7 +- .../keychain/ui/adapter/TrustIdsAdapter.java | 131 +++++++++++++----- .../keychain/ui/keyview/ViewKeyFragment.java | 28 +++- .../keyview/presenter/TrustIdsPresenter.java | 36 +++-- .../ui/keyview/presenter/ViewKeyMvpView.java | 6 + .../ui/keyview/view/TrustIdsIdCardView.java | 33 +---- .../ui/util/adapter/CursorAdapter.java | 17 +-- .../ui/util/adapter/SectionCursorAdapter.java | 7 +- .../src/main/res/layout/trust_ids_card.xml | 2 +- .../src/main/res/layout/view_key_fragment.xml | 3 +- .../res/layout/view_key_trust_id_item.xml | 88 ++++++++---- 11 files changed, 231 insertions(+), 127 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java index e0a4df9c1..abc730292 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java @@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.ui.adapter.CertSectionedListAdapter.CertCursor; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; @@ -39,7 +40,7 @@ import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; import java.util.ArrayList; import java.util.Arrays; -public class CertSectionedListAdapter extends SectionCursorAdapter { private CertListListener mListener; @@ -162,11 +163,11 @@ public class CertSectionedListAdapter extends SectionCursorAdapter projection = new ArrayList<>(); - projection.addAll(Arrays.asList(AbstractCursor.PROJECTION)); + projection.addAll(Arrays.asList(SimpleCursor.PROJECTION)); projection.addAll(Arrays.asList( KeychainContract.Certs.MASTER_KEY_ID, KeychainContract.Certs.VERIFIED, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java index 5b25538c6..dd6a6595c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TrustIdsAdapter.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.util.HashMap; import java.util.List; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -32,7 +31,7 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.support.v4.content.CursorLoader; -import android.support.v4.widget.CursorAdapter; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -43,9 +42,14 @@ import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentity; +import org.sufficientlysecure.keychain.ui.adapter.TrustIdsAdapter.ViewHolder; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter.SimpleCursor; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener.OnItemClickListener; -public class TrustIdsAdapter extends CursorAdapter { +public class TrustIdsAdapter extends CursorAdapter { private static final String[] TRUST_IDS_PROJECTION = new String[] { ApiTrustIdentity._ID, ApiTrustIdentity.PACKAGE_NAME, @@ -55,39 +59,12 @@ public class TrustIdsAdapter extends CursorAdapter { private static final int INDEX_TRUST_ID = 2; - protected LayoutInflater mInflater; private HashMap appIconCache = new HashMap<>(); + private Integer expandedPosition; + private OnItemClickListener onItemClickListener; - - public TrustIdsAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - mInflater = LayoutInflater.from(context); - } - - @Override - public void bindView(View view, final Context context, Cursor cursor) { - final String packageName = cursor.getString(INDEX_PACKAGE_NAME); - final String trustId = cursor.getString(INDEX_TRUST_ID); - - TextView vTrustId = (TextView) view.findViewById(R.id.trust_id_name); - ImageView vAppIcon = (ImageView) view.findViewById(R.id.trust_id_app_icon); - ImageView vActionIcon = (ImageView) view.findViewById(R.id.trust_id_action); - - Drawable drawable = getDrawableForPackageName(packageName); - vTrustId.setText(trustId); - vAppIcon.setImageDrawable(drawable); - - if (isTrustIdActivityAvailable(packageName, trustId, context)) { - vActionIcon.setVisibility(View.VISIBLE); - vActionIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - launchTrustIdActivity(packageName, trustId, context); - } - }); - } else { - vActionIcon.setVisibility(View.GONE); - } + public TrustIdsAdapter(Context context, SimpleCursor simpleCursor) { + super(context, simpleCursor, FLAG_REGISTER_CONTENT_OBSERVER); } private void launchTrustIdActivity(String packageName, String trustId, Context context) { @@ -118,7 +95,7 @@ public class TrustIdsAdapter extends CursorAdapter { return appIconCache.get(packageName); } - PackageManager pm = mContext.getPackageManager(); + PackageManager pm = getContext().getPackageManager(); try { ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); @@ -136,8 +113,88 @@ public class TrustIdsAdapter extends CursorAdapter { return new CursorLoader(context, baseUri, TrustIdsAdapter.TRUST_IDS_PROJECTION, null, null, null); } + public void setExpandedView(Integer position) { + if (position == null) { + if (expandedPosition != null) { + notifyItemChanged(expandedPosition); + } + expandedPosition = null; + } else if (expandedPosition == null || !expandedPosition.equals(position)) { + if (expandedPosition != null) { + notifyItemChanged(expandedPosition); + } + expandedPosition = position; + notifyItemChanged(position); + } + } + + public void setOnItemClickListener(RecyclerItemClickListener.OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_trust_id_item, parent, false); + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(getContext()).inflate(R.layout.view_key_trust_id_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, final int position) { + moveCursorOrThrow(position); + + SimpleCursor cursor = getCursor(); + final String packageName = cursor.getString(INDEX_PACKAGE_NAME); + final String trustId = cursor.getString(INDEX_TRUST_ID); + + Drawable drawable = getDrawableForPackageName(packageName); + holder.vTrustId.setText(trustId); + holder.vAppIcon.setImageDrawable(drawable); + + if (isTrustIdActivityAvailable(packageName, trustId, getContext())) { + holder.vActionIcon.setVisibility(View.VISIBLE); + holder.vActionIcon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + launchTrustIdActivity(packageName, trustId, getContext()); + } + }); + } else { + holder.vActionIcon.setVisibility(View.GONE); + } + + if (expandedPosition != null && position == expandedPosition) { + holder.vButtonBar.setVisibility(View.VISIBLE); + } else { + holder.vButtonBar.setVisibility(View.GONE); + } + + holder.itemView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(holder.itemView, position); + } + } + }); + } + + public void swapCursor(Cursor data) { + swapCursor(new SimpleCursor(data)); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView vTrustId; + private final ImageView vAppIcon; + private final ImageView vActionIcon; + private final View vButtonBar; + + public ViewHolder(View view) { + super(view); + + vTrustId = (TextView) view.findViewById(R.id.trust_id_name); + vAppIcon = (ImageView) view.findViewById(R.id.trust_id_app_icon); + vActionIcon = (ImageView) view.findViewById(R.id.trust_id_action); + vButtonBar = view.findViewById(R.id.trust_id_button_bar); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index f6629bd1a..1e2407fd8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -24,6 +24,8 @@ import android.os.Bundle; import android.os.Handler; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; @@ -128,7 +130,7 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { mKeyserverStatusPresenter.startLoader(getLoaderManager()); mTrustIdsPresenter = new TrustIdsPresenter( - getContext(), mTrustIdsCard, LOADER_ID_TRUST_IDS, masterKeyId, false); + getContext(), mTrustIdsCard, this, LOADER_ID_TRUST_IDS, masterKeyId, false); mTrustIdsPresenter.startLoader(getLoaderManager()); } @@ -174,4 +176,28 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { } }); } + + @Override + public void addFakeBackStackItem(String tag, final OnBackStackPoppedListener listener) { + FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager.getBackStackEntryCount() > 0) { + return; + } + + fragmentManager.beginTransaction() + .addToBackStack("expand_trust_id") + .commitAllowingStateLoss(); + fragmentManager.executePendingTransactions(); + + fragmentManager.addOnBackStackChangedListener(new OnBackStackChangedListener() { + @Override + public void onBackStackChanged() { + FragmentManager fragMan = getFragmentManager(); + fragMan.popBackStack("expand_trust_id", FragmentManager.POP_BACK_STACK_INCLUSIVE); + fragMan.removeOnBackStackChangedListener(this); + + listener.onBackStackPopped(); + } + }); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java index b33497958..532cc4e7a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/TrustIdsPresenter.java @@ -24,15 +24,18 @@ import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; +import android.view.View; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.adapter.TrustIdsAdapter; -import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.keyview.presenter.ViewKeyMvpView.OnBackStackPoppedListener; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener.OnItemClickListener; public class TrustIdsPresenter implements LoaderCallbacks { private final Context context; private final TrustIdsMvpView view; + private final ViewKeyMvpView viewKeyMvpView; private final int loaderId; private final TrustIdsAdapter trustIdsAdapter; @@ -40,21 +43,23 @@ public class TrustIdsPresenter implements LoaderCallbacks { private final long masterKeyId; private final boolean isSecret; - public TrustIdsPresenter(Context context, TrustIdsMvpView view, int loaderId, long masterKeyId, boolean isSecret) { + public TrustIdsPresenter(Context context, TrustIdsMvpView view, ViewKeyMvpView viewKeyMvpView, int loaderId, + long masterKeyId, boolean isSecret) { this.context = context; this.view = view; + this.viewKeyMvpView = viewKeyMvpView; this.loaderId = loaderId; this.masterKeyId = masterKeyId; this.isSecret = isSecret; - trustIdsAdapter = new TrustIdsAdapter(context, null, 0); + trustIdsAdapter = new TrustIdsAdapter(context, null); view.setTrustIdAdapter(trustIdsAdapter); - view.setTrustIdClickListener(new TrustIdsClickListener() { + trustIdsAdapter.setOnItemClickListener(new OnItemClickListener() { @Override - public void onTrustIdItemClick(int position) { - + public void onItemClick(View view, int position) { + onClickTrustId(position); } }); } @@ -71,7 +76,7 @@ public class TrustIdsPresenter implements LoaderCallbacks { @Override public void onLoadFinished(Loader loader, Cursor data) { trustIdsAdapter.swapCursor(data); - view.showCard(trustIdsAdapter.getCount() > 0); + view.showCard(data.getCount() > 0); } @Override @@ -79,14 +84,19 @@ public class TrustIdsPresenter implements LoaderCallbacks { trustIdsAdapter.swapCursor(null); } + private void onClickTrustId(int position) { + trustIdsAdapter.setExpandedView(position); + + viewKeyMvpView.addFakeBackStackItem("expand_trust_id", new OnBackStackPoppedListener() { + @Override + public void onBackStackPopped() { + trustIdsAdapter.setExpandedView(null); + } + }); + } + public interface TrustIdsMvpView { void setTrustIdAdapter(TrustIdsAdapter trustIdsAdapter); void showCard(boolean show); - - void setTrustIdClickListener(TrustIdsClickListener trustIdsClickListener); - } - - public interface TrustIdsClickListener { - void onTrustIdItemClick(int position); } } \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java index 98399a52c..42a2b9dc6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java @@ -12,4 +12,10 @@ public interface ViewKeyMvpView { void startActivityAndShowResultSnackbar(Intent intent); void showDialogFragment(DialogFragment dialogFragment, final String tag); void setContentShown(boolean show, boolean animate); + + void addFakeBackStackItem(String tag, OnBackStackPoppedListener listener); + + interface OnBackStackPoppedListener { + void onBackStackPopped(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java index 93fa03750..42855e7b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/TrustIdsIdCardView.java @@ -20,45 +20,27 @@ package org.sufficientlysecure.keychain.ui.keyview.view; import android.content.Context; import android.support.v7.widget.CardView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.Button; -import android.widget.ListView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.TrustIdsAdapter; -import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesCardListener; -import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesMvpView; -import org.sufficientlysecure.keychain.ui.keyview.presenter.LinkedIdentitiesPresenter.LinkedIdsClickListener; -import org.sufficientlysecure.keychain.ui.keyview.presenter.LinkedIdentitiesPresenter.LinkedIdsMvpView; -import org.sufficientlysecure.keychain.ui.keyview.presenter.TrustIdsPresenter.TrustIdsClickListener; import org.sufficientlysecure.keychain.ui.keyview.presenter.TrustIdsPresenter.TrustIdsMvpView; public class TrustIdsIdCardView extends CardView implements TrustIdsMvpView { - private ListView vTrustIds; - - private TrustIdsClickListener trustIdsClickListener; + private RecyclerView vTrustIds; public TrustIdsIdCardView(Context context, AttributeSet attrs) { super(context, attrs); View view = LayoutInflater.from(context).inflate(R.layout.trust_ids_card, this, true); - vTrustIds = (ListView) view.findViewById(R.id.view_key_trust_ids); - vTrustIds.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (trustIdsClickListener != null) { - trustIdsClickListener.onTrustIdItemClick(position); - } - } - }); + vTrustIds = (RecyclerView) view.findViewById(R.id.view_key_trust_ids); + vTrustIds.setLayoutManager(new LinearLayoutManager(getContext())); } @Override @@ -70,9 +52,4 @@ public class TrustIdsIdCardView extends CardView implements TrustIdsMvpView { public void showCard(boolean show) { setVisibility(show ? View.VISIBLE : View.GONE); } - - @Override - public void setTrustIdClickListener(TrustIdsClickListener trustIdsClickListener) { - this.trustIdsClickListener = trustIdsClickListener; - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java index e9fb2f035..06fa523d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java @@ -27,6 +27,7 @@ import android.support.v7.widget.RecyclerView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter.SimpleCursor; import org.sufficientlysecure.keychain.util.Log; import java.lang.reflect.Constructor; @@ -35,7 +36,7 @@ import java.util.Arrays; import java.util.Date; import java.util.HashMap; -public abstract class CursorAdapter +public abstract class CursorAdapter extends RecyclerView.Adapter { public static final String TAG = "CursorAdapter"; @@ -58,7 +59,7 @@ public abstract class CursorAdapternot + * {@link #changeCursor(SimpleCursor)}, the returned old Cursor is not * closed. * * @param newCursor The new cursor to be used. @@ -312,10 +313,10 @@ public abstract class CursorAdapter T wrap(Cursor cursor, Class type) { + public static T wrap(Cursor cursor, Class type) { if (cursor != null) { try { Constructor constructor = type.getConstructor(Cursor.class); @@ -335,7 +336,7 @@ public abstract class CursorAdapter(cursor.getColumnCount() * 4 / 3, 0.75f); } @@ -376,12 +377,12 @@ public abstract class CursorAdapter arr = new ArrayList<>(); - arr.addAll(Arrays.asList(AbstractCursor.PROJECTION)); + arr.addAll(Arrays.asList(SimpleCursor.PROJECTION)); arr.addAll(Arrays.asList( KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java index b4292782d..ca9f5e9f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.util.adapter; import android.content.Context; -import android.database.Cursor; import android.support.v4.util.SparseArrayCompat; import android.support.v7.widget.RecyclerView; import android.view.View; @@ -27,18 +26,16 @@ import android.view.ViewGroup; import com.tonicartos.superslim.LayoutManager; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter.SimpleCursor; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.Objects; /** * @param section type. * @param the view holder extending {@code BaseViewHolder} that is bound to the cursor data. * @param the view holder extending {@code BaseViewHolder<>} that is bound to the section data. */ -public abstract class SectionCursorAdapter extends CursorAdapter { public static final String TAG = "SectionCursorAdapter"; diff --git a/OpenKeychain/src/main/res/layout/trust_ids_card.xml b/OpenKeychain/src/main/res/layout/trust_ids_card.xml index f99d73c10..34e43b5e5 100644 --- a/OpenKeychain/src/main/res/layout/trust_ids_card.xml +++ b/OpenKeychain/src/main/res/layout/trust_ids_card.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:text="Known to Apps as" /> - + android:orientation="vertical" + android:animateLayoutChanges="true"> - - - - - + + + + + + + + + + + style="?android:buttonBarStyle"> - +