aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OpenPGP-Keychain/src/main/AndroidManifest.xml9
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java24
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java6
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java31
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java32
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java62
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java119
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java8
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java7
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java205
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java299
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java1
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java19
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_cert_activity.xml189
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml53
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_certs_header.xml30
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_certs_item.xml46
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml12
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/view_cert.xml13
-rw-r--r--OpenPGP-Keychain/src/main/res/values/strings.xml16
20 files changed, 1077 insertions, 104 deletions
diff --git a/OpenPGP-Keychain/src/main/AndroidManifest.xml b/OpenPGP-Keychain/src/main/AndroidManifest.xml
index e588817e7..cf494ba08 100644
--- a/OpenPGP-Keychain/src/main/AndroidManifest.xml
+++ b/OpenPGP-Keychain/src/main/AndroidManifest.xml
@@ -95,6 +95,15 @@
android:value=".ui.KeyListActivity" />
</activity>
<activity
+ android:name=".ui.ViewCertActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="View Certificate Details"
+ android:parentActivityName=".ui.ViewKeyActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.ViewKeyActivity" />
+ </activity>
+ <activity
android:name=".ui.SelectPublicKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_recipients"
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
index 7c25c2c2a..c6c62d649 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
@@ -21,6 +21,8 @@ import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureList;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
@@ -123,6 +125,28 @@ public class PgpConversionHelper {
}
/**
+ * Convert from byte[] to PGPSignature
+ *
+ * @param sigBytes
+ * @return
+ */
+ public static PGPSignature BytesToPGPSignature(byte[] sigBytes) {
+ PGPObjectFactory factory = new PGPObjectFactory(sigBytes);
+ PGPSignatureList signatures = null;
+ try {
+ if ((signatures = (PGPSignatureList) factory.nextObject()) == null || signatures.isEmpty()) {
+ Log.e(Constants.TAG, "No signatures given!");
+ return null;
+ }
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Error while converting to PGPSignature!", e);
+ return null;
+ }
+
+ return signatures.get(0);
+ }
+
+ /**
* Convert from ArrayList<PGPSecretKey> to byte[]
*
* @param keys
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
index 9a97bc717..4c786f555 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
@@ -454,7 +454,6 @@ public class PgpKeyHelper {
algorithmStr = "RSA";
break;
}
-
case PGPPublicKey.DSA: {
algorithmStr = "DSA";
break;
@@ -471,7 +470,10 @@ public class PgpKeyHelper {
break;
}
}
- return algorithmStr + ", " + keySize + " bit";
+ if(keySize > 0)
+ return algorithmStr + ", " + keySize + " bit";
+ else
+ return algorithmStr;
}
/**
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
index 9eeb57222..2b40300d7 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -54,6 +54,16 @@ public class KeychainContract {
String IS_PRIMARY = "is_primary";
}
+ interface CertsColumns {
+ String MASTER_KEY_ID = "master_key_id"; // verified id, foreign key to key_rings._ID
+ String RANK = "rank"; // rank of verified key
+ String KEY_ID_CERTIFIER = "key_id_certifier"; // verifying id, not a database id
+ String CREATION = "creation";
+ String EXPIRY = "expiry";
+ String VERIFIED = "verified";
+ String KEY_DATA = "key_data"; // certification blob
+ }
+
interface ApiAppsColumns {
String PACKAGE_NAME = "package_name";
String PACKAGE_SIGNATURE = "package_signature";
@@ -91,6 +101,7 @@ public class KeychainContract {
public static final String PATH_SECRET = "secret";
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
+ public static final String PATH_CERTS = "certs";
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ACCOUNTS = "accounts";
@@ -145,6 +156,9 @@ public class KeychainContract {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build();
}
+ public static Uri buildSecretKeyRingUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
+ }
public static Uri buildSecretKeyRingUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
}
@@ -178,6 +192,7 @@ public class KeychainContract {
}
public static class UserIds implements UserIdsColumns, BaseColumns {
+ public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
@@ -243,6 +258,22 @@ public class KeychainContract {
}
}
+ public static class Certs implements CertsColumns, BaseColumns {
+ public static final String USER_ID = UserIdsColumns.USER_ID;
+ public static final String SIGNER_UID = "signer_user_id";
+
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ public static Uri buildCertsUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
+ }
+ public static Uri buildCertsUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build();
+ }
+
+ }
+
public static class DataStream {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_DATA).build();
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index 36e2a7962..fda1783cb 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -30,14 +30,13 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
-import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
-import java.util.Arrays;
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
@@ -49,6 +48,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys";
String USER_IDS = "user_ids";
+ String CERTS = "certs";
String API_APPS = "api_apps";
String API_ACCOUNTS = "api_accounts";
}
@@ -98,11 +98,31 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ UserIdsColumns.IS_PRIMARY + " BOOLEAN, "
+ UserIdsColumns.RANK+ " INTEGER, "
- + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "),"
+ + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), "
+ + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), "
+ "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
+ private static final String CREATE_CERTS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "("
+ + CertsColumns.MASTER_KEY_ID + " INTEGER,"
+ + CertsColumns.RANK + " INTEGER, " // rank of certified uid
+
+ + CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
+ + CertsColumns.CREATION + " INTEGER, "
+ + CertsColumns.EXPIRY + " INTEGER, "
+ + CertsColumns.VERIFIED + " INTEGER, "
+
+ + CertsColumns.KEY_DATA + " BLOB,"
+ + "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", "
+ + CertsColumns.KEY_ID_CERTIFIER + "), "
+ + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
+ + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
+ + Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE"
+ + ")";
+
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
@@ -145,6 +165,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYRINGS_SECRET);
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
}
@@ -155,6 +176,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
+ // TODO remove, once we remove the "always migrate" debug stuff
+ // db.execSQL("DROP TABLE certs;");
+ // db.execSQL("DROP TABLE user_ids;");
+ db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_CERTS);
}
}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index 1dd6ab08f..83b3dd744 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.Log;
@@ -45,12 +46,14 @@ public class KeychainProvider extends ContentProvider {
private static final int KEY_RINGS_UNIFIED = 101;
private static final int KEY_RINGS_PUBLIC = 102;
+ private static final int KEY_RINGS_SECRET = 103;
private static final int KEY_RING_UNIFIED = 200;
private static final int KEY_RING_KEYS = 201;
private static final int KEY_RING_USER_IDS = 202;
private static final int KEY_RING_PUBLIC = 203;
private static final int KEY_RING_SECRET = 204;
+ private static final int KEY_RING_CERTS = 205;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 303;
@@ -60,6 +63,8 @@ public class KeychainProvider extends ContentProvider {
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
+ private static final int CERTS_FIND_BY_CERTIFIER_ID = 501;
+
// private static final int DATA_STREAM = 501;
protected UriMatcher mUriMatcher;
@@ -87,6 +92,9 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ "/" + KeychainContract.PATH_PUBLIC,
KEY_RINGS_PUBLIC);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ + "/" + KeychainContract.PATH_SECRET,
+ KEY_RINGS_SECRET);
/**
* find by criteria other than master key id
@@ -128,6 +136,9 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_SECRET,
KEY_RING_SECRET);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_CERTS,
+ KEY_RING_CERTS);
/**
* API apps
@@ -362,6 +373,7 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(UserIds.USER_ID, UserIds.USER_ID);
projectionMap.put(UserIds.RANK, UserIds.RANK);
projectionMap.put(UserIds.IS_PRIMARY, UserIds.IS_PRIMARY);
+ projectionMap.put(UserIds.VERIFIED, "0 AS " + UserIds.VERIFIED);
qb.setProjectionMap(projectionMap);
qb.setTables(Tables.USER_IDS);
@@ -394,6 +406,7 @@ public class KeychainProvider extends ContentProvider {
break;
}
+ case KEY_RINGS_SECRET:
case KEY_RING_SECRET: {
HashMap<String, String> projectionMap = new HashMap<String, String>();
projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
@@ -402,12 +415,52 @@ public class KeychainProvider extends ContentProvider {
qb.setProjectionMap(projectionMap);
qb.setTables(Tables.KEY_RINGS_SECRET);
- qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
- qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+ if(match == KEY_RING_SECRET) {
+ qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ }
break;
}
+ case KEY_RING_CERTS:
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
+ projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
+ projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK);
+ projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION);
+ projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER);
+ projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
+ projectionMap.put(Certs.KEY_DATA, Tables.CERTS + "." + Certs.KEY_DATA);
+ // verified key data
+ projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
+ // verifying key data
+ projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.CERTS
+ + " JOIN " + Tables.USER_IDS + " ON ("
+ + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "
+ + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+ + " AND "
+ + Tables.CERTS + "." + Certs.RANK + " = "
+ + Tables.USER_IDS + "." + UserIds.RANK
+ + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON ("
+ + Tables.CERTS + "." + Keys.MASTER_KEY_ID + " = "
+ + "signer." + UserIds.MASTER_KEY_ID
+ + " AND "
+ + "signer." + Keys.RANK + " = 0"
+ + ")");
+
+ groupBy = Tables.CERTS + "." + Certs.RANK + ", "
+ + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER;
+
+ qb.appendWhere(Tables.CERTS + "." + KeyRings.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+ break;
+
case API_APPS:
qb.setTables(Tables.API_APPS);
@@ -499,6 +552,11 @@ public class KeychainProvider extends ContentProvider {
keyId = values.getAsLong(UserIds.MASTER_KEY_ID);
break;
+ case KEY_RING_CERTS:
+ db.insertOrThrow(Tables.CERTS, null, values);
+ keyId = values.getAsLong(Certs.MASTER_KEY_ID);
+ break;
+
case API_APPS:
db.insertOrThrow(Tables.API_APPS, null, values);
break;
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 581ddb378..503fed3c9 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -17,24 +17,24 @@
package org.sufficientlysecure.keychain.provider;
+import java.security.SignatureException;
+import org.spongycastle.bcpg.ArmoredOutputStream;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
-import android.database.CursorWindow;
-import android.database.CursorWrapper;
import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteCursor;
import android.net.Uri;
import android.os.RemoteException;
-import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
-import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.sufficientlysecure.keychain.Constants;
@@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -56,6 +57,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.Map;
import java.util.HashSet;
import java.util.Set;
@@ -104,9 +106,6 @@ public class ProviderHelper {
return getGenericData(context, KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), proj, types);
}
- /** Find the master key id related to a given query. The id will either be extracted from the
- * query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
- */
public static long getMasterKeyId(Context context, Uri queryUri) {
// try extracting from the uri first
String firstSegment = queryUri.getPathSegments().get(1);
@@ -123,25 +122,31 @@ public class ProviderHelper {
return 0L;
}
- public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
+ public static Map<Long, PGPKeyRing> getPGPKeyRings(Context context, Uri queryUri) {
Cursor cursor = context.getContentResolver().query(queryUri,
- new String[]{KeyRings._ID, KeyRingData.KEY_RING_DATA}, null, null, null);
-
- PGPKeyRing keyRing = null;
- if (cursor != null && cursor.moveToFirst()) {
- int keyRingDataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
+ new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA },
+ null, null, null);
- byte[] data = cursor.getBlob(keyRingDataCol);
+ Map<Long, PGPKeyRing> result = new HashMap<Long, PGPKeyRing>(cursor.getCount());
+ if (cursor != null && cursor.moveToFirst()) do {
+ long masterKeyId = cursor.getLong(0);
+ byte[] data = cursor.getBlob(1);
if (data != null) {
- keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
+ result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data));
}
- }
+ } while(cursor.moveToNext());
if (cursor != null) {
cursor.close();
}
- return keyRing;
+ return result;
+ }
+ public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
+ Map<Long, PGPKeyRing> result = getPGPKeyRings(context, queryUri);
+ if(result.isEmpty())
+ return null;
+ return result.values().iterator().next();
}
public static PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(Context context, long keyId) {
@@ -216,19 +221,49 @@ public class ProviderHelper {
++rank;
}
+ // get a list of owned secret keys, for verification filtering
+ Map<Long, PGPKeyRing> allKeyRings = getPGPKeyRings(context, KeyRingData.buildSecretKeyRingUri());
+
int userIdRank = 0;
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
operations.add(buildUserIdOperations(context, masterKeyId, userId, userIdRank));
- ++userIdRank;
- }
- for (PGPSignature certification :
- new IterableIterator<PGPSignature>(
- masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) {
- // TODO: how to do this??
- // we need to verify the signatures again and again when they are displayed...
-// if (certification.verify
-// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
+ // look through signatures for this specific key
+ for (PGPSignature cert : new IterableIterator<PGPSignature>(
+ masterKey.getSignaturesForID(userId))) {
+ long certId = cert.getKeyID();
+ boolean verified = false;
+ // do verify signatures from our own private keys
+ if(allKeyRings.containsKey(certId)) try {
+ // mark them as verified
+ cert.init(
+ new JcaPGPContentVerifierBuilderProvider().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME),
+ allKeyRings.get(certId).getPublicKey());
+ verified = cert.verifyCertification(userId, masterKey);
+ Log.d(Constants.TAG, "Verified sig for " + userId + " " + verified + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID())
+ );
+ } catch(SignatureException e) {
+ Log.e(Constants.TAG, "Signature verification failed! "
+ + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
+ } catch(PGPException e) {
+ Log.e(Constants.TAG, "Signature verification failed! "
+ + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
+ }
+ Log.d(Constants.TAG, "sig for " + userId + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID())
+ );
+ // regardless of verification, save the certification
+ operations.add(buildCertOperations(
+ context, masterKeyId, userIdRank, masterKey.getKeyID(), cert, verified));
+ }
+
+ ++userIdRank;
}
try {
@@ -311,11 +346,27 @@ public class ProviderHelper {
}
/**
- * Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing
+ * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/
- private static ContentProviderOperation buildSecretKeyOperations(Context context,
- long masterKeyId, PGPSecretKey key, int rank) throws IOException {
- return buildPublicKeyOperations(context, masterKeyId, key.getPublicKey(), rank);
+ private static ContentProviderOperation buildCertOperations(Context context,
+ long masterKeyId,
+ int rank,
+ long keyId,
+ PGPSignature cert,
+ boolean verified)
+ throws IOException {
+ ContentValues values = new ContentValues();
+ values.put(Certs.MASTER_KEY_ID, masterKeyId);
+ values.put(Certs.RANK, rank);
+ values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID());
+ values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
+ values.put(Certs.EXPIRY, (String) null); // TODO
+ values.put(Certs.VERIFIED, verified);
+ values.put(Certs.KEY_DATA, cert.getEncoded());
+
+ Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
/**
@@ -333,12 +384,6 @@ public class ProviderHelper {
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
- public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) {
- Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
- // see if we can get our master key id back from the uri
- return getMasterKeyId(context, queryUri) == masterKeyId;
- }
-
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, long[] masterKeyIds) {
ArrayList<String> output = new ArrayList<String>();
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
index fb8726ace..2efa8a69c 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
@@ -47,6 +47,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
@@ -158,7 +159,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
KeychainContract.KeyRings._ID,
KeychainContract.Keys.MASTER_KEY_ID,
KeychainContract.Keys.FINGERPRINT,
- KeychainContract.UserIds.USER_ID
+ KeychainContract.UserIds.USER_ID,
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_FINGERPRINT = 2;
@@ -168,10 +169,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
new String[]{
KeychainContract.UserIds._ID,
KeychainContract.UserIds.USER_ID,
- KeychainContract.UserIds.RANK
+ KeychainContract.UserIds.RANK,
+ "verified"
};
static final String USER_IDS_SORT_ORDER =
- KeychainContract.UserIds.RANK + " ASC";
+ KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC";
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index 1d8f7c8ad..45db30fe5 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -461,8 +461,8 @@ public class KeyListFragment extends Fragment
/**
* Bind cursor data to the item list view
* <p/>
- * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus
- * no ViewHolder is required here.
+ * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method.
+ * Thus no ViewHolder is required here.
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
@@ -587,8 +587,7 @@ public class KeyListFragment extends Fragment
String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
String headerText = convertView.getResources().getString(R.string.user_id_no_name);
if (userId != null && userId.length() > 0) {
- headerText = "" +
- mCursor.getString(KeyListFragment.INDEX_USER_ID).subSequence(0, 1).charAt(0);
+ headerText = "" + userId.subSequence(0, 1).charAt(0);
}
holder.mText.setText(headerText);
holder.mCount.setVisibility(View.GONE);
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
new file mode 100644
index 000000000..b273955dd
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2012-2014 Dominik SchĂĽrmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.text.format.DateFormat;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.TextView;
+
+import org.spongycastle.openpgp.PGPSignature;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Date;
+
+/**
+ * Swag
+ */
+public class ViewCertActivity extends ActionBarActivity
+ implements LoaderManager.LoaderCallbacks<Cursor> {
+
+ // These are the rows that we will retrieve.
+ static final String[] PROJECTION = new String[] {
+ Certs.MASTER_KEY_ID,
+ Certs.USER_ID,
+ Certs.CREATION,
+ Certs.KEY_ID_CERTIFIER,
+ Certs.SIGNER_UID,
+ Certs.KEY_DATA
+ };
+ private static final int INDEX_MASTER_KEY_ID = 1;
+ private static final int INDEX_USER_ID = 2;
+ private static final int INDEX_CREATION = 3;
+ private static final int INDEX_KEY_ID_CERTIFIER = 4;
+ private static final int INDEX_UID_CERTIFIER = 5;
+ private static final int INDEX_KEY_DATA = 6;
+
+ private Uri mDataUri;
+
+ private long mSignerKeyId;
+
+ private TextView mSigneeKey, mSigneeUid, mRank, mAlgorithm, mType, mCreation, mExpiry;
+ private TextView mSignerKey, mSignerUid;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.view_cert_activity);
+
+ mSigneeKey = (TextView) findViewById(R.id.signee_key);
+ mSigneeUid = (TextView) findViewById(R.id.signee_uid);
+ mAlgorithm = (TextView) findViewById(R.id.algorithm);
+ mType = (TextView) findViewById(R.id.signature_type);
+ mCreation = (TextView) findViewById(R.id.creation);
+ mExpiry = (TextView) findViewById(R.id.expiry);
+
+ mSignerKey = (TextView) findViewById(R.id.signer_key_id);
+ mSignerUid = (TextView) findViewById(R.id.signer_uid);
+
+ mDataUri = getIntent().getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ }
+
+ getSupportLoaderManager().initLoader(0, null, this);
+
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(this, mDataUri, PROJECTION, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if(data.moveToFirst()) {
+ String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID));
+ mSigneeKey.setText(signeeKey);
+
+ String signeeUid = data.getString(INDEX_USER_ID);
+ mSigneeUid.setText(signeeUid);
+
+ Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000);
+ mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate));
+
+ mSignerKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER);
+ String signerKey = "0x" + PgpKeyHelper.convertKeyIdToHex(mSignerKeyId);
+ mSignerKey.setText(signerKey);
+
+ String signerUid = data.getString(INDEX_UID_CERTIFIER);
+ if(signerUid != null)
+ mSignerUid.setText(signerUid);
+ else
+ mSignerUid.setText(R.string.unknown_uid);
+
+ byte[] sigData = data.getBlob(INDEX_KEY_DATA);
+ PGPSignature sig = PgpConversionHelper.BytesToPGPSignature(sigData);
+ if(sig != null) {
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(sig.getKeyAlgorithm(), 0);
+ mAlgorithm.setText(algorithmStr);
+
+ switch(sig.getSignatureType()) {
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ mType.setText(R.string.sig_type_default); break;
+ case PGPSignature.NO_CERTIFICATION:
+ mType.setText(R.string.sig_type_none); break;
+ case PGPSignature.CASUAL_CERTIFICATION:
+ mType.setText(R.string.sig_type_casual); break;
+ case PGPSignature.POSITIVE_CERTIFICATION:
+ mType.setText(R.string.sig_type_positive); break;
+ }
+
+ long expiry = sig.getHashedSubPackets().getSignatureExpirationTime();
+ if(expiry == 0)
+ mExpiry.setText("never");
+ else {
+ Date expiryDate = new Date(creationDate.getTime() + expiry * 1000);
+ mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format(expiryDate));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.view_cert, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_view_cert_verify:
+ // TODO verification routine
+ return true;
+ case R.id.menu_view_cert_view_signer:
+ // can't do this before the data is initialized
+ Intent viewIntent = null;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ viewIntent = new Intent(this, ViewKeyActivity.class);
+ } else {
+ viewIntent = new Intent(this, ViewKeyActivityJB.class);
+ }
+ //
+ long signerMasterKeyId = ProviderHelper.getMasterKeyId(this,
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId))
+ );
+ // TODO notify user of this, maybe offer download?
+ if(mSignerKeyId == 0L)
+ return true;
+ viewIntent.setData(KeyRings.buildGenericKeyRingUri(
+ Long.toString(signerMasterKeyId))
+ );
+ startActivity(viewIntent);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java
index 02c04c11d..26e72f10b 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java
@@ -17,74 +17,321 @@
package org.sufficientlysecure.keychain.ui;
+import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import com.beardedhen.androidbootstrap.BootstrapButton;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.util.Log;
+import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+
+
+public class ViewKeyCertsFragment extends Fragment
+ implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
+
+ // These are the rows that we will retrieve.
+ static final String[] PROJECTION = new String[] {
+ KeychainContract.Certs._ID,
+ KeychainContract.Certs.MASTER_KEY_ID,
+ KeychainContract.Certs.VERIFIED,
+ KeychainContract.Certs.RANK,
+ KeychainContract.Certs.KEY_ID_CERTIFIER,
+ KeychainContract.Certs.USER_ID,
+ KeychainContract.Certs.SIGNER_UID
+ };
+
+ // sort by our user id,
+ static final String SORT_ORDER =
+ KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC, "
+ + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.VERIFIED + " DESC, "
+ + KeychainContract.Certs.SIGNER_UID + " ASC";
-public class ViewKeyCertsFragment extends Fragment {
+ public static final String ARG_DATA_URI = "data_uri";
- public static final String ARG_DATA_URI = "uri";
+ private StickyListHeadersListView mStickyList;
+ private Spinner mSpinner;
- private BootstrapButton mActionCertify;
+ private CertListAdapter mAdapter;
+ private boolean mUnknownShown = false;
- private Uri mDataUri;
+ private Uri mBaseUri, mDataUri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false);
- mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify);
-
return view;
}
+ private void changeShowState(int type) {
+ switch(type) {
+ case 0:
+ mDataUri = mBaseUri.buildUpon().appendPath("has_secret").build();
+ break;
+ case 1:
+ mDataUri = mBaseUri;
+ break;
+ case 2:
+ mDataUri = mBaseUri.buildUpon().appendPath("all").build();
+ break;
+ }
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
- if (dataUri == null) {
+ mSpinner = (Spinner) getActivity().findViewById(R.id.spinner);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_spinner_item, new String[] {
+ getResources().getString(R.string.certs_list_known_secret),
+ getResources().getString(R.string.certs_list_known),
+ getResources().getString(R.string.certs_list_all)
+ } );
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSpinner.setAdapter(adapter);
+ mSpinner.setSelection(1);
+ mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
+ changeShowState(i);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ }
+ });
+
+ mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
+
+ if (!getArguments().containsKey(ARG_DATA_URI)) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
- loadData(dataUri);
+ Uri uri = getArguments().getParcelable(ARG_DATA_URI);
+ mBaseUri = KeychainContract.Certs.buildCertsUri(uri);
+
+ mStickyList.setAreHeadersSticky(true);
+ mStickyList.setDrawingListUnderStickyHeader(false);
+ mStickyList.setFastScrollEnabled(true);
+ mStickyList.setOnItemClickListener(this);
+
+ try {
+ mStickyList.setFastScrollAlwaysVisible(true);
+ } catch (ApiLevelTooLowException e) {
+ }
+
+ mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+
+ // TODO this view is made visible if no data is available
+ // mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new CertListAdapter(getActivity(), null);
+ mStickyList.setAdapter(mAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ mDataUri = mBaseUri;
+ getLoaderManager().initLoader(0, null, this);
+
}
- private void loadData(Uri dataUri) {
- if (dataUri.equals(mDataUri)) {
- Log.d(Constants.TAG, "Same URI, no need to load the data again!");
- return;
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, SORT_ORDER);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+
+ mStickyList.setAdapter(mAdapter);
+ }
+
+ /**
+ * On click on item, start key view activity
+ */
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ Intent viewIntent = null;
+ viewIntent = new Intent(getActivity(), ViewCertActivity.class);
+ viewIntent.setData(KeychainContract.Certs.buildCertsUri(Long.toString(id)));
+ startActivity(viewIntent);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ /**
+ * Implements StickyListHeadersAdapter from library
+ */
+ private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter {
+ private LayoutInflater mInflater;
+ private int mIndexCertId;
+ private int mIndexUserId, mIndexRank;
+ private int mIndexSignerKeyId, mIndexSignerUserId;
+ private int mIndexVerified;
+
+ public CertListAdapter(Context context, Cursor c) {
+ super(context, c, 0);
+
+ mInflater = LayoutInflater.from(context);
+ initIndex(c);
}
- mDataUri = dataUri;
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ initIndex(newCursor);
- Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
+ return super.swapCursor(newCursor);
+ }
- mActionCertify.setOnClickListener(new View.OnClickListener() {
+ /**
+ * Get column indexes for performance reasons just once in constructor and swapCursor. For a
+ * performance comparison see http://stackoverflow.com/a/17999582
+ *
+ * @param cursor
+ */
+ private void initIndex(Cursor cursor) {
+ if (cursor != null) {
- @Override
- public void onClick(View v) {
- certifyKey(mDataUri);
+ mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID);
+ mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID);
+ mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK);
+ mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED);
+ mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER);
+ mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID);
}
- });
+ }
- }
+ /**
+ * Bind cursor data to the item list view
+ * <p/>
+ * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method.
+ * Thus no ViewHolder is required here.
+ */
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ // set name and stuff, common to both key types
+ TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId);
+ TextView wSignerUserId = (TextView) view.findViewById(R.id.signerUserId);
+ TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus);
+
+ String signerKeyId = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexSignerKeyId));
+ String signerUserId = cursor.getString(mIndexSignerUserId);
+ String signStatus = cursor.getInt(mIndexVerified) > 0 ? "ok" : "unknown";
+
+ wSignerUserId.setText(signerUserId);
+ wSignerKeyId.setText(signerKeyId);
+ wSignStatus.setText(signStatus);
+
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.view_key_certs_item, parent, false);
+ }
+
+ /**
+ * Creates a new header view and binds the section headers to it. It uses the ViewHolder
+ * pattern. Most functionality is similar to getView() from Android's CursorAdapter.
+ * <p/>
+ * NOTE: The variables mDataValid and mCursor are available due to the super class
+ * CursorAdapter.
+ */
+ @Override
+ public View getHeaderView(int position, View convertView, ViewGroup parent) {
+ HeaderViewHolder holder;
+ if (convertView == null) {
+ holder = new HeaderViewHolder();
+ convertView = mInflater.inflate(R.layout.view_key_certs_header, parent, false);
+ holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text);
+ holder.count = (TextView) convertView.findViewById(R.id.certs_num);
+ convertView.setTag(holder);
+ } else {
+ holder = (HeaderViewHolder) convertView.getTag();
+ }
+
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return convertView;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ // set header text as first char in user id
+ String userId = mCursor.getString(mIndexUserId);
+ holder.text.setText(userId);
+ holder.count.setVisibility(View.GONE);
+ return convertView;
+ }
+
+ /**
+ * Header IDs should be static, position=1 should always return the same Id that is.
+ */
+ @Override
+ public long getHeaderId(int position) {
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return -1;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ // otherwise, return the first character of the name as ID
+ return mCursor.getInt(mIndexRank);
+
+ // sort by the first four characters (should be enough I guess?)
+ // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0);
+ }
+
+ class HeaderViewHolder {
+ TextView text;
+ TextView count;
+ }
- private void certifyKey(Uri dataUri) {
- Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class);
- signIntent.setData(dataUri);
- startActivity(signIntent);
}
}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
index 830c5fcae..b5a800712 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
@@ -39,6 +39,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
index fcaa6a940..2677a1a1a 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
@@ -25,6 +25,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
+import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
@@ -36,6 +37,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private int mIndexUserId, mIndexRank;
+ private int mVerifiedId;
private final ArrayList<Boolean> mCheckStates;
@@ -61,8 +63,10 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
int count = newCursor.getCount();
mCheckStates.ensureCapacity(count);
// initialize to true (use case knowledge: we usually want to sign all uids)
- for (int i = 0; i < count; i++) {
- mCheckStates.add(true);
+ for(int i = 0; i < count; i++) {
+ newCursor.moveToPosition(i);
+ int verified = newCursor.getInt(mVerifiedId);
+ mCheckStates.add(verified == 0);
}
}
}
@@ -80,6 +84,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
if (cursor != null) {
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
+ // mVerifiedId = cursor.getColumnIndexOrThrow(UserIds.VERIFIED);
}
}
@@ -89,6 +94,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
TextView vRank = (TextView) view.findViewById(R.id.rank);
TextView vUserId = (TextView) view.findViewById(R.id.userId);
TextView vAddress = (TextView) view.findViewById(R.id.address);
+ ImageView vVerified = (ImageView) view.findViewById(R.id.certified);
vRank.setText(Integer.toString(cursor.getInt(mIndexRank)));
@@ -100,6 +106,13 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
}
vAddress.setText(userId[1]);
+ int verified = 1; // cursor.getInt(mVerifiedId);
+ // TODO introduce own resource for this :)
+ if(verified > 0)
+ vVerified.setImageResource(android.R.drawable.presence_online);
+ else
+ vVerified.setImageResource(android.R.drawable.presence_invisible);
+
// don't care further if checkboxes aren't shown
if (mCheckStates == null) {
return;
@@ -107,7 +120,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
final int position = cursor.getPosition();
- vCheckBox.setClickable(false);
+ vCheckBox.setOnCheckedChangeListener(null);
vCheckBox.setChecked(mCheckStates.get(position));
vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_cert_activity.xml b/OpenPGP-Keychain/src/main/res/layout/view_cert_activity.xml
new file mode 100644
index 000000000..3c6c09429
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_cert_activity.xml
@@ -0,0 +1,189 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="beforeDescendants"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp">
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_cert" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/signee_key"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_user_id" />
+
+ <TextView
+ android:id="@+id/signee_uid"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_algorithm" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Type" />
+
+ <TextView
+ android:id="@+id/signature_type"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Creation" />
+
+ <TextView
+ android:id="@+id/creation"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Expires" />
+
+ <TextView
+ android:id="@+id/expiry"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ </TableLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_signer_id" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/signer_key_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text=""
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_email" />
+
+ <TextView
+ android:id="@+id/signer_uid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+
+ </TableLayout>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml
index 9c0ecb15d..032b9eee6 100644
--- a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml
@@ -1,38 +1,45 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fillViewport="true">
- <LinearLayout
+ <RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingLeft="16dp"
- android:paddingRight="16dp">
+ android:orientation="vertical">
- <TextView
+ <Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="4dp"
- android:layout_marginTop="14dp"
- android:text="Display of existing certifications is a planned feature for a later release of OpenPGP Keychain. Stay tuned for updates!" />
+ android:id="@+id/spinner"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp" />
+
+ <view
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ class="se.emilsjolander.stickylistheaders.StickyListHeadersListView"
+ android:id="@+id/list"
+ android:paddingRight="32dp"
+ android:paddingLeft="16dp"
+ android:layout_alignParentStart="false"
+ android:layout_alignParentEnd="false"
+ android:layout_below="@+id/spinner" />
<TextView
- style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="14dp"
- android:text="@string/section_actions" />
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@string/empty_certs"
+ android:id="@+id/empty"
+ android:visibility="gone"
+ android:layout_centerInParent="true"
+ android:paddingBottom="32dp" />
- <com.beardedhen.androidbootstrap.BootstrapButton
- android:id="@+id/action_certify"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:layout_marginTop="4dp"
- android:layout_marginBottom="4dp"
- android:text="@string/key_view_action_certify"
- bootstrapbutton:bb_icon_left="fa-pencil"
- bootstrapbutton:bb_type="info" />
- </LinearLayout>
+ </RelativeLayout>
-</ScrollView> \ No newline at end of file
+</ScrollView>
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_header.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_header.xml
new file mode 100644
index 000000000..de481c1cc
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_header.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView
+ android:id="@+id/stickylist_header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|left"
+ android:padding="8dp"
+ android:textColor="@color/emphasis"
+ android:textSize="17sp"
+ android:textStyle="bold"
+ android:text="header text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="certification count"
+ android:id="@+id/certs_num"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginRight="10px"
+ android:visibility="visible"
+ android:textColor="@android:color/darker_gray" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_item.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_item.xml
new file mode 100644
index 000000000..de7570818
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingLeft="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:singleLine="true"
+ android:descendantFocusability="blocksDescendants"
+ android:focusable="false">
+
+ <TextView
+ android:id="@+id/signerKeyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="signer key id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <TextView
+ android:id="@+id/signerUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="&lt;user@example.com>"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_below="@+id/signerKeyId"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <TextView
+ android:id="@+id/signStatus"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="status"
+ android:visibility="visible"
+ android:layout_above="@+id/signerUserId"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginRight="10dp" />
+
+</RelativeLayout>
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml
index 2e8cfeb04..aa14e6d2f 100644
--- a/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml
@@ -24,7 +24,8 @@
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
<TextView
android:id="@+id/userId"
@@ -40,6 +41,15 @@
android:text="@string/label_email"
android:textAppearance="?android:attr/textAppearanceSmall"
android:paddingLeft="10dp" />
+
</LinearLayout>
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:id="@+id/certified"
+ android:src="@android:drawable/presence_invisible"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp" />
+
</LinearLayout>
diff --git a/OpenPGP-Keychain/src/main/res/menu/view_cert.xml b/OpenPGP-Keychain/src/main/res/menu/view_cert.xml
new file mode 100644
index 000000000..dbdb2bfbe
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/view_cert.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_view_cert_verify"
+ app:showAsAction="never"
+ android:title="Verify signature" />
+ <item
+ android:id="@+id/menu_view_cert_view_signer"
+ app:showAsAction="never"
+ android:title="View signing key" />
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml
index cd280d7b7..55971003b 100644
--- a/OpenPGP-Keychain/src/main/res/values/strings.xml
+++ b/OpenPGP-Keychain/src/main/res/values/strings.xml
@@ -485,8 +485,24 @@
<string name="label_secret_key">Secret Key</string>
<string name="secret_key_yes">available</string>
<string name="secret_key_no">unavailable</string>
+
+ <!-- unsorted -->
+ <string name="show_unknown_signatures">Show unknown signatures</string>
+ <string name="section_signer_id">Signer</string>
+ <string name="section_cert">Certificate Details</string>
+ <string name="sig_type_default">default</string>
+ <string name="sig_type_none">none</string>
+ <string name="sig_type_casual">casual</string>
+ <string name="sig_type_positive">positive</string>
+ <string name="label_user_id">User ID</string>
+ <string name="label_subkey_rank">Subkey Rank</string>
+ <string name="unknown_uid"><![CDATA[<unknown>]]></string>
+ <string name="empty_certs">No certificates for this key</string>
<string name="section_uids_to_sign">User IDs to sign</string>
<string name="progress_re_adding_certs">Reapplying certificates</string>
+ <string name="certs_list_known_secret">Show by known secret keys</string>
+ <string name="certs_list_known">Show by known public keys</string>
+ <string name="certs_list_all">Show all certificates</string>
<!-- hints -->
<string name="encrypt_content_edit_text_hint">Write message here to encrypt and/or sign…</string>