work more on separation of linked identities and resources, initial ui work
This commit is contained in:
@@ -14,20 +14,21 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class LinkedResource {
|
||||
public abstract class LinkedCookieResource {
|
||||
|
||||
protected final URI mSubUri;
|
||||
protected final Set<String> mFlags;
|
||||
protected final HashMap<String,String> mParams;
|
||||
|
||||
static Pattern magicPattern =
|
||||
Pattern.compile("\\[Verifying my PGP key: pgpid\\+cookie:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]");
|
||||
Pattern.compile("\\[Verifying my PGP key: openpgp4fpr:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]");
|
||||
|
||||
protected LinkedResource(Set<String> flags, HashMap<String, String> params, URI uri) {
|
||||
protected LinkedCookieResource(Set<String> flags, HashMap<String, String> params, URI uri) {
|
||||
mFlags = flags;
|
||||
mParams = params;
|
||||
mSubUri = uri;
|
||||
@@ -41,22 +42,58 @@ public abstract class LinkedResource {
|
||||
return new HashMap<String,String>(mParams);
|
||||
}
|
||||
|
||||
public URI toUri () {
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("pgpid+cookie:");
|
||||
|
||||
// add flags
|
||||
if (mFlags != null) {
|
||||
boolean first = true;
|
||||
for (String flag : mFlags) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
b.append(flag);
|
||||
}
|
||||
}
|
||||
|
||||
// add parameters
|
||||
if (mParams != null) {
|
||||
boolean first = true;
|
||||
for (Entry<String, String> stringStringEntry : mParams.entrySet()) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
b.append(stringStringEntry.getKey()).append("=").append(stringStringEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
b.append("@");
|
||||
b.append(mSubUri);
|
||||
|
||||
return URI.create(b.toString());
|
||||
|
||||
}
|
||||
|
||||
public URI getSubUri () {
|
||||
return mSubUri;
|
||||
}
|
||||
|
||||
public static String generate (Context context, byte[] fingerprint, String nonce) {
|
||||
|
||||
return "[Verifying my PGP key: pgpid+cookie:"
|
||||
return "[Verifying my PGP key: openpgp4fpr:"
|
||||
+ KeyFormattingUtils.convertFingerprintToHex(fingerprint) + "#" + nonce + "]";
|
||||
|
||||
}
|
||||
|
||||
public static String generatePreview () {
|
||||
return "[Verifying my PGP key: pgpid+cookie:0x…]";
|
||||
return "[Verifying my PGP key: openpgp4fpr:0x…]";
|
||||
}
|
||||
|
||||
public LinkedVerifyResult verify(byte[] fingerprint, String nonce) {
|
||||
public LinkedVerifyResult verify(byte[] fingerprint, int nonce) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_LV, 0);
|
||||
@@ -82,7 +119,7 @@ public abstract class LinkedResource {
|
||||
|
||||
protected LinkedVerifyResult verifyString (OperationLog log, int indent,
|
||||
String res,
|
||||
String nonce, byte[] fingerprint) {
|
||||
int nonce, byte[] fingerprint) {
|
||||
|
||||
log.add(LogType.MSG_LV_MATCH, indent);
|
||||
Matcher match = matchResource(log, indent+1, res);
|
||||
@@ -92,7 +129,7 @@ public abstract class LinkedResource {
|
||||
}
|
||||
|
||||
String candidateFp = match.group(1).toLowerCase();
|
||||
String nonceCandidate = match.group(2).toLowerCase();
|
||||
int nonceCandidate = Integer.parseInt(match.group(2).toLowerCase(), 16);
|
||||
|
||||
String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint);
|
||||
|
||||
@@ -102,7 +139,7 @@ public abstract class LinkedResource {
|
||||
}
|
||||
log.add(LogType.MSG_LV_FP_OK, indent);
|
||||
|
||||
if (!nonce.equals(nonceCandidate)) {
|
||||
if (nonce != nonceCandidate) {
|
||||
log.add(LogType.MSG_LV_NONCE_ERROR, indent);
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
@@ -112,17 +149,61 @@ public abstract class LinkedResource {
|
||||
|
||||
}
|
||||
|
||||
public static LinkedResource findResourceType
|
||||
(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
protected static LinkedCookieResource fromRawLinkedId (RawLinkedIdentity id) {
|
||||
return fromUri(id.mNonce, id.mUri);
|
||||
}
|
||||
|
||||
LinkedResource res;
|
||||
protected static LinkedCookieResource fromUri (int nonce, URI uri) {
|
||||
|
||||
res = GenericHttpsResource.create(flags, params, uri);
|
||||
if ("pgpid".equals(uri.getScheme())) {
|
||||
Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uri.isOpaque()) {
|
||||
Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String specific = uri.getSchemeSpecificPart();
|
||||
if (!specific.contains("@")) {
|
||||
Log.e(Constants.TAG, "unknown uri scheme in linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] pieces = specific.split("@", 2);
|
||||
URI subUri = URI.create(pieces[1]);
|
||||
|
||||
Set<String> flags = new HashSet<String>();
|
||||
HashMap<String,String> params = new HashMap<String,String>();
|
||||
{
|
||||
String[] rawParams = pieces[0].split(";");
|
||||
for (String param : rawParams) {
|
||||
String[] p = param.split("=", 2);
|
||||
if (p.length == 1) {
|
||||
flags.add(param);
|
||||
} else {
|
||||
params.put(p[0], p[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findResourceType(nonce, flags, params, subUri);
|
||||
|
||||
}
|
||||
|
||||
protected static LinkedCookieResource findResourceType (int nonce, Set<String> flags,
|
||||
HashMap<String,String> params,
|
||||
URI subUri) {
|
||||
|
||||
LinkedCookieResource res;
|
||||
|
||||
res = GenericHttpsResource.create(flags, params, subUri);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return new UnknownResource(flags, params, uri);
|
||||
return new UnknownResource(flags, params, subUri);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.pgp.linked;
|
||||
|
||||
import org.spongycastle.bcpg.UserAttributeSubpacket;
|
||||
import org.spongycastle.util.Strings;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
public class LinkedIdentity {
|
||||
|
||||
protected byte[] mData;
|
||||
public final String mNonce;
|
||||
public final URI mSubUri;
|
||||
final Set<String> mFlags;
|
||||
final HashMap<String,String> mParams;
|
||||
|
||||
protected LinkedIdentity(byte[] data, String nonce, Set<String> flags,
|
||||
HashMap<String, String> params, URI subUri) {
|
||||
if ( ! nonce.matches("[0-9a-zA-Z]+")) {
|
||||
throw new AssertionError("bug: nonce must be hexstring!");
|
||||
}
|
||||
|
||||
mData = data;
|
||||
mNonce = nonce;
|
||||
mFlags = flags;
|
||||
mParams = params;
|
||||
mSubUri = subUri;
|
||||
}
|
||||
|
||||
LinkedIdentity(String nonce, Set<String> flags,
|
||||
HashMap<String, String> params, URI subUri) {
|
||||
this(null, nonce, flags, params, subUri);
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
if (mData != null) {
|
||||
return mData;
|
||||
}
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("pgpid:");
|
||||
|
||||
// add flags
|
||||
if (mFlags != null) {
|
||||
boolean first = true;
|
||||
for (String flag : mFlags) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
b.append(flag);
|
||||
}
|
||||
}
|
||||
|
||||
// add parameters
|
||||
if (mParams != null) {
|
||||
boolean first = true;
|
||||
Iterator<Entry<String, String>> it = mParams.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
Entry<String, String> entry = it.next();
|
||||
b.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
b.append("@");
|
||||
b.append(mSubUri);
|
||||
|
||||
byte[] nonceBytes = Hex.decode(mNonce);
|
||||
if (nonceBytes.length != 4) {
|
||||
throw new AssertionError("nonce must be 4 bytes");
|
||||
}
|
||||
byte[] data = Strings.toUTF8ByteArray(b.toString());
|
||||
|
||||
byte[] result = new byte[data.length+4];
|
||||
System.arraycopy(nonceBytes, 0, result, 0, 4);
|
||||
System.arraycopy(data, 0, result, 4, data.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** This method parses a linked id from a UserAttributeSubpacket, or returns null if the
|
||||
* subpacket can not be parsed as a valid linked id.
|
||||
*/
|
||||
static LinkedIdentity parseAttributeSubpacket(UserAttributeSubpacket subpacket) {
|
||||
if (subpacket.getType() != 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = subpacket.getData();
|
||||
String nonce = Hex.toHexString(data, 0, 4);
|
||||
|
||||
try {
|
||||
return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 4, data.length)));
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static LinkedIdentity parseUri (String nonce, String uriString) {
|
||||
URI uri = URI.create(uriString);
|
||||
|
||||
if ("pgpid".equals(uri.getScheme())) {
|
||||
Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uri.isOpaque()) {
|
||||
Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String specific = uri.getSchemeSpecificPart();
|
||||
if (!specific.contains("@")) {
|
||||
Log.e(Constants.TAG, "unknown uri scheme in linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] pieces = specific.split("@", 2);
|
||||
URI subUri = URI.create(pieces[1]);
|
||||
|
||||
Set<String> flags = new HashSet<String>();
|
||||
HashMap<String,String> params = new HashMap<String,String>();
|
||||
{
|
||||
String[] rawParams = pieces[0].split(";");
|
||||
for (String param : rawParams) {
|
||||
String[] p = param.split("=", 2);
|
||||
if (p.length == 1) {
|
||||
flags.add(param);
|
||||
} else {
|
||||
params.put(p[0], p[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LinkedIdentity(nonce, flags, params, subUri);
|
||||
|
||||
}
|
||||
|
||||
public static LinkedIdentity fromResource (LinkedResource res, String nonce) {
|
||||
return new LinkedIdentity(nonce, res.getFlags(), res.getParams(), res.getSubUri());
|
||||
}
|
||||
|
||||
public WrappedUserAttribute toUserAttribute () {
|
||||
return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded());
|
||||
}
|
||||
|
||||
public static String generateNonce() {
|
||||
// TODO make this actually random
|
||||
// byte[] data = new byte[4];
|
||||
// new SecureRandom().nextBytes(data);
|
||||
// return Hex.toHexString(data);
|
||||
|
||||
// debug for now
|
||||
return "01234567";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.sufficientlysecure.keychain.pgp.linked;
|
||||
|
||||
import org.spongycastle.bcpg.UserAttributeSubpacket;
|
||||
import org.spongycastle.util.Strings;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/** The RawLinkedIdentity contains raw parsed data from a Linked Identity subpacket. */
|
||||
public class RawLinkedIdentity {
|
||||
|
||||
public final int mNonce;
|
||||
public final URI mUri;
|
||||
|
||||
protected RawLinkedIdentity(int nonce, URI uri) {
|
||||
mNonce = nonce;
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
byte[] uriData = Strings.toUTF8ByteArray(mUri.toASCIIString());
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4 + uriData.length);
|
||||
|
||||
buf.putInt(mNonce);
|
||||
buf.put(uriData);
|
||||
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
/** This method parses a linked id from a UserAttributeSubpacket, or returns null if the
|
||||
* subpacket can not be parsed as a valid linked id.
|
||||
*/
|
||||
static RawLinkedIdentity fromAttributeSubpacket(UserAttributeSubpacket subpacket) {
|
||||
if (subpacket.getType() != 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = subpacket.getData();
|
||||
|
||||
return fromSubpacketData(data);
|
||||
|
||||
}
|
||||
|
||||
public static RawLinkedIdentity fromSubpacketData(byte[] data) {
|
||||
|
||||
try {
|
||||
int nonce = ByteBuffer.wrap(data).getInt();
|
||||
String uri = Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 4, data.length));
|
||||
|
||||
return new RawLinkedIdentity(nonce, URI.create(uri));
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static RawLinkedIdentity fromResource (LinkedCookieResource res, int nonce) {
|
||||
return new RawLinkedIdentity(nonce, res.toUri());
|
||||
}
|
||||
|
||||
public WrappedUserAttribute toUserAttribute () {
|
||||
return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded());
|
||||
}
|
||||
|
||||
public static String generateNonce() {
|
||||
// TODO make this actually random
|
||||
// byte[] data = new byte[4];
|
||||
// new SecureRandom().nextBytes(data);
|
||||
// return Hex.toHexString(data);
|
||||
|
||||
// debug for now
|
||||
return "01234567";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.pgp.linked.resources;
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -21,7 +21,7 @@ import de.measite.minidns.Record.CLASS;
|
||||
import de.measite.minidns.Record.TYPE;
|
||||
import de.measite.minidns.record.TXT;
|
||||
|
||||
public class DnsResource extends LinkedResource {
|
||||
public class DnsResource extends LinkedCookieResource {
|
||||
|
||||
final static Pattern magicPattern =
|
||||
Pattern.compile("pgpid\\+cookie=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)");
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -22,14 +22,14 @@ import java.util.Set;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public class GenericHttpsResource extends LinkedResource {
|
||||
public class GenericHttpsResource extends LinkedCookieResource {
|
||||
|
||||
GenericHttpsResource(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
super(flags, params, uri);
|
||||
}
|
||||
|
||||
public static String generateText (Context context, byte[] fingerprint, String nonce) {
|
||||
String cookie = LinkedResource.generate(context, fingerprint, nonce);
|
||||
String cookie = LinkedCookieResource.generate(context, fingerprint, nonce);
|
||||
|
||||
return String.format(context.getResources().getString(R.string.linked_id_generic_text),
|
||||
cookie, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24));
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.apache.http.params.BasicHttpParams;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -29,7 +29,7 @@ import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public class TwitterResource extends LinkedResource {
|
||||
public class TwitterResource extends LinkedCookieResource {
|
||||
|
||||
TwitterResource(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
super(flags, params, uri);
|
||||
@@ -37,7 +37,7 @@ public class TwitterResource extends LinkedResource {
|
||||
|
||||
public static String generateText (Context context, byte[] fingerprint, String nonce) {
|
||||
// nothing special here for now, might change this later
|
||||
return LinkedResource.generate(context, fingerprint, nonce);
|
||||
return LinkedCookieResource.generate(context, fingerprint, nonce);
|
||||
}
|
||||
|
||||
private String getTwitterStream(String screenName) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.sufficientlysecure.keychain.pgp.linked.resources;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public class UnknownResource extends LinkedResource {
|
||||
public class UnknownResource extends LinkedCookieResource {
|
||||
|
||||
public UnknownResource(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
super(flags, params, uri);
|
||||
|
||||
@@ -106,6 +106,7 @@ public class KeychainContract {
|
||||
public static final String PATH_PUBLIC = "public";
|
||||
public static final String PATH_SECRET = "secret";
|
||||
public static final String PATH_USER_IDS = "user_ids";
|
||||
public static final String PATH_LINKED_IDS = "linked_ids";
|
||||
public static final String PATH_KEYS = "keys";
|
||||
public static final String PATH_CERTS = "certs";
|
||||
|
||||
@@ -261,6 +262,10 @@ public class KeychainContract {
|
||||
public static Uri buildUserIdsUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildLinkedIdsUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiApps implements ApiAppsColumns, BaseColumns {
|
||||
|
||||
@@ -328,7 +328,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
case LOADER_ID_SUBKEYS: {
|
||||
|
||||
@@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.widget.CardView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -49,10 +51,14 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_LINKED_IDS = 2;
|
||||
|
||||
private UserIdsAdapter mUserIdsAdapter;
|
||||
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||
|
||||
private Uri mDataUri;
|
||||
private ListView mLinkedIds;
|
||||
private CardView mLinkedIdsCard;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@@ -73,6 +79,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
|
||||
|
||||
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
||||
mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids);
|
||||
|
||||
mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids);
|
||||
mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
|
||||
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
@@ -80,6 +91,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
showUserIdInfo(position);
|
||||
}
|
||||
});
|
||||
mLinkedIdsCard.setVisibility(View.GONE);
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -145,23 +157,23 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
case LOADER_ID_USER_IDS:
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
|
||||
case LOADER_ID_LINKED_IDS:
|
||||
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
/* TODO better error handling? May cause problems when a key is deleted,
|
||||
* because the notification triggers faster than the activity closes.
|
||||
*/
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
@@ -180,6 +192,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
@@ -194,6 +211,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsCard.setVisibility(View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
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.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
public class LinkedIdsAdapter extends UserAttributesAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
|
||||
public LinkedIdsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
RawLinkedIdentity id = (RawLinkedIdentity) getItem(position);
|
||||
|
||||
// TODO return different ids by type
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
Cursor c = getCursor();
|
||||
c.moveToPosition(position);
|
||||
|
||||
byte[] data = c.getBlob(INDEX_ATTRIBUTE_DATA);
|
||||
RawLinkedIdentity identity = RawLinkedIdentity.fromSubpacketData(data);
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView vName = (TextView) view.findViewById(R.id.user_id_item_name);
|
||||
TextView vAddress = (TextView) view.findViewById(R.id.user_id_item_address);
|
||||
TextView vComment = (TextView) view.findViewById(R.id.user_id_item_comment);
|
||||
ImageView vVerified = (ImageView) view.findViewById(R.id.user_id_item_certified);
|
||||
View vVerifiedLayout = view.findViewById(R.id.user_id_item_certified_layout);
|
||||
ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_item_edit_image);
|
||||
ImageView vDeleteButton = (ImageView) view.findViewById(R.id.user_id_item_delete_button);
|
||||
vDeleteButton.setVisibility(View.GONE); // not used
|
||||
|
||||
String userId = cursor.getString(INDEX_USER_ID);
|
||||
String[] splitUserId = KeyRing.splitUserId(userId);
|
||||
if (splitUserId[0] != null) {
|
||||
vName.setText(splitUserId[0]);
|
||||
} else {
|
||||
vName.setText(R.string.user_id_no_name);
|
||||
}
|
||||
if (splitUserId[1] != null) {
|
||||
vAddress.setText(splitUserId[1]);
|
||||
vAddress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vAddress.setVisibility(View.GONE);
|
||||
}
|
||||
if (splitUserId[2] != null) {
|
||||
vComment.setText(splitUserId[2]);
|
||||
vComment.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vComment.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0;
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
vVerifiedLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
if (isRevoked) {
|
||||
// set revocation icon (can this even be primary?)
|
||||
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
|
||||
|
||||
// disable revoked user ids
|
||||
vName.setEnabled(false);
|
||||
vAddress.setEnabled(false);
|
||||
vComment.setEnabled(false);
|
||||
} else {
|
||||
vName.setEnabled(true);
|
||||
vAddress.setEnabled(true);
|
||||
vComment.setEnabled(true);
|
||||
|
||||
if (isPrimary) {
|
||||
vName.setTypeface(null, Typeface.BOLD);
|
||||
vAddress.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
vName.setTypeface(null, Typeface.NORMAL);
|
||||
vAddress.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
|
||||
int isVerified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (isVerified) {
|
||||
case Certs.VERIFIED_SECRET:
|
||||
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
case Certs.VERIFIED_SELF:
|
||||
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
default:
|
||||
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.view_key_adv_user_id_item, null);
|
||||
}
|
||||
|
||||
// don't show revoked user ids, irrelevant for average users
|
||||
public static final String LINKED_IDS_WHERE = UserPackets.IS_REVOKED + " = 0";
|
||||
|
||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||
Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri);
|
||||
return new CursorLoader(activity, baseUri,
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, LINKED_IDS_WHERE, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,11 @@ import android.view.View;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
|
||||
public abstract class UserAttributesAdapter extends CursorAdapter {
|
||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
||||
public static final String[] USER_PACKETS_PROJECTION = new String[]{
|
||||
UserPackets._ID,
|
||||
UserPackets.TYPE,
|
||||
UserPackets.USER_ID,
|
||||
UserPackets.ATTRIBUTE_DATA,
|
||||
UserPackets.RANK,
|
||||
UserPackets.VERIFIED,
|
||||
UserPackets.IS_PRIMARY,
|
||||
@@ -20,10 +21,11 @@ public abstract class UserAttributesAdapter extends CursorAdapter {
|
||||
protected static final int INDEX_ID = 0;
|
||||
protected static final int INDEX_TYPE = 1;
|
||||
protected static final int INDEX_USER_ID = 2;
|
||||
protected static final int INDEX_RANK = 3;
|
||||
protected static final int INDEX_VERIFIED = 4;
|
||||
protected static final int INDEX_IS_PRIMARY = 5;
|
||||
protected static final int INDEX_IS_REVOKED = 6;
|
||||
protected static final int INDEX_ATTRIBUTE_DATA = 3;
|
||||
protected static final int INDEX_RANK = 4;
|
||||
protected static final int INDEX_VERIFIED = 5;
|
||||
protected static final int INDEX_IS_PRIMARY = 6;
|
||||
protected static final int INDEX_IS_REVOKED = 7;
|
||||
|
||||
public UserAttributesAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
@@ -187,7 +187,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {
|
||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(dataUri);
|
||||
return new CursorLoader(activity, baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource;
|
||||
|
||||
public class LinkedIdCreateDnsStep1Fragment extends Fragment {
|
||||
@@ -73,7 +73,7 @@ public class LinkedIdCreateDnsStep1Fragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
String proofNonce = LinkedIdentity.generateNonce();
|
||||
String proofNonce = RawLinkedIdentity.generateNonce();
|
||||
String proofText = DnsResource.generateText(getActivity(),
|
||||
mLinkedIdWizard.mFingerprint, proofNonce);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -42,7 +41,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
@@ -71,7 +70,8 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment {
|
||||
TextView mVerifyStatus;
|
||||
|
||||
String mResourceDomain;
|
||||
String mResourceNonce, mResourceString;
|
||||
int mResourceNonce;
|
||||
String mResourceString;
|
||||
|
||||
// This is a resource, set AFTER it has been verified
|
||||
DnsResource mVerifiedResource = null;
|
||||
@@ -98,7 +98,7 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment {
|
||||
final View view = inflater.inflate(R.layout.linked_create_dns_fragment_step2, container, false);
|
||||
|
||||
mResourceDomain = getArguments().getString(DOMAIN);
|
||||
mResourceNonce = getArguments().getString(NONCE);
|
||||
mResourceNonce = getArguments().getInt(NONCE);
|
||||
mResourceString = getArguments().getString(TEXT);
|
||||
|
||||
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||
@@ -308,7 +308,7 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment {
|
||||
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
|
||||
|
||||
WrappedUserAttribute ua =
|
||||
LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute();
|
||||
RawLinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute();
|
||||
|
||||
skp.mAddUserAttribute.add(ua);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource;
|
||||
|
||||
public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
|
||||
@@ -72,7 +72,7 @@ public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
String proofNonce = LinkedIdentity.generateNonce();
|
||||
String proofNonce = RawLinkedIdentity.generateNonce();
|
||||
String proofText = GenericHttpsResource.generateText(getActivity(),
|
||||
mLinkedIdWizard.mFingerprint, proofNonce);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
@@ -73,7 +73,8 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment {
|
||||
TextView mVerifyStatus;
|
||||
|
||||
String mResourceUri;
|
||||
String mResourceNonce, mResourceString;
|
||||
int mResourceNonce;
|
||||
String mResourceString;
|
||||
|
||||
// This is a resource, set AFTER it has been verified
|
||||
GenericHttpsResource mVerifiedResource = null;
|
||||
@@ -100,7 +101,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment {
|
||||
final View view = inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false);
|
||||
|
||||
mResourceUri = getArguments().getString(URI);
|
||||
mResourceNonce = getArguments().getString(NONCE);
|
||||
mResourceNonce = getArguments().getInt(NONCE);
|
||||
mResourceString = getArguments().getString(TEXT);
|
||||
|
||||
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||
@@ -314,7 +315,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment {
|
||||
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
|
||||
|
||||
WrappedUserAttribute ua =
|
||||
LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute();
|
||||
RawLinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute();
|
||||
|
||||
skp.mAddUserAttribute.add(ua);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
@@ -92,7 +92,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
String proofNonce = LinkedIdentity.generateNonce();
|
||||
String proofNonce = RawLinkedIdentity.generateNonce();
|
||||
String proofText = TwitterResource.generateText(getActivity(),
|
||||
mLinkedIdWizard.mFingerprint, proofNonce);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:id="@+id/card_view"
|
||||
android:id="@+id/card_identities"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -41,6 +41,34 @@
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:id="@+id/card_linked_ids"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
card_view:cardBackgroundColor="@android:color/white"
|
||||
card_view:cardElevation="2dp"
|
||||
card_view:cardUseCompatPadding="true"
|
||||
card_view:cardCornerRadius="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/CardViewHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/section_linked_identities" />
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.FixedListView
|
||||
android:id="@+id/view_key_linked_ids"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp" />
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1296,5 +1296,6 @@
|
||||
<string name="linked_verify_pending">Not yet verified</string>
|
||||
<string name="linked_need_verify">The resource needs to be verified before you can proceed!</string>
|
||||
<string name="menu_linked_add_identity">"Add Linked Identity"</string>
|
||||
<string name="section_linked_identities">Linked Identities</string>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user