Merge remote-tracking branch 'origin/master' into wrapped-key-ring
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* 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 android.support.v4.widget;
|
package android.support.v4.widget;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -52,7 +52,7 @@ import java.util.TimeZone;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class HkpKeyServer extends KeyServer {
|
public class HkpKeyserver extends Keyserver {
|
||||||
private static class HttpError extends Exception {
|
private static class HttpError extends Exception {
|
||||||
private static final long serialVersionUID = 1718783705229428893L;
|
private static final long serialVersionUID = 1718783705229428893L;
|
||||||
private int mCode;
|
private int mCode;
|
||||||
@ -148,7 +148,7 @@ public class HkpKeyServer extends KeyServer {
|
|||||||
* connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
|
* connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
|
||||||
* ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>").
|
* ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>").
|
||||||
*/
|
*/
|
||||||
public HkpKeyServer(String hostAndPort) {
|
public HkpKeyserver(String hostAndPort) {
|
||||||
String host = hostAndPort;
|
String host = hostAndPort;
|
||||||
short port = PORT_DEFAULT;
|
short port = PORT_DEFAULT;
|
||||||
final int colonPosition = hostAndPort.lastIndexOf(':');
|
final int colonPosition = hostAndPort.lastIndexOf(':');
|
||||||
@ -161,7 +161,7 @@ public class HkpKeyServer extends KeyServer {
|
|||||||
mPort = port;
|
mPort = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HkpKeyServer(String host, short port) {
|
public HkpKeyserver(String host, short port) {
|
||||||
mHost = host;
|
mHost = host;
|
||||||
mPort = port;
|
mPort = port;
|
||||||
}
|
}
|
||||||
@ -237,6 +237,7 @@ public class HkpKeyServer extends KeyServer {
|
|||||||
final Matcher matcher = PUB_KEY_LINE.matcher(data);
|
final Matcher matcher = PUB_KEY_LINE.matcher(data);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
||||||
|
entry.setQuery(query);
|
||||||
|
|
||||||
entry.setBitStrength(Integer.parseInt(matcher.group(3)));
|
entry.setBitStrength(Integer.parseInt(matcher.group(3)));
|
||||||
|
|
||||||
@ -247,7 +248,7 @@ public class HkpKeyServer extends KeyServer {
|
|||||||
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
|
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
|
||||||
String fingerprintOrKeyId = matcher.group(1);
|
String fingerprintOrKeyId = matcher.group(1);
|
||||||
if (fingerprintOrKeyId.length() > 16) {
|
if (fingerprintOrKeyId.length() > 16) {
|
||||||
entry.setFingerPrintHex(fingerprintOrKeyId.toLowerCase(Locale.US));
|
entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US));
|
||||||
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
|
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
|
||||||
- 16, fingerprintOrKeyId.length()));
|
- 16, fingerprintOrKeyId.length()));
|
||||||
} else {
|
} else {
|
@ -45,11 +45,13 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
public String keyIdHex;
|
public String keyIdHex;
|
||||||
public boolean revoked;
|
public boolean revoked;
|
||||||
public Date date; // TODO: not displayed
|
public Date date; // TODO: not displayed
|
||||||
public String fingerPrintHex;
|
public String fingerprintHex;
|
||||||
public int bitStrength;
|
public int bitStrength;
|
||||||
public String algorithm;
|
public String algorithm;
|
||||||
public boolean secretKey;
|
public boolean secretKey;
|
||||||
public String mPrimaryUserId;
|
public String mPrimaryUserId;
|
||||||
|
private String mExtraData;
|
||||||
|
private String mQuery;
|
||||||
|
|
||||||
private boolean mSelected;
|
private boolean mSelected;
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
dest.writeLong(keyId);
|
dest.writeLong(keyId);
|
||||||
dest.writeByte((byte) (revoked ? 1 : 0));
|
dest.writeByte((byte) (revoked ? 1 : 0));
|
||||||
dest.writeSerializable(date);
|
dest.writeSerializable(date);
|
||||||
dest.writeString(fingerPrintHex);
|
dest.writeString(fingerprintHex);
|
||||||
dest.writeString(keyIdHex);
|
dest.writeString(keyIdHex);
|
||||||
dest.writeInt(bitStrength);
|
dest.writeInt(bitStrength);
|
||||||
dest.writeString(algorithm);
|
dest.writeString(algorithm);
|
||||||
@ -74,6 +76,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
dest.writeByte((byte) (mSelected ? 1 : 0));
|
dest.writeByte((byte) (mSelected ? 1 : 0));
|
||||||
dest.writeInt(mBytes.length);
|
dest.writeInt(mBytes.length);
|
||||||
dest.writeByteArray(mBytes);
|
dest.writeByteArray(mBytes);
|
||||||
|
dest.writeString(mExtraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
|
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
|
||||||
@ -85,7 +88,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
vr.keyId = source.readLong();
|
vr.keyId = source.readLong();
|
||||||
vr.revoked = source.readByte() == 1;
|
vr.revoked = source.readByte() == 1;
|
||||||
vr.date = (Date) source.readSerializable();
|
vr.date = (Date) source.readSerializable();
|
||||||
vr.fingerPrintHex = source.readString();
|
vr.fingerprintHex = source.readString();
|
||||||
vr.keyIdHex = source.readString();
|
vr.keyIdHex = source.readString();
|
||||||
vr.bitStrength = source.readInt();
|
vr.bitStrength = source.readInt();
|
||||||
vr.algorithm = source.readString();
|
vr.algorithm = source.readString();
|
||||||
@ -93,6 +96,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
vr.mSelected = source.readByte() == 1;
|
vr.mSelected = source.readByte() == 1;
|
||||||
vr.mBytes = new byte[source.readInt()];
|
vr.mBytes = new byte[source.readInt()];
|
||||||
source.readByteArray(vr.mBytes);
|
source.readByteArray(vr.mBytes);
|
||||||
|
vr.mExtraData = source.readString();
|
||||||
|
|
||||||
return vr;
|
return vr;
|
||||||
}
|
}
|
||||||
@ -150,12 +154,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
this.date = date;
|
this.date = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFingerPrintHex() {
|
public String getFingerprintHex() {
|
||||||
return fingerPrintHex;
|
return fingerprintHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFingerPrintHex(String fingerPrintHex) {
|
public void setFingerprintHex(String fingerprintHex) {
|
||||||
this.fingerPrintHex = fingerPrintHex;
|
this.fingerprintHex = fingerprintHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBitStrength() {
|
public int getBitStrength() {
|
||||||
@ -198,6 +202,22 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
mPrimaryUserId = uid;
|
mPrimaryUserId = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getExtraData() {
|
||||||
|
return mExtraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtraData(String extraData) {
|
||||||
|
mExtraData = extraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQuery() {
|
||||||
|
return mQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(String query) {
|
||||||
|
mQuery = query;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for later querying from keyserver
|
* Constructor for later querying from keyserver
|
||||||
*/
|
*/
|
||||||
@ -260,7 +280,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||||
|
|
||||||
this.revoked = key.isRevoked();
|
this.revoked = key.isRevoked();
|
||||||
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
|
this.fingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
|
||||||
this.bitStrength = key.getBitStrength();
|
this.bitStrength = key.getBitStrength();
|
||||||
final int algorithm = key.getAlgorithm();
|
final int algorithm = key.getAlgorithm();
|
||||||
this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm);
|
this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm);
|
||||||
|
@ -21,6 +21,7 @@ import org.json.JSONArray;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.util.JWalk;
|
import org.sufficientlysecure.keychain.util.JWalk;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
@ -28,19 +29,20 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
public class KeybaseKeyServer extends KeyServer {
|
public class KeybaseKeyserver extends Keyserver {
|
||||||
|
private String mQuery;
|
||||||
private WeakHashMap<String, String> mKeyCache = new WeakHashMap<String, String>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
|
public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
|
||||||
InsufficientQuery {
|
InsufficientQuery {
|
||||||
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
|
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
|
||||||
|
|
||||||
|
if (query.startsWith("0x")) {
|
||||||
|
// cut off "0x" if a user is searching for a key id
|
||||||
|
query = query.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
|
JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -50,59 +52,76 @@ public class KeybaseKeyServer extends KeyServer {
|
|||||||
|
|
||||||
// only list them if they have a key
|
// only list them if they have a key
|
||||||
if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
|
if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
|
||||||
|
String keybaseId = JWalk.getString(match, "components", "username", "val");
|
||||||
|
String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
|
||||||
|
fingerprint = fingerprint.replace(" ", "").toUpperCase();
|
||||||
|
|
||||||
|
if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) {
|
||||||
|
results.add(makeEntry(match));
|
||||||
|
} else {
|
||||||
results.add(makeEntry(match));
|
results.add(makeEntry(match));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
Log.e(Constants.TAG, "keybase result parsing error", e);
|
||||||
throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage());
|
throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONObject getUser(String keybaseID) throws QueryException {
|
private JSONObject getUser(String keybaseId) throws QueryException {
|
||||||
try {
|
try {
|
||||||
return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID);
|
return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String detail = "";
|
String detail = "";
|
||||||
if (keybaseID != null) {
|
if (keybaseId != null) {
|
||||||
detail = ". Query was for user '" + keybaseID + "'";
|
detail = ". Query was for user '" + keybaseId + "'";
|
||||||
}
|
}
|
||||||
throw new QueryException(e.getMessage() + detail);
|
throw new QueryException(e.getMessage() + detail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException {
|
private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException {
|
||||||
|
|
||||||
String keybaseID = JWalk.getString(match, "components", "username", "val");
|
|
||||||
String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
|
|
||||||
key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase();
|
|
||||||
match = getUser(keybaseID);
|
|
||||||
|
|
||||||
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
||||||
|
entry.setQuery(mQuery);
|
||||||
|
|
||||||
// TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls
|
String keybaseId = JWalk.getString(match, "components", "username", "val");
|
||||||
entry.setBitStrength(4096);
|
String fullName = JWalk.getString(match, "components", "full_name", "val");
|
||||||
entry.setAlgorithm("RSA");
|
String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
|
||||||
entry.setKeyIdHex("0x" + key_fingerprint);
|
fingerprint = fingerprint.replace(" ", "").toUpperCase(); // not strictly necessary but doesn't hurt
|
||||||
entry.setRevoked(false);
|
entry.setFingerprintHex(fingerprint);
|
||||||
|
|
||||||
// ctime
|
entry.setKeyIdHex("0x" + fingerprint.substring(Math.max(0, fingerprint.length() - 16)));
|
||||||
final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime");
|
// store extra info, so we can query for the keybase id directly
|
||||||
final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
entry.setExtraData(keybaseId);
|
||||||
tmpGreg.setTimeInMillis(creationDate * 1000);
|
|
||||||
entry.setDate(tmpGreg.getTime());
|
|
||||||
|
|
||||||
// key bits
|
final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo");
|
||||||
// we have to fetch the user object to construct the search-result list, so we might as
|
entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId));
|
||||||
// well (weakly) remember the key, in case they try to import it
|
final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits");
|
||||||
mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle"));
|
entry.setBitStrength(bitStrength);
|
||||||
|
|
||||||
// String displayName = JWalk.getString(match, "them", "profile", "full_name");
|
|
||||||
ArrayList<String> userIds = new ArrayList<String>();
|
ArrayList<String> userIds = new ArrayList<String>();
|
||||||
String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>";
|
String name = fullName + " <keybase.io/" + keybaseId + ">";
|
||||||
userIds.add(name);
|
userIds.add(name);
|
||||||
userIds.add(keybaseID);
|
try {
|
||||||
|
userIds.add("github.com/" + JWalk.getString(match, "components", "github", "val"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
userIds.add("twitter.com/" + JWalk.getString(match, "components", "twitter", "val"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONArray array = JWalk.getArray(match, "components", "websites");
|
||||||
|
JSONObject website = array.getJSONObject(0);
|
||||||
|
userIds.add(JWalk.getString(website, "val"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
entry.setUserIds(userIds);
|
entry.setUserIds(userIds);
|
||||||
entry.setPrimaryUserId(name);
|
entry.setPrimaryUserId(name);
|
||||||
return entry;
|
return entry;
|
||||||
@ -142,17 +161,13 @@ public class KeybaseKeyServer extends KeyServer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(String id) throws QueryException {
|
public String get(String id) throws QueryException {
|
||||||
String key = mKeyCache.get(id);
|
|
||||||
if (key == null) {
|
|
||||||
try {
|
try {
|
||||||
JSONObject user = getUser(id);
|
JSONObject user = getUser(id);
|
||||||
key = JWalk.getString(user, "them", "public_keys", "primary", "bundle");
|
return JWalk.getString(user, "them", "public_keys", "primary", "bundle");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new QueryException(e.getMessage());
|
throw new QueryException(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(String armoredKey) throws AddKeyException {
|
public void add(String armoredKey) throws AddKeyException {
|
@ -23,7 +23,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class KeyServer {
|
public abstract class Keyserver {
|
||||||
public static class QueryException extends Exception {
|
public static class QueryException extends Exception {
|
||||||
private static final long serialVersionUID = 2703768928624654512L;
|
private static final long serialVersionUID = 2703768928624654512L;
|
||||||
|
|
@ -36,9 +36,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyServer;
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeyServer.AddKeyException;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -100,7 +100,7 @@ public class PgpImportExport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean uploadKeyRingToServer(HkpKeyServer server, WrappedPublicKeyRing keyring) {
|
public boolean uploadKeyRingToServer(HkpKeyserver server, WrappedPublicKeyRing keyring) {
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
ArmoredOutputStream aos = null;
|
ArmoredOutputStream aos = null;
|
||||||
try {
|
try {
|
||||||
|
@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||||
@ -54,9 +55,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyServer;
|
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer;
|
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
|
|
||||||
@ -721,7 +721,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
// and dataUri!
|
// and dataUri!
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||||
|
|
||||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||||
WrappedPublicKeyRing keyring = providerHelper.getWrappedPublicKeyRing(dataUri);
|
WrappedPublicKeyRing keyring = providerHelper.getWrappedPublicKeyRing(dataUri);
|
||||||
@ -740,11 +740,11 @@ public class KeychainIntentService extends IntentService
|
|||||||
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
|
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KeybaseKeyServer server = new KeybaseKeyServer();
|
KeybaseKeyserver server = new KeybaseKeyserver();
|
||||||
for (ImportKeysListEntry entry : entries) {
|
for (ImportKeysListEntry entry : entries) {
|
||||||
// the keybase handle is in userId(1)
|
// the keybase handle is in userId(1)
|
||||||
String keybaseID = entry.getUserIds().get(1);
|
String keybaseId = entry.getExtraData();
|
||||||
byte[] downloadedKeyBytes = server.get(keybaseID).getBytes();
|
byte[] downloadedKeyBytes = server.get(keybaseId).getBytes();
|
||||||
|
|
||||||
// create PGPKeyRing object based on downloaded armored key
|
// create PGPKeyRing object based on downloaded armored key
|
||||||
PGPKeyRing downloadedKey = null;
|
PGPKeyRing downloadedKey = null;
|
||||||
@ -791,13 +791,13 @@ public class KeychainIntentService extends IntentService
|
|||||||
String keyServer = data.getString(DOWNLOAD_KEY_SERVER);
|
String keyServer = data.getString(DOWNLOAD_KEY_SERVER);
|
||||||
|
|
||||||
// this downloads the keys and places them into the ImportKeysListEntry entries
|
// this downloads the keys and places them into the ImportKeysListEntry entries
|
||||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||||
|
|
||||||
for (ImportKeysListEntry entry : entries) {
|
for (ImportKeysListEntry entry : entries) {
|
||||||
// if available use complete fingerprint for get request
|
// if available use complete fingerprint for get request
|
||||||
byte[] downloadedKeyBytes;
|
byte[] downloadedKeyBytes;
|
||||||
if (entry.getFingerPrintHex() != null) {
|
if (entry.getFingerprintHex() != null) {
|
||||||
downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes();
|
downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes();
|
||||||
} else {
|
} else {
|
||||||
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
|
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
|
||||||
}
|
}
|
||||||
@ -807,10 +807,10 @@ public class KeychainIntentService extends IntentService
|
|||||||
UncachedKeyRing.decodePubkeyFromData(downloadedKeyBytes);
|
UncachedKeyRing.decodePubkeyFromData(downloadedKeyBytes);
|
||||||
|
|
||||||
// verify downloaded key by comparing fingerprints
|
// verify downloaded key by comparing fingerprints
|
||||||
if (entry.getFingerPrintHex() != null) {
|
if (entry.getFingerprintHex() != null) {
|
||||||
String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(
|
String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(
|
||||||
downloadedKey.getFingerprint());
|
downloadedKey.getFingerprint());
|
||||||
if (downloadedKeyFp.equals(entry.getFingerPrintHex())) {
|
if (downloadedKeyFp.equals(entry.getFingerprintHex())) {
|
||||||
Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " +
|
Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " +
|
||||||
"the requested fingerprint!");
|
"the requested fingerprint!");
|
||||||
} else {
|
} else {
|
||||||
|
@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
|
|||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeyServer;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -280,13 +280,13 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
mAdapter.getCount(), mAdapter.getCount()),
|
mAdapter.getCount(), mAdapter.getCount()),
|
||||||
AppMsg.STYLE_INFO
|
AppMsg.STYLE_INFO
|
||||||
).show();
|
).show();
|
||||||
} else if (error instanceof KeyServer.InsufficientQuery) {
|
} else if (error instanceof Keyserver.InsufficientQuery) {
|
||||||
AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query,
|
AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query,
|
||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
} else if (error instanceof KeyServer.QueryException) {
|
} else if (error instanceof Keyserver.QueryException) {
|
||||||
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
|
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
|
||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
} else if (error instanceof KeyServer.TooManyResponses) {
|
} else if (error instanceof Keyserver.TooManyResponses) {
|
||||||
AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses,
|
AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses,
|
||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
mAdapter.getCount(), mAdapter.getCount()),
|
mAdapter.getCount(), mAdapter.getCount()),
|
||||||
AppMsg.STYLE_INFO
|
AppMsg.STYLE_INFO
|
||||||
).show();
|
).show();
|
||||||
} else if (error instanceof KeyServer.QueryException) {
|
} else if (error instanceof Keyserver.QueryException) {
|
||||||
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
|
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
|
||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ import android.support.v4.content.CursorLoader;
|
|||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
@ -61,8 +62,8 @@ import org.sufficientlysecure.keychain.helper.ExportHelper;
|
|||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
|
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -82,7 +83,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
private KeyListAdapter mAdapter;
|
private KeyListAdapter mAdapter;
|
||||||
private StickyListHeadersListView mStickyList;
|
private StickyListHeadersListView mStickyList;
|
||||||
|
|
||||||
private String mCurQuery;
|
private String mQuery;
|
||||||
private SearchView mSearchView;
|
private SearchView mSearchView;
|
||||||
// empty list layout
|
// empty list layout
|
||||||
private BootstrapButton mButtonEmptyCreate;
|
private BootstrapButton mButtonEmptyCreate;
|
||||||
@ -130,7 +131,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
/**
|
/**
|
||||||
* Define Adapter and Loader on create of Activity
|
* Define Adapter and Loader on create of Activity
|
||||||
*/
|
*/
|
||||||
@SuppressLint("NewApi")
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
@ -141,8 +142,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
mStickyList.setFastScrollEnabled(true);
|
mStickyList.setFastScrollEnabled(true);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
|
* Multi-selection is only available for Android >= 3.0
|
||||||
* available for Android >= 3.0
|
|
||||||
*/
|
*/
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
mStickyList.setFastScrollAlwaysVisible(true);
|
mStickyList.setFastScrollAlwaysVisible(true);
|
||||||
@ -263,9 +263,18 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||||
String where = null;
|
String where = null;
|
||||||
String whereArgs[] = null;
|
String whereArgs[] = null;
|
||||||
if (mCurQuery != null) {
|
if (mQuery != null) {
|
||||||
where = KeyRings.USER_ID + " LIKE ?";
|
String[] words = mQuery.trim().split("\\s+");
|
||||||
whereArgs = new String[]{"%" + mCurQuery + "%"};
|
whereArgs = new String[words.length];
|
||||||
|
for (int i = 0; i < words.length; ++i) {
|
||||||
|
if (where == null) {
|
||||||
|
where = "";
|
||||||
|
} else {
|
||||||
|
where += " AND ";
|
||||||
|
}
|
||||||
|
where += KeyRings.USER_ID + " LIKE ?";
|
||||||
|
whereArgs[i] = "%" + words[i] + "%";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now create and return a CursorLoader that will take care of
|
// Now create and return a CursorLoader that will take care of
|
||||||
@ -277,7 +286,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
// Swap the new cursor in. (The framework will take care of closing the
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
// old cursor once we return.)
|
// old cursor once we return.)
|
||||||
mAdapter.setSearchQuery(mCurQuery);
|
mAdapter.setSearchQuery(mQuery);
|
||||||
mAdapter.swapCursor(data);
|
mAdapter.swapCursor(data);
|
||||||
|
|
||||||
mStickyList.setAdapter(mAdapter);
|
mStickyList.setAdapter(mAdapter);
|
||||||
@ -312,7 +321,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
startActivity(viewIntent);
|
startActivity(viewIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(11)
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
|
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
|
||||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||||
@ -329,7 +338,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
* @param masterKeyIds
|
* @param masterKeyIds
|
||||||
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
|
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
|
||||||
*/
|
*/
|
||||||
@TargetApi(11)
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
||||||
// Can only work on singular secret keys
|
// Can only work on singular secret keys
|
||||||
if(hasSecret && masterKeyIds.length > 1) {
|
if(hasSecret && masterKeyIds.length > 1) {
|
||||||
@ -379,7 +388,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||||
mCurQuery = null;
|
mQuery = null;
|
||||||
mSearchView.setQuery("", true);
|
mSearchView.setQuery("", true);
|
||||||
getLoaderManager().restartLoader(0, null, KeyListFragment.this);
|
getLoaderManager().restartLoader(0, null, KeyListFragment.this);
|
||||||
return true;
|
return true;
|
||||||
@ -399,7 +408,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
// Called when the action bar search text has changed. Update
|
// Called when the action bar search text has changed. Update
|
||||||
// the search filter, and restart the loader to do a new query
|
// the search filter, and restart the loader to do a new query
|
||||||
// with this filter.
|
// with this filter.
|
||||||
mCurQuery = !TextUtils.isEmpty(s) ? s : null;
|
mQuery = !TextUtils.isEmpty(s) ? s : null;
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -407,7 +416,8 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
/**
|
/**
|
||||||
* Implements StickyListHeadersAdapter from library
|
* Implements StickyListHeadersAdapter from library
|
||||||
*/
|
*/
|
||||||
private class KeyListAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter {
|
private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter {
|
||||||
|
private String mQuery;
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
|
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
|
||||||
@ -418,6 +428,10 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
mInflater = LayoutInflater.from(context);
|
mInflater = LayoutInflater.from(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSearchQuery(String query) {
|
||||||
|
mQuery = query;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
return super.swapCursor(newCursor);
|
return super.swapCursor(newCursor);
|
||||||
@ -456,18 +470,19 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
Highlighter highlighter = new Highlighter(context, mQuery);
|
||||||
ItemViewHolder h = (ItemViewHolder) view.getTag();
|
ItemViewHolder h = (ItemViewHolder) view.getTag();
|
||||||
|
|
||||||
{ // set name and stuff, common to both key types
|
{ // set name and stuff, common to both key types
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
String userId = cursor.getString(INDEX_USER_ID);
|
||||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||||
if (userIdSplit[0] != null) {
|
if (userIdSplit[0] != null) {
|
||||||
h.mMainUserId.setText(highlightSearchQuery(userIdSplit[0]));
|
h.mMainUserId.setText(highlighter.highlight(userIdSplit[0]));
|
||||||
} else {
|
} else {
|
||||||
h.mMainUserId.setText(R.string.user_id_no_name);
|
h.mMainUserId.setText(R.string.user_id_no_name);
|
||||||
}
|
}
|
||||||
if (userIdSplit[1] != null) {
|
if (userIdSplit[1] != null) {
|
||||||
h.mMainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
|
h.mMainUserIdRest.setText(highlighter.highlight(userIdSplit[1]));
|
||||||
h.mMainUserIdRest.setVisibility(View.VISIBLE);
|
h.mMainUserIdRest.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
h.mMainUserIdRest.setVisibility(View.GONE);
|
h.mMainUserIdRest.setVisibility(View.GONE);
|
||||||
|
@ -55,7 +55,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
private SelectKeyCursorAdapter mAdapter;
|
private SelectKeyCursorAdapter mAdapter;
|
||||||
private EditText mSearchView;
|
private EditText mSearchView;
|
||||||
private long mSelectedMasterKeyIds[];
|
private long mSelectedMasterKeyIds[];
|
||||||
private String mCurQuery;
|
private String mQuery;
|
||||||
|
|
||||||
// copied from ListFragment
|
// copied from ListFragment
|
||||||
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
|
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
|
||||||
@ -281,9 +281,18 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
}
|
}
|
||||||
String where = null;
|
String where = null;
|
||||||
String whereArgs[] = null;
|
String whereArgs[] = null;
|
||||||
if (mCurQuery != null) {
|
if (mQuery != null) {
|
||||||
where = KeyRings.USER_ID + " LIKE ?";
|
String[] words = mQuery.trim().split("\\s+");
|
||||||
whereArgs = new String[]{"%" + mCurQuery + "%"};
|
whereArgs = new String[words.length];
|
||||||
|
for (int i = 0; i < words.length; ++i) {
|
||||||
|
if (where == null) {
|
||||||
|
where = "";
|
||||||
|
} else {
|
||||||
|
where += " AND ";
|
||||||
|
}
|
||||||
|
where += KeyRings.USER_ID + " LIKE ?";
|
||||||
|
whereArgs[i] = "%" + words[i] + "%";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now create and return a CursorLoader that will take care of
|
// Now create and return a CursorLoader that will take care of
|
||||||
@ -295,7 +304,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
// Swap the new cursor in. (The framework will take care of closing the
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
// old cursor once we return.)
|
// old cursor once we return.)
|
||||||
mAdapter.setSearchQuery(mCurQuery);
|
mAdapter.setSearchQuery(mQuery);
|
||||||
mAdapter.swapCursor(data);
|
mAdapter.swapCursor(data);
|
||||||
|
|
||||||
// The list should now be shown.
|
// The list should now be shown.
|
||||||
@ -329,7 +338,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable editable) {
|
public void afterTextChanged(Editable editable) {
|
||||||
mCurQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null;
|
mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null;
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
||||||
*
|
|
||||||
* 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.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public abstract class HighlightQueryCursorAdapter extends CursorAdapter {
|
|
||||||
|
|
||||||
private String mCurQuery;
|
|
||||||
|
|
||||||
public HighlightQueryCursorAdapter(Context context, Cursor c, int flags) {
|
|
||||||
super(context, c, flags);
|
|
||||||
mCurQuery = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchQuery(String searchQuery) {
|
|
||||||
mCurQuery = searchQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSearchQuery() {
|
|
||||||
return mCurQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Spannable highlightSearchQuery(String text) {
|
|
||||||
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
|
|
||||||
|
|
||||||
if (mCurQuery != null) {
|
|
||||||
Pattern pattern = Pattern.compile("(?i)" + mCurQuery);
|
|
||||||
Matcher matcher = pattern.matcher(text);
|
|
||||||
if (matcher.find()) {
|
|
||||||
highlight.setSpan(
|
|
||||||
new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
|
|
||||||
matcher.start(),
|
|
||||||
matcher.end(),
|
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
return highlight;
|
|
||||||
} else {
|
|
||||||
return highlight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,6 +33,7 @@ import android.widget.TextView;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -99,6 +100,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
ImportKeysListEntry entry = mData.get(position);
|
ImportKeysListEntry entry = mData.get(position);
|
||||||
|
Highlighter highlighter = new Highlighter(mActivity, entry.getQuery());
|
||||||
ViewHolder holder;
|
ViewHolder holder;
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
holder = new ViewHolder();
|
holder = new ViewHolder();
|
||||||
@ -128,7 +130,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
+ " " + userIdSplit[0]);
|
+ " " + userIdSplit[0]);
|
||||||
holder.mainUserId.setTextColor(Color.RED);
|
holder.mainUserId.setTextColor(Color.RED);
|
||||||
} else {
|
} else {
|
||||||
holder.mainUserId.setText(userIdSplit[0]);
|
holder.mainUserId.setText(highlighter.highlight(userIdSplit[0]));
|
||||||
holder.mainUserId.setTextColor(Color.BLACK);
|
holder.mainUserId.setTextColor(Color.BLACK);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -139,21 +141,26 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
// email
|
// email
|
||||||
if (userIdSplit[1] != null) {
|
if (userIdSplit[1] != null) {
|
||||||
holder.mainUserIdRest.setVisibility(View.VISIBLE);
|
holder.mainUserIdRest.setVisibility(View.VISIBLE);
|
||||||
holder.mainUserIdRest.setText(userIdSplit[1]);
|
holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit[1]));
|
||||||
} else {
|
} else {
|
||||||
holder.mainUserIdRest.setVisibility(View.GONE);
|
holder.mainUserIdRest.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.keyId.setText(entry.keyIdHex);
|
holder.keyId.setText(entry.keyIdHex);
|
||||||
|
|
||||||
if (entry.fingerPrintHex != null) {
|
if (entry.fingerprintHex != null) {
|
||||||
holder.fingerprint.setVisibility(View.VISIBLE);
|
holder.fingerprint.setVisibility(View.VISIBLE);
|
||||||
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex));
|
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerprintHex));
|
||||||
} else {
|
} else {
|
||||||
holder.fingerprint.setVisibility(View.GONE);
|
holder.fingerprint.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry.bitStrength != 0 && entry.algorithm != null) {
|
||||||
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
||||||
|
holder.algorithm.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.algorithm.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.revoked) {
|
if (entry.revoked) {
|
||||||
holder.status.setVisibility(View.VISIBLE);
|
holder.status.setVisibility(View.VISIBLE);
|
||||||
@ -177,7 +184,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
String uid = it.next();
|
String uid = it.next();
|
||||||
TextView uidView = (TextView) mInflater.inflate(
|
TextView uidView = (TextView) mInflater.inflate(
|
||||||
R.layout.import_keys_list_entry_user_id, null);
|
R.layout.import_keys_list_entry_user_id, null);
|
||||||
uidView.setText(uid);
|
uidView.setText(highlighter.highlight(uid));
|
||||||
holder.userIdsList.addView(uidView);
|
holder.userIdsList.addView(uidView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ import android.support.v4.content.AsyncTaskLoader;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeyServer;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer;
|
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -86,7 +86,7 @@ public class ImportKeysListKeybaseLoader
|
|||||||
*/
|
*/
|
||||||
private void queryServer(String query) {
|
private void queryServer(String query) {
|
||||||
|
|
||||||
KeybaseKeyServer server = new KeybaseKeyServer();
|
KeybaseKeyserver server = new KeybaseKeyserver();
|
||||||
try {
|
try {
|
||||||
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
||||||
|
|
||||||
@ -94,11 +94,11 @@ public class ImportKeysListKeybaseLoader
|
|||||||
|
|
||||||
mEntryList.addAll(searchResult);
|
mEntryList.addAll(searchResult);
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||||
} catch (KeyServer.InsufficientQuery e) {
|
} catch (Keyserver.InsufficientQuery e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
} catch (KeyServer.QueryException e) {
|
} catch (Keyserver.QueryException e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
} catch (KeyServer.TooManyResponses e) {
|
} catch (Keyserver.TooManyResponses e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ import android.content.Context;
|
|||||||
import android.support.v4.content.AsyncTaskLoader;
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyServer;
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeyServer;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -92,7 +92,7 @@ public class ImportKeysListServerLoader
|
|||||||
* Query keyserver
|
* Query keyserver
|
||||||
*/
|
*/
|
||||||
private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
|
private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
|
||||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||||
try {
|
try {
|
||||||
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ public class ImportKeysListServerLoader
|
|||||||
* set fingerprint explicitly after query
|
* set fingerprint explicitly after query
|
||||||
* to enforce a check when the key is imported by KeychainIntentService
|
* to enforce a check when the key is imported by KeychainIntentService
|
||||||
*/
|
*/
|
||||||
uniqueEntry.setFingerPrintHex(fingerprint);
|
uniqueEntry.setFingerprintHex(fingerprint);
|
||||||
uniqueEntry.setSelected(true);
|
uniqueEntry.setSelected(true);
|
||||||
mEntryList.add(uniqueEntry);
|
mEntryList.add(uniqueEntry);
|
||||||
}
|
}
|
||||||
@ -116,11 +116,11 @@ public class ImportKeysListServerLoader
|
|||||||
mEntryList.addAll(searchResult);
|
mEntryList.addAll(searchResult);
|
||||||
}
|
}
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||||
} catch (KeyServer.InsufficientQuery e) {
|
} catch (Keyserver.InsufficientQuery e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
} catch (KeyServer.QueryException e) {
|
} catch (Keyserver.QueryException e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
} catch (KeyServer.TooManyResponses e) {
|
} catch (Keyserver.TooManyResponses e) {
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -29,6 +30,7 @@ import android.widget.TextView;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
|
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@ -36,8 +38,9 @@ import java.util.Date;
|
|||||||
/**
|
/**
|
||||||
* Yes this class is abstract!
|
* Yes this class is abstract!
|
||||||
*/
|
*/
|
||||||
abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter {
|
abstract public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||||
|
|
||||||
|
private String mQuery;
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
protected int mIndexUserId, mIndexMasterKeyId, mIndexRevoked, mIndexExpiry;
|
protected int mIndexUserId, mIndexMasterKeyId, mIndexRevoked, mIndexExpiry;
|
||||||
@ -48,6 +51,10 @@ abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter
|
|||||||
initIndex(c);
|
initIndex(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSearchQuery(String query) {
|
||||||
|
mQuery = query;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
initIndex(newCursor);
|
initIndex(newCursor);
|
||||||
@ -101,19 +108,20 @@ abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
Highlighter highlighter = new Highlighter(context, mQuery);
|
||||||
ViewHolderItem h = (ViewHolderItem) view.getTag();
|
ViewHolderItem h = (ViewHolderItem) view.getTag();
|
||||||
|
|
||||||
String userId = cursor.getString(mIndexUserId);
|
String userId = cursor.getString(mIndexUserId);
|
||||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||||
|
|
||||||
if (userIdSplit[0] != null) {
|
if (userIdSplit[0] != null) {
|
||||||
h.mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
|
h.mainUserId.setText(highlighter.highlight(userIdSplit[0]));
|
||||||
} else {
|
} else {
|
||||||
h.mainUserId.setText(R.string.user_id_no_name);
|
h.mainUserId.setText(R.string.user_id_no_name);
|
||||||
}
|
}
|
||||||
if (userIdSplit[1] != null) {
|
if (userIdSplit[1] != null) {
|
||||||
h.mainUserIdRest.setVisibility(View.VISIBLE);
|
h.mainUserIdRest.setVisibility(View.VISIBLE);
|
||||||
h.mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
|
h.mainUserIdRest.setText(highlighter.highlight(userIdSplit[1]));
|
||||||
} else {
|
} else {
|
||||||
h.mainUserIdRest.setVisibility(View.GONE);
|
h.mainUserIdRest.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Highlighter {
|
||||||
|
private Context mContext;
|
||||||
|
private String mQuery;
|
||||||
|
|
||||||
|
public Highlighter(Context context, String query) {
|
||||||
|
mContext = context;
|
||||||
|
mQuery = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spannable highlight(String text) {
|
||||||
|
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
|
||||||
|
|
||||||
|
if (mQuery == null) {
|
||||||
|
return highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")");
|
||||||
|
Matcher matcher = pattern.matcher(text);
|
||||||
|
while (matcher.find()) {
|
||||||
|
highlight.setSpan(
|
||||||
|
new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
|
||||||
|
matcher.start(),
|
||||||
|
matcher.end(),
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlight;
|
||||||
|
}
|
||||||
|
}
|
@ -23,29 +23,77 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal hierarchy selector
|
* Minimal hierarchy selector
|
||||||
|
*
|
||||||
|
* This is for picking out an item in a large multilevel JSON object, for example look at
|
||||||
|
* the Keybase.io User object, documentation at https://keybase.io/__/api-docs/1.0#user-objects
|
||||||
|
* an example available via
|
||||||
|
* curl https://keybase.io/_/api/1.0/user/lookup.json?username=timbray
|
||||||
|
*
|
||||||
|
* If you want to retrieve the ascii-armored key, you'd say
|
||||||
|
* String key = JWalk.getString(match,"them", "public_keys", "primary", "bundle");
|
||||||
*/
|
*/
|
||||||
public class JWalk {
|
public class JWalk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an int member value from the JSON sub-object addressed by the path
|
||||||
|
*
|
||||||
|
* @param json The object
|
||||||
|
* @param path list of string object member selectors
|
||||||
|
* @return the int addressed by the path, assuming such a thing exists
|
||||||
|
* @throws JSONException if any step in the path doesn’t work
|
||||||
|
*/
|
||||||
public static int getInt(JSONObject json, String... path) throws JSONException {
|
public static int getInt(JSONObject json, String... path) throws JSONException {
|
||||||
json = walk(json, path);
|
json = walk(json, path);
|
||||||
return json.getInt(path[path.length - 1]);
|
return json.getInt(path[path.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a long member value from the JSON sub-object addressed by the path
|
||||||
|
*
|
||||||
|
* @param json The object
|
||||||
|
* @param path list of string object member selectors
|
||||||
|
* @return the int addressed by the path, assuming such a thing exists
|
||||||
|
* @throws JSONException if any step in the path doesn’t work
|
||||||
|
*/
|
||||||
public static long getLong(JSONObject json, String... path) throws JSONException {
|
public static long getLong(JSONObject json, String... path) throws JSONException {
|
||||||
json = walk(json, path);
|
json = walk(json, path);
|
||||||
return json.getLong(path[path.length - 1]);
|
return json.getLong(path[path.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a String member value from the JSON sub-object addressed by the path
|
||||||
|
*
|
||||||
|
* @param json The object
|
||||||
|
* @param path list of string object member selectors
|
||||||
|
* @return the int addressed by the path, assuming such a thing exists
|
||||||
|
* @throws JSONException if any step in the path doesn’t work
|
||||||
|
*/
|
||||||
public static String getString(JSONObject json, String... path) throws JSONException {
|
public static String getString(JSONObject json, String... path) throws JSONException {
|
||||||
json = walk(json, path);
|
json = walk(json, path);
|
||||||
return json.getString(path[path.length - 1]);
|
return json.getString(path[path.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSONArray member value from the JSON sub-object addressed by the path
|
||||||
|
*
|
||||||
|
* @param json The object
|
||||||
|
* @param path list of string object member selectors
|
||||||
|
* @return the int addressed by the path, assuming such a thing exists
|
||||||
|
* @throws JSONException if any step in the path doesn’t work
|
||||||
|
*/
|
||||||
public static JSONArray getArray(JSONObject json, String... path) throws JSONException {
|
public static JSONArray getArray(JSONObject json, String... path) throws JSONException {
|
||||||
json = walk(json, path);
|
json = walk(json, path);
|
||||||
return json.getJSONArray(path[path.length - 1]);
|
return json.getJSONArray(path[path.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSONObject member value from the JSON sub-object addressed by the path, or null
|
||||||
|
*
|
||||||
|
* @param json The object
|
||||||
|
* @param path list of string object member selectors
|
||||||
|
* @return the int addressed by the path, assuming such a thing exists
|
||||||
|
* @throws JSONException if any step in the path, except for the last, doesn’t work
|
||||||
|
*/
|
||||||
public static JSONObject optObject(JSONObject json, String... path) throws JSONException {
|
public static JSONObject optObject(JSONObject json, String... path) throws JSONException {
|
||||||
json = walk(json, path);
|
json = walk(json, path);
|
||||||
return json.optJSONObject(path[path.length - 1]);
|
return json.optJSONObject(path[path.length - 1]);
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/content_frame"
|
android:id="@+id/content_frame"
|
||||||
android:layout_marginLeft="@dimen/drawer_content_padding"
|
android:layout_marginLeft="@dimen/drawer_content_padding"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent">
|
||||||
android:layout_centerHorizontal="true">
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/import_navigation_fragment"
|
android:id="@+id/import_navigation_fragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical" />
|
||||||
android:paddingLeft="4dp"
|
|
||||||
android:paddingRight="4dp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/import_footer"
|
android:id="@+id/import_footer"
|
||||||
@ -56,6 +52,7 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_below="@+id/import_navigation_fragment"
|
android:layout_below="@+id/import_navigation_fragment"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="4dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingRight="4dp" />
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -3,13 +3,15 @@
|
|||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
android:orientation="horizontal" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
android:id="@+id/import_clipboard_button"
|
android:id="@+id/import_clipboard_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:text="@string/import_clipboard_button"
|
android:text="@string/import_clipboard_button"
|
||||||
bootstrapbutton:bb_icon_left="fa-clipboard"
|
bootstrapbutton:bb_icon_left="fa-clipboard"
|
||||||
bootstrapbutton:bb_size="default"
|
bootstrapbutton:bb_size="default"
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
android:id="@+id/import_keys_file_browse"
|
android:id="@+id/import_keys_file_browse"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:text="@string/filemanager_title_open"
|
android:text="@string/filemanager_title_open"
|
||||||
android:contentDescription="@string/filemanager_title_open"
|
android:contentDescription="@string/filemanager_title_open"
|
||||||
bootstrapbutton:bb_icon_left="fa-folder-open"
|
bootstrapbutton:bb_icon_left="fa-folder-open"
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal" >
|
android:paddingTop="8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
<LinearLayout
|
android:paddingRight="16dp"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/import_keybase_query"
|
android:id="@+id/import_keybase_query"
|
||||||
android:layout_width="0dip"
|
android:layout_width="0dip"
|
||||||
@ -30,22 +29,10 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="8dp"
|
||||||
bootstrapbutton:bb_icon_left="fa-search"
|
bootstrapbutton:bb_icon_left="fa-search"
|
||||||
bootstrapbutton:bb_roundedCorners="true"
|
bootstrapbutton:bb_roundedCorners="true"
|
||||||
bootstrapbutton:bb_size="default"
|
bootstrapbutton:bb_size="default"
|
||||||
bootstrapbutton:bb_type="default" />
|
bootstrapbutton:bb_type="default" />
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
|
||||||
android:id="@+id/import_keybase_button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="70dp"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:text="@string/import_keybase_button"
|
|
||||||
bootstrapbutton:bb_size="default"
|
|
||||||
bootstrapbutton:bb_type="default" />
|
|
||||||
-->
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -25,8 +25,7 @@
|
|||||||
android:id="@+id/selected"
|
android:id="@+id/selected"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="4dp"
|
android:paddingRight="8dp"
|
||||||
android:layout_marginRight="4dp"
|
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:focusableInTouchMode="false" />
|
android:focusableInTouchMode="false" />
|
||||||
@ -46,8 +45,7 @@
|
|||||||
android:layout_width="0dip"
|
android:layout_width="0dip"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:paddingRight="4dip">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/mainUserId"
|
android:id="@+id/mainUserId"
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="10dp"
|
android:paddingTop="8dp"
|
||||||
android:orientation="horizontal" >
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
android:id="@+id/import_nfc_button"
|
android:id="@+id/import_nfc_button"
|
||||||
@ -12,7 +14,7 @@
|
|||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:text="@string/import_nfc_help_button"
|
android:text="@string/import_nfc_help_button"
|
||||||
bootstrapbutton:bb_icon_left="fa-question"
|
bootstrapbutton:bb_icon_left="fa-question"
|
||||||
bootstrapbutton:bb_size="default"
|
bootstrapbutton:bb_size="default"
|
||||||
|
@ -3,13 +3,15 @@
|
|||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:paddingTop="8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
android:id="@+id/import_qrcode_button"
|
android:id="@+id/import_qrcode_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:text="@string/import_qr_scan_button"
|
android:text="@string/import_qr_scan_button"
|
||||||
bootstrapbutton:bb_icon_left="fa-barcode"
|
bootstrapbutton:bb_icon_left="fa-barcode"
|
||||||
bootstrapbutton:bb_size="default"
|
bootstrapbutton:bb_size="default"
|
||||||
@ -19,8 +21,9 @@
|
|||||||
android:id="@+id/import_qrcode_text"
|
android:id="@+id/import_qrcode_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="10dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="10dp"
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
@ -28,8 +31,8 @@
|
|||||||
style="?android:attr/progressBarStyleHorizontal"
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="10dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="10dp"
|
android:paddingRight="16dp"
|
||||||
android:progress="0"
|
android:progress="0"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="10dp"
|
android:paddingTop="8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
@ -35,7 +37,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="8dp"
|
||||||
bootstrapbutton:bb_icon_left="fa-search"
|
bootstrapbutton:bb_icon_left="fa-search"
|
||||||
bootstrapbutton:bb_roundedCorners="true"
|
bootstrapbutton:bb_roundedCorners="true"
|
||||||
bootstrapbutton:bb_size="default"
|
bootstrapbutton:bb_size="default"
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingRight="3dip"
|
|
||||||
android:singleLine="true">
|
android:singleLine="true">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
|
@ -14,7 +14,7 @@ And don't add newlines before or after p tags because of transifex -->
|
|||||||
<p>Several applications support OpenKeychain to encrypt/sign your private communication:
|
<p>Several applications support OpenKeychain to encrypt/sign your private communication:
|
||||||
<br/><img src="apps_k9"/><br/>K-9 Mail: OpenKeychain support available in current <a href="https://github.com/k9mail/k-9/releases/tag/4.904">alpha build</a>!
|
<br/><img src="apps_k9"/><br/>K-9 Mail: OpenKeychain support available in current <a href="https://github.com/k9mail/k-9/releases/tag/4.904">alpha build</a>!
|
||||||
<br/><a href="market://details?id=eu.siacs.conversations"><img src="apps_conversations"/><br/>Conversations</a>: Jabber/XMPP client
|
<br/><a href="market://details?id=eu.siacs.conversations"><img src="apps_conversations"/><br/>Conversations</a>: Jabber/XMPP client
|
||||||
<br/><a href="market://details?id=org.lf_net.pgpunlocker"><img src="apps_pgpauth"/><br/>PGPAuth</a>: App to send a PGP-signed request to a server to open or close $thing</p>
|
<br/><a href="market://details?id=org.lf_net.pgpunlocker"><img src="apps_pgpauth"/><br/>PGPAuth</a>: App to send a PGP-signed request to a server to open or close something, e.g. a door</p>
|
||||||
|
|
||||||
<h2>I found a bug in OpenKeychain!</h2>
|
<h2>I found a bug in OpenKeychain!</h2>
|
||||||
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
|
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
|
||||||
|
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 72 KiB |
BIN
Resources/screenshots/device-2014-05-14-221745.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
Resources/screenshots/device-2014-05-14-222126.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
Resources/screenshots/device-2014-05-14-222143.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
Resources/screenshots/device-2014-05-14-222455.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
Resources/screenshots/device-2014-05-14-222753.png
Normal file
After Width: | Height: | Size: 55 KiB |