From 7865b92285893ddb87fa8351d724d09d0a1eb781 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Mon, 26 May 2014 20:24:13 +0200 Subject: ContactHelper can read email addresses from contact list --- OpenKeychain/src/main/AndroidManifest.xml | 1 + .../keychain/helper/ContactHelper.java | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index f4007c098..fd26d6acf 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ + (emailSet); } + + public static List getContactMails(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, + new String[]{ContactsContract.CommonDataKinds.Email.DATA}, + null, null, null); + if (mailCursor == null) return null; + + Set mails = new HashSet(); + while (mailCursor.moveToNext()) { + String email = mailCursor.getString(0); + if (email != null) { + mails.add(email); + } + } + mailCursor.close(); + return new ArrayList(mails); + } } -- cgit v1.2.3 From 3110122a85c5659a758a8f234381a7de783bdbca Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 19:45:58 +0200 Subject: Add ability to resolve HkpKeyserver from _hkp._tcp SRV record --- OpenKeychain/build.gradle | 1 + .../keychain/keyimport/HkpKeyserver.java | 38 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 090a7a2bf..738097fa0 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -10,6 +10,7 @@ sourceSets { dependencies { compile 'com.android.support:support-v4:19.1.0' compile 'com.android.support:appcompat-v7:19.1.0' + compile 'dnsjava:dnsjava:2.1.1' compile project(':extern:openpgp-api-lib') compile project(':extern:openkeychain-api-lib') compile project(':extern:html-textview') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 5969455bd..2041548f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -33,6 +33,10 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Log; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.Type; import java.io.IOException; import java.io.InputStream; @@ -45,6 +49,8 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; @@ -336,4 +342,36 @@ public class HkpKeyserver extends Keyserver { client.getConnectionManager().shutdown(); } } + + @Override + public String toString() { + return mHost + ":" + mPort; + } + + /** + * Tries to find a server responsible for a given domain + * + * @return A responsible Keyserver or null if not found. + */ + public static HkpKeyserver resolve(String domain) { + try { + Record[] records = new Lookup("_hkp._tcp." + domain, Type.SRV).run(); + if (records.length > 0) { + Arrays.sort(records, new Comparator() { + @Override + public int compare(Record lhs, Record rhs) { + if (!(lhs instanceof SRVRecord)) return 1; + if (!(rhs instanceof SRVRecord)) return -1; + return ((SRVRecord) lhs).getPriority() - ((SRVRecord) rhs).getPriority(); + } + }); + Record record = records[0]; // This is our best choice + if (record instanceof SRVRecord) { + return new HkpKeyserver(((SRVRecord) record).getTarget().toString(), (short) ((SRVRecord) record).getPort()); + } + } + } catch (Exception ignored) { + } + return null; + } } -- cgit v1.2.3 From 8e5767f967646412a03563c966c3bb5b7c26e0fa Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 20:17:49 +0200 Subject: Store origin with ImportKeysListEntry --- .../sufficientlysecure/keychain/keyimport/HkpKeyserver.java | 1 + .../keychain/keyimport/ImportKeysListEntry.java | 11 +++++++++++ .../keychain/keyimport/KeybaseKeyserver.java | 1 + 3 files changed, 13 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 2041548f3..fbe38c30a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -244,6 +244,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); + entry.setOrigin("hkp:"+mHost+":"+mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 04b86e295..8af41a8a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -52,6 +52,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public String mPrimaryUserId; private String mExtraData; private String mQuery; + private String mOrigin; private boolean mSelected; @@ -77,6 +78,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeInt(mBytes.length); dest.writeByteArray(mBytes); dest.writeString(mExtraData); + dest.writeString(mOrigin); } public static final Creator CREATOR = new Creator() { @@ -97,6 +99,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mBytes = new byte[source.readInt()]; source.readByteArray(vr.mBytes); vr.mExtraData = source.readString(); + vr.mOrigin = source.readString(); return vr; } @@ -218,6 +221,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mQuery = query; } + public String getOrigin() { + return mOrigin; + } + + public void setOrigin(String origin) { + mOrigin = origin; + } + /** * Constructor for later querying from keyserver */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index f9b6abf18..43c0b12fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -87,6 +87,7 @@ public class KeybaseKeyserver extends Keyserver { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(mQuery); + entry.setOrigin("keybase.io"); String keybaseId = JWalk.getString(match, "components", "username", "val"); String fullName = JWalk.getString(match, "components", "full_name", "val"); -- cgit v1.2.3 From cb92c9ccc811a72b4e216f819be32b19748113c7 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 21:16:52 +0200 Subject: Add hkps support --- .../keychain/keyimport/HkpKeyserver.java | 46 +++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index fbe38c30a..71c251ddc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -81,6 +81,7 @@ public class HkpKeyserver extends Keyserver { private String mHost; private short mPort; + private boolean mSecure; /** * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% @@ -147,6 +148,7 @@ public class HkpKeyserver extends Keyserver { Pattern.CASE_INSENSITIVE); private static final short PORT_DEFAULT = 11371; + private static final short PORT_DEFAULT_HKPS = 443; /** * @param hostAndPort may be just @@ -157,19 +159,45 @@ public class HkpKeyserver extends Keyserver { public HkpKeyserver(String hostAndPort) { String host = hostAndPort; short port = PORT_DEFAULT; - final int colonPosition = hostAndPort.lastIndexOf(':'); - if (colonPosition > 0) { - host = hostAndPort.substring(0, colonPosition); - final String portStr = hostAndPort.substring(colonPosition + 1); - port = Short.decode(portStr); + boolean secure = false; + String[] parts = hostAndPort.split(":"); + if (parts.length > 1) { + if (!parts[0].contains(".")) { // This is not a domain or ip, so it must be a protocol name + if (parts[0].equalsIgnoreCase("hkps") || parts[0].equalsIgnoreCase("https")) { + secure = true; + port = PORT_DEFAULT_HKPS; + } else if (!parts[0].equalsIgnoreCase("hkp") && !parts[0].equalsIgnoreCase("http")) { + throw new IllegalArgumentException("Protocol " + parts[0] + " is unknown"); + } + host = parts[1]; + if (host.startsWith("//")) { // People tend to type https:// and hkps://, so we'll support that as well + host = host.substring(2); + } + if (parts.length > 2) { + port = Short.decode(parts[2]); + } + } else { + host = parts[0]; + port = Short.decode(parts[1]); + } } mHost = host; mPort = port; + mSecure = secure; } public HkpKeyserver(String host, short port) { + this(host, port, false); + } + + public HkpKeyserver(String host, short port, boolean secure) { mHost = host; mPort = port; + mSecure = secure; + } + + private String getUrlPrefix() { + return mSecure ? "https://" : "http://"; } private String query(String request) throws QueryFailedException, HttpError { @@ -181,7 +209,7 @@ public class HkpKeyserver extends Keyserver { } for (int i = 0; i < ips.length; ++i) { try { - String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; + String url = getUrlPrefix() + ips[i].getHostAddress() + ":" + mPort + request; Log.d(Constants.TAG, "hkp keyserver query: " + url); URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); @@ -244,7 +272,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.setOrigin("hkp:"+mHost+":"+mPort); + entry.setOrigin("hkp:" + mHost + ":" + mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); @@ -297,7 +325,7 @@ public class HkpKeyserver extends Keyserver { public String get(String keyIdHex) throws QueryFailedException { HttpClient client = new DefaultHttpClient(); try { - String query = "http://" + mHost + ":" + mPort + + String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/lookup?op=get&options=mr&search=" + keyIdHex; Log.d(Constants.TAG, "hkp keyserver get: " + query); HttpGet get = new HttpGet(query); @@ -326,7 +354,7 @@ public class HkpKeyserver extends Keyserver { public void add(String armoredKey) throws AddKeyException { HttpClient client = new DefaultHttpClient(); try { - String query = "http://" + mHost + ":" + mPort + "/pks/add"; + String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add"; HttpPost post = new HttpPost(query); Log.d(Constants.TAG, "hkp keyserver add: " + query); List nameValuePairs = new ArrayList(2); -- cgit v1.2.3 From c676e534799545f6aa95071463c10aa0b2f92b9d Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 28 May 2014 20:44:01 +0200 Subject: Fix url building to support certificate check on hkps servers Note: the CA used by sks-keyservers.net is not valid for android, thus using hkps fails for them. pgp.mit.edu uses a perfectly valid cert. --- .../keychain/keyimport/HkpKeyserver.java | 27 +++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 71c251ddc..b064fc5b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -201,15 +201,26 @@ public class HkpKeyserver extends Keyserver { } private String query(String request) throws QueryFailedException, HttpError { - InetAddress ips[]; - try { - ips = InetAddress.getAllByName(mHost); - } catch (UnknownHostException e) { - throw new QueryFailedException(e.toString()); + List urls = new ArrayList(); + if (mSecure) { + urls.add(getUrlPrefix() + mHost + ":" + mPort + request); + } else { + InetAddress ips[]; + try { + ips = InetAddress.getAllByName(mHost); + } catch (UnknownHostException e) { + throw new QueryFailedException(e.toString()); + } + for (InetAddress ip : ips) { + // Note: This is actually not HTTP 1.1 compliant, as we hide the real "Host" value, + // but Android's HTTPUrlConnection does not support any other way to set + // Socket's remote IP address... + urls.add(getUrlPrefix() + ip.getHostAddress() + ":" + mPort + request); + } } - for (int i = 0; i < ips.length; ++i) { + + for (String url : urls) { try { - String url = getUrlPrefix() + ips[i].getHostAddress() + ":" + mPort + request; Log.d(Constants.TAG, "hkp keyserver query: " + url); URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); @@ -272,7 +283,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.setOrigin("hkp:" + mHost + ":" + mPort); + entry.setOrigin(getUrlPrefix() + mHost + ":" + mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); -- cgit v1.2.3 From be490307f97b5e73bad5c4882afab6dff1f10b48 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:24:00 +0200 Subject: Download from origin during ACTION_DOWNLOAD_AND_IMPORT_KEYS --- .../sufficientlysecure/keychain/service/KeychainIntentService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 6f38418ff..b6ee234f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -796,10 +796,11 @@ public class KeychainIntentService extends IntentService ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); String keyServer = data.getString(DOWNLOAD_KEY_SERVER); - // this downloads the keys and places them into the ImportKeysListEntry entries - HkpKeyserver server = new HkpKeyserver(keyServer); - for (ImportKeysListEntry entry : entries) { + + // this downloads the keys and places them into the ImportKeysListEntry entries + HkpKeyserver server = new HkpKeyserver(entry.getOrigin() != null ? entry.getOrigin() : keyServer); + // if available use complete fingerprint for get request byte[] downloadedKeyBytes; if (entry.getFingerprintHex() != null) { -- cgit v1.2.3 From 518f3e1763ae1fad54a20f3cba6578e79adcdb63 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:33:15 +0200 Subject: Make abstract methods in Keyserver public (implementations make them public anyway) --- .../java/org/sufficientlysecure/keychain/keyimport/Keyserver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java index 868f543f0..842e7d922 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -48,12 +48,12 @@ public abstract class Keyserver { private static final long serialVersionUID = -507574859137295530L; } - abstract List search(String query) throws QueryFailedException, + public abstract List search(String query) throws QueryFailedException, QueryNeedsRepairException; - abstract String get(String keyIdHex) throws QueryFailedException; + public abstract String get(String keyIdHex) throws QueryFailedException; - abstract void add(String armoredKey) throws AddKeyException; + public abstract void add(String armoredKey) throws AddKeyException; public static String readAll(InputStream in, String encoding) throws IOException { ByteArrayOutputStream raw = new ByteArrayOutputStream(); -- cgit v1.2.3 From 3417a7a2de190efe5c5ea19581dd4f099453a99e Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:34:50 +0200 Subject: Store nice origin with keybase keys (that can't be interpreted as HKP server) --- .../org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 43c0b12fd..8054edd3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -31,6 +31,7 @@ import java.net.URLEncoder; import java.util.ArrayList; public class KeybaseKeyserver extends Keyserver { + public static final String ORIGIN = "keybase:keybase.io"; private String mQuery; @Override @@ -87,7 +88,7 @@ public class KeybaseKeyserver extends Keyserver { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(mQuery); - entry.setOrigin("keybase.io"); + entry.setOrigin(ORIGIN); String keybaseId = JWalk.getString(match, "components", "username", "val"); String fullName = JWalk.getString(match, "components", "full_name", "val"); -- cgit v1.2.3 From 34b97cb136376d14d4a1a48ccd89afa7d8abbb48 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 11:43:41 +0200 Subject: Merge ACTION_DOWNLOAD_AND_IMPORT_KEYS and ACTION_IMPORT_KEYBASE_KEYS --- .../keychain/service/KeychainIntentService.java | 69 +++++----------------- 1 file changed, 15 insertions(+), 54 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index b6ee234f4..2b8745f0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; @@ -742,68 +743,28 @@ public class KeychainIntentService extends IntentService } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { - ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); - - try { - KeybaseKeyserver server = new KeybaseKeyserver(); - for (ImportKeysListEntry entry : entries) { - // the keybase handle is in userId(1) - String keybaseId = entry.getExtraData(); - byte[] downloadedKeyBytes = server.get(keybaseId).getBytes(); - - // create PGPKeyRing object based on downloaded armored key - PGPKeyRing downloadedKey = null; - BufferedInputStream bufferedInput = - new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes)); - if (bufferedInput.available() > 0) { - InputStream in = PGPUtil.getDecoderStream(bufferedInput); - PGPObjectFactory objectFactory = new PGPObjectFactory(in); - - // get first object in block - Object obj; - if ((obj = objectFactory.nextObject()) != null) { - - if (obj instanceof PGPKeyRing) { - downloadedKey = (PGPKeyRing) obj; - } else { - throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); - } - } - } - - // save key bytes in entry object for doing the - // actual import afterwards - entry.setBytes(downloadedKey.getEncoded()); - } - - Intent importIntent = new Intent(this, KeychainIntentService.class); - importIntent.setAction(ACTION_IMPORT_KEYRING); - Bundle importData = new Bundle(); - importData.putParcelableArrayList(IMPORT_KEY_LIST, entries); - importIntent.putExtra(EXTRA_DATA, importData); - importIntent.putExtra(EXTRA_MESSENGER, mMessenger); - - // now import it with this service - onHandleIntent(importIntent); - - // result is handled in ACTION_IMPORT_KEYRING - } catch (Exception e) { - sendErrorToHandler(e); - } - } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) { + } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action) || ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { try { ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); String keyServer = data.getString(DOWNLOAD_KEY_SERVER); + // this downloads the keys and places them into the ImportKeysListEntry entries for (ImportKeysListEntry entry : entries) { - // this downloads the keys and places them into the ImportKeysListEntry entries - HkpKeyserver server = new HkpKeyserver(entry.getOrigin() != null ? entry.getOrigin() : keyServer); + Keyserver server; + if (entry.getOrigin() == null) { + server = new HkpKeyserver(keyServer); + } else if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) { + server = new KeybaseKeyserver(); + } else { + server = new HkpKeyserver(entry.getOrigin()); + } // if available use complete fingerprint for get request byte[] downloadedKeyBytes; - if (entry.getFingerprintHex() != null) { + if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) { + downloadedKeyBytes = server.get(entry.getExtraData()).getBytes(); + } else if (entry.getFingerprintHex() != null) { downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes(); } else { downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); @@ -833,7 +794,7 @@ public class KeychainIntentService extends IntentService if (entry.getFingerprintHex() != null) { String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex( downloadedKey.getPublicKey().getFingerprint()); - if (downloadedKeyFp.equals(entry.getFingerprintHex())) { + if (downloadedKeyFp.equalsIgnoreCase(entry.getFingerprintHex())) { Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " + "the requested fingerprint!"); } else { -- cgit v1.2.3 From 54b7b0e522b00cf6dcacc18bce1efaebb662119b Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Tue, 3 Jun 2014 11:30:22 -0700 Subject: fixed error message --- .../org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java | 3 ++- .../org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index f9b6abf18..145738e66 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -144,7 +144,8 @@ public class KeybaseKeyserver extends Keyserver { try { JSONObject json = new JSONObject(text); if (JWalk.getInt(json, "status", "code") != 0) { - throw new QueryFailedException("Keybase autocomplete search failed"); + throw new QueryFailedException("Keybase.io query failed: " + path + "?" + + query); } return json; } catch (JSONException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java index a996079c9..a639fe0e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java @@ -18,8 +18,8 @@ package org.sufficientlysecure.keychain.ui; import android.content.Context; -import android.support.v4.app.Fragment; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -31,9 +31,7 @@ import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Log; /** * Import public keys from the Keybase.io directory. First cut: just raw search. -- cgit v1.2.3 From cc2ef0c17ca1d032477eb21308c5ea677b1cc548 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 17:05:57 +0200 Subject: Store expired state within ImportKeysListEntry --- .../sufficientlysecure/keychain/keyimport/HkpKeyserver.java | 1 + .../keychain/keyimport/ImportKeysListEntry.java | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index b064fc5b1..d4f1af84f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -308,6 +308,7 @@ public class HkpKeyserver extends Keyserver { entry.setDate(tmpGreg.getTime()); entry.setRevoked(matcher.group(6).contains("r")); + entry.setExpired(matcher.group(6).contains("e")); ArrayList userIds = new ArrayList(); final String uidLines = matcher.group(7); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 8af41a8a3..ea4f5948e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -44,6 +44,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public long keyId; public String keyIdHex; public boolean revoked; + public boolean expired; public Date date; // TODO: not displayed public String fingerprintHex; public int bitStrength; @@ -68,6 +69,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeStringList(userIds); dest.writeLong(keyId); dest.writeByte((byte) (revoked ? 1 : 0)); + dest.writeByte((byte) (expired ? 1 : 0)); dest.writeSerializable(date); dest.writeString(fingerprintHex); dest.writeString(keyIdHex); @@ -89,6 +91,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { source.readStringList(vr.userIds); vr.keyId = source.readLong(); vr.revoked = source.readByte() == 1; + vr.expired = source.readByte() == 1; vr.date = (Date) source.readSerializable(); vr.fingerprintHex = source.readString(); vr.keyIdHex = source.readString(); @@ -129,6 +132,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mSelected = selected; } + public boolean isExpired() { + return expired; + } + + public void setExpired(boolean expired) { + this.expired = expired; + } + public long getKeyId() { return keyId; } -- cgit v1.2.3 From dd959876f4a1a7f26a3f7524e238416c1a30c7e5 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 17:55:24 +0200 Subject: First version of automatic contact discovery. TODO: - Configuration (much of it) - Enabled by default? - Which keys to import? Current state: All non-revoked and non-expired with matching userid - Search for keys if already known? Current state: yes, may cause traffic (configuration: only when wifi?) - Update interval: Currently Android handles it, might be good (causes automatic refresh on new contact and stuff like that) or bad (too many of refreshes) --- OpenKeychain/src/main/AndroidManifest.xml | 26 ++++ .../keychain/KeychainApplication.java | 13 ++ .../keychain/helper/EmailKeyHelper.java | 97 +++++++++++++++ .../service/ContactSyncAdapterService.java | 70 +++++++++++ .../keychain/service/DummyAccountService.java | 131 +++++++++++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 2 + OpenKeychain/src/main/res/xml/account_desc.xml | 6 + .../main/res/xml/custom_pgp_contacts_structure.xml | 7 ++ .../src/main/res/xml/sync_adapter_desc.xml | 6 + 9 files changed, 358 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java create mode 100644 OpenKeychain/src/main/res/xml/account_desc.xml create mode 100644 OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml create mode 100644 OpenKeychain/src/main/res/xml/sync_adapter_desc.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index fd26d6acf..9a2011205 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -53,6 +53,10 @@ + + + + @@ -435,6 +439,28 @@ + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index f911318a0..3ac3a9dee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.graphics.PorterDuff; @@ -76,6 +78,17 @@ public class KeychainApplication extends Application { brandGlowEffect(getApplicationContext(), getApplicationContext().getResources().getColor(R.color.emphasis)); + + setupAccountAsNeeded(); + } + + private void setupAccountAsNeeded() { + AccountManager manager = AccountManager.get(this); + Account[] accounts = manager.getAccountsByType(getPackageName()); + if (accounts == null || accounts.length == 0) { + Account dummy = new Account(getString(R.string.app_name), getPackageName()); + manager.addAccountExplicitly(dummy, null, null); + } } static void brandGlowEffect(Context context, int brandColor) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java new file mode 100644 index 000000000..80f52f914 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.helper; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Messenger; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.service.KeychainIntentService; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class EmailKeyHelper { + + public static void importContacts(Context context, Messenger messenger) { + importAll(context, messenger, ContactHelper.getContactMails(context)); + } + + public static void importAll(Context context, Messenger messenger, List mails) { + Set keys = new HashSet(); + for (String mail : mails) { + keys.addAll(getEmailKeys(context, mail)); + } + importKeys(context, messenger, new ArrayList(keys)); + } + + public static List getEmailKeys(Context context, String mail) { + Set keys = new HashSet(); + + // Try _hkp._tcp SRV record first + String[] mailparts = mail.split("@"); + if (mailparts.length == 2) { + HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]); + if (hkp != null) { + keys.addAll(getEmailKeys(mail, hkp)); + } + } + + // Most users don't have the SRV record, so ask a default server as well + String[] servers = Preferences.getPreferences(context).getKeyServers(); + if (servers != null && servers.length != 0) { + HkpKeyserver hkp = new HkpKeyserver(servers[0]); + keys.addAll(getEmailKeys(mail, hkp)); + } + return new ArrayList(keys); + } + + private static void importKeys(Context context, Messenger messenger, List keys) { + Intent importIntent = new Intent(context, KeychainIntentService.class); + importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS); + Bundle importData = new Bundle(); + importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, + new ArrayList(keys)); + importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData); + importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + context.startService(importIntent); + } + + public static List getEmailKeys(String mail, Keyserver keyServer) { + Set keys = new HashSet(); + try { + for (ImportKeysListEntry key : keyServer.search(mail)) { + if (key.isRevoked() || key.isExpired()) continue; + for (String userId : key.getUserIds()) { + if (userId.toLowerCase().contains(mail.toLowerCase())) { + keys.add(key); + } + } + } + } catch (Keyserver.QueryFailedException ignored) { + } catch (Keyserver.QueryNeedsRepairException ignored) { + } + return new ArrayList(keys); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java new file mode 100644 index 000000000..4d0397196 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.service; + +import android.accounts.Account; +import android.app.Service; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.Intent; +import android.content.SyncResult; +import android.os.*; +import org.sufficientlysecure.keychain.helper.EmailKeyHelper; +import org.sufficientlysecure.keychain.util.Log; + +public class ContactSyncAdapterService extends Service { + + private class ContactSyncAdapter extends AbstractThreadedSyncAdapter { + + public ContactSyncAdapter() { + super(ContactSyncAdapterService.this, true); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, + final SyncResult syncResult) { + EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), + new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Bundle data = msg.getData(); + switch (msg.arg1) { + case KeychainIntentServiceHandler.MESSAGE_OKAY: + return true; + case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: + if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && + data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { + Log.d("Keychain/ContactSync/DownloadKeys", "Progress: " + + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); + return false; + } + default: + Log.d("Keychain/ContactSync/DownloadKeys", "Syncing... " + msg.toString()); + return false; + } + } + }))); + } + } + + @Override + public IBinder onBind(Intent intent) { + return new ContactSyncAdapter().getSyncAdapterBinder(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java new file mode 100644 index 000000000..d3b29d5cf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.service; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.widget.Toast; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; + +/** + * This service actually does nothing, it's sole task is to show a Toast if the use tries to create an account. + */ +public class DummyAccountService extends Service { + + private class Toaster { + private static final String TOAST_MESSAGE = "toast_message"; + private Context context; + private Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Toast.makeText(context, msg.getData().getString(TOAST_MESSAGE), Toast.LENGTH_LONG).show(); + return true; + } + }); + + private Toaster(Context context) { + this.context = context; + } + + public void toast(int resourceId) { + toast(context.getString(resourceId)); + } + + public void toast(String message) { + Message msg = new Message(); + Bundle bundle = new Bundle(); + bundle.putString(TOAST_MESSAGE, message); + msg.setData(bundle); + handler.sendMessage(msg); + } + } + + private class Authenticator extends AbstractAccountAuthenticator { + + public Authenticator() { + super(DummyAccountService.this); + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + Log.d("DummyAccountService", "editProperties"); + return null; + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) throws NetworkErrorException { + response.onResult(new Bundle()); + toaster.toast(R.string.info_no_manual_account_creation); + Log.d("DummyAccountService", "addAccount"); + return null; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) + throws NetworkErrorException { + Log.d("DummyAccountService", "confirmCredentials"); + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, + Bundle options) throws NetworkErrorException { + Log.d("DummyAccountService", "getAuthToken"); + return null; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + Log.d("DummyAccountService", "getAuthTokenLabel"); + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, + Bundle options) throws NetworkErrorException { + Log.d("DummyAccountService", "updateCredentials"); + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) + throws NetworkErrorException { + Log.d("DummyAccountService", "hasFeatures"); + return null; + } + } + + private Toaster toaster; + + @Override + public IBinder onBind(Intent intent) { + toaster = new Toaster(this); + return new Authenticator().getIBinder(); + } +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 1ba8a6d2d..70b8616d4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -518,5 +518,7 @@ unknown cannot sign No encryption subkey available! + Do not create OpenKeychain-Accounts manually. + For more information, see Help. diff --git a/OpenKeychain/src/main/res/xml/account_desc.xml b/OpenKeychain/src/main/res/xml/account_desc.xml new file mode 100644 index 000000000..94ffdf40b --- /dev/null +++ b/OpenKeychain/src/main/res/xml/account_desc.xml @@ -0,0 +1,6 @@ + + + diff --git a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml new file mode 100644 index 000000000..3318f3b45 --- /dev/null +++ b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml b/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml new file mode 100644 index 000000000..d8fe60e91 --- /dev/null +++ b/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file -- cgit v1.2.3 From 6a637462782b4ce57ecf154edf0974114181b8ad Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 18:07:28 +0200 Subject: Fix regex for hkp parsing to support multiple uids --- .../java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index d4f1af84f..2ec9e1c07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -116,7 +116,7 @@ public class HkpKeyserver extends Keyserver { */ public static final Pattern PUB_KEY_LINE = Pattern .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line - + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines + + "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines Pattern.CASE_INSENSITIVE); /** @@ -144,7 +144,7 @@ public class HkpKeyserver extends Keyserver { * */ public static final Pattern UID_LINE = Pattern - .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)", + .compile("uid:([^:]*):([0-9]+):([0-9]*):([rde]*)", Pattern.CASE_INSENSITIVE); private static final short PORT_DEFAULT = 11371; -- cgit v1.2.3 From dc1e26f39c9c7fa88dd28d2920a2919f83e0575c Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 00:59:39 +0200 Subject: Make keylist case insensitive You want "michael" to be next to "Michael", don't you? --- .../java/org/sufficientlysecure/keychain/ui/KeyListFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 9c90b5eb7..d5a753133 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -253,7 +253,7 @@ public class KeyListFragment extends LoaderFragment static final int INDEX_HAS_ANY_SECRET = 6; static final String ORDER = - KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " ASC"; + KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; @Override @@ -593,7 +593,7 @@ public class KeyListFragment extends LoaderFragment String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID); String headerText = convertView.getResources().getString(R.string.user_id_no_name); if (userId != null && userId.length() > 0) { - headerText = "" + userId.subSequence(0, 1).charAt(0); + headerText = "" + userId.charAt(0); } holder.mText.setText(headerText); holder.mCount.setVisibility(View.GONE); @@ -622,7 +622,7 @@ public class KeyListFragment extends LoaderFragment // otherwise, return the first character of the name as ID String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID); if (userId != null && userId.length() > 0) { - return userId.charAt(0); + return Character.toUpperCase(userId.charAt(0)); } else { return Long.MAX_VALUE; } -- cgit v1.2.3 From 781197021875d06e22ff28fb7983391b36910c5b Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Thu, 5 Jun 2014 08:51:55 -0700 Subject: Don't show full fingerprint in key search results --- .../sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 114c6afae..02ddbb45f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -149,12 +149,8 @@ public class ImportKeysAdapter extends ArrayAdapter { holder.keyId.setText(entry.keyIdHex); - if (entry.fingerprintHex != null) { - holder.fingerprint.setVisibility(View.VISIBLE); - holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerprintHex)); - } else { - holder.fingerprint.setVisibility(View.GONE); - } + // don't show full fingerprint on key import + holder.fingerprint.setVisibility(View.GONE); if (entry.bitStrength != 0 && entry.algorithm != null) { holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); -- cgit v1.2.3 From 5466adee410280af390023b163ee512d98be0601 Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Thu, 5 Jun 2014 11:56:38 -0700 Subject: Clean up keyimport.ImportKeysListEntry --- .../keychain/keyimport/ImportKeysListEntry.java | 116 ++++++++++----------- .../keychain/ui/adapter/ImportKeysAdapter.java | 17 ++- 2 files changed, 66 insertions(+), 67 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index c43f72235..c64794bb6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -32,16 +32,16 @@ import java.util.Date; public class ImportKeysListEntry implements Serializable, Parcelable { private static final long serialVersionUID = -7797972103284992662L; - public ArrayList userIds; - public long keyId; - public String keyIdHex; - public boolean revoked; - public Date date; // TODO: not displayed - public String fingerprintHex; - public int bitStrength; - public String algorithm; - public boolean secretKey; - public String mPrimaryUserId; + private ArrayList mUserIds; + private long mKeyId; + private String mKeyIdHex; + private boolean mRevoked; + private Date mDate; // TODO: not displayed + private String mFingerprintHex; + private int mBitStrength; + private String mAlgorithm; + private boolean mSecretKey; + private String mPrimaryUserId; private String mExtraData; private String mQuery; @@ -54,15 +54,15 @@ public class ImportKeysListEntry implements Serializable, Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPrimaryUserId); - dest.writeStringList(userIds); - dest.writeLong(keyId); - dest.writeByte((byte) (revoked ? 1 : 0)); - dest.writeSerializable(date); - dest.writeString(fingerprintHex); - dest.writeString(keyIdHex); - dest.writeInt(bitStrength); - dest.writeString(algorithm); - dest.writeByte((byte) (secretKey ? 1 : 0)); + dest.writeStringList(mUserIds); + dest.writeLong(mKeyId); + dest.writeByte((byte) (mRevoked ? 1 : 0)); + dest.writeSerializable(mDate); + dest.writeString(mFingerprintHex); + dest.writeString(mKeyIdHex); + dest.writeInt(mBitStrength); + dest.writeString(mAlgorithm); + dest.writeByte((byte) (mSecretKey ? 1 : 0)); dest.writeByte((byte) (mSelected ? 1 : 0)); dest.writeString(mExtraData); } @@ -71,16 +71,16 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public ImportKeysListEntry createFromParcel(final Parcel source) { ImportKeysListEntry vr = new ImportKeysListEntry(); vr.mPrimaryUserId = source.readString(); - vr.userIds = new ArrayList(); - source.readStringList(vr.userIds); - vr.keyId = source.readLong(); - vr.revoked = source.readByte() == 1; - vr.date = (Date) source.readSerializable(); - vr.fingerprintHex = source.readString(); - vr.keyIdHex = source.readString(); - vr.bitStrength = source.readInt(); - vr.algorithm = source.readString(); - vr.secretKey = source.readByte() == 1; + vr.mUserIds = new ArrayList(); + source.readStringList(vr.mUserIds); + vr.mKeyId = source.readLong(); + vr.mRevoked = source.readByte() == 1; + vr.mDate = (Date) source.readSerializable(); + vr.mFingerprintHex = source.readString(); + vr.mKeyIdHex = source.readString(); + vr.mBitStrength = source.readInt(); + vr.mAlgorithm = source.readString(); + vr.mSecretKey = source.readByte() == 1; vr.mSelected = source.readByte() == 1; vr.mExtraData = source.readString(); @@ -93,7 +93,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { }; public String getKeyIdHex() { - return keyIdHex; + return mKeyIdHex; } public boolean isSelected() { @@ -105,71 +105,71 @@ public class ImportKeysListEntry implements Serializable, Parcelable { } public long getKeyId() { - return keyId; + return mKeyId; } public void setKeyId(long keyId) { - this.keyId = keyId; + this.mKeyId = keyId; } public void setKeyIdHex(String keyIdHex) { - this.keyIdHex = keyIdHex; + this.mKeyIdHex = keyIdHex; } public boolean isRevoked() { - return revoked; + return mRevoked; } public void setRevoked(boolean revoked) { - this.revoked = revoked; + this.mRevoked = revoked; } public Date getDate() { - return date; + return mDate; } public void setDate(Date date) { - this.date = date; + this.mDate = date; } public String getFingerprintHex() { - return fingerprintHex; + return mFingerprintHex; } public void setFingerprintHex(String fingerprintHex) { - this.fingerprintHex = fingerprintHex; + this.mFingerprintHex = fingerprintHex; } public int getBitStrength() { - return bitStrength; + return mBitStrength; } public void setBitStrength(int bitStrength) { - this.bitStrength = bitStrength; + this.mBitStrength = bitStrength; } public String getAlgorithm() { - return algorithm; + return mAlgorithm; } public void setAlgorithm(String algorithm) { - this.algorithm = algorithm; + this.mAlgorithm = algorithm; } public boolean isSecretKey() { - return secretKey; + return mSecretKey; } public void setSecretKey(boolean secretKey) { - this.secretKey = secretKey; + this.mSecretKey = secretKey; } public ArrayList getUserIds() { - return userIds; + return mUserIds; } public void setUserIds(ArrayList userIds) { - this.userIds = userIds; + this.mUserIds = userIds; } public String getPrimaryUserId() { @@ -201,10 +201,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable { */ public ImportKeysListEntry() { // keys from keyserver are always public keys; from keybase too - secretKey = false; + mSecretKey = false; // do not select by default mSelected = false; - userIds = new ArrayList(); + mUserIds = new ArrayList(); } /** @@ -215,24 +215,24 @@ public class ImportKeysListEntry implements Serializable, Parcelable { // selected is default this.mSelected = true; - secretKey = ring.isSecret(); + mSecretKey = ring.isSecret(); UncachedPublicKey key = ring.getPublicKey(); mPrimaryUserId = key.getPrimaryUserId(); - userIds = key.getUnorderedUserIds(); + mUserIds = key.getUnorderedUserIds(); // if there was no user id flagged as primary, use the first one if (mPrimaryUserId == null) { - mPrimaryUserId = userIds.get(0); + mPrimaryUserId = mUserIds.get(0); } - this.keyId = key.getKeyId(); - this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId); + this.mKeyId = key.getKeyId(); + this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); - this.revoked = key.maybeRevoked(); - this.fingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); - this.bitStrength = key.getBitStrength(); + this.mRevoked = key.maybeRevoked(); + this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); + this.mBitStrength = key.getBitStrength(); final int algorithm = key.getAlgorithm(); - this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); + this.mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 041fc8040..233b1fca8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -33,7 +33,6 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Highlighter; import java.util.ArrayList; @@ -120,13 +119,13 @@ public class ImportKeysAdapter extends ArrayAdapter { } // main user id - String userId = entry.userIds.get(0); + String userId = entry.getUserIds().get(0); String[] userIdSplit = KeyRing.splitUserId(userId); // name if (userIdSplit[0] != null) { // show red user id if it is a secret key - if (entry.secretKey) { + if (entry.isSecretKey()) { holder.mainUserId.setText(mActivity.getString(R.string.secret_key) + " " + userIdSplit[0]); holder.mainUserId.setTextColor(Color.RED); @@ -147,26 +146,26 @@ public class ImportKeysAdapter extends ArrayAdapter { holder.mainUserIdRest.setVisibility(View.GONE); } - holder.keyId.setText(entry.keyIdHex); + holder.keyId.setText(entry.getKeyIdHex()); // don't show full fingerprint on key import holder.fingerprint.setVisibility(View.GONE); - if (entry.bitStrength != 0 && entry.algorithm != null) { - holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); + if (entry.getBitStrength() != 0 && entry.getAlgorithm() != null) { + holder.algorithm.setText("" + entry.getBitStrength() + "/" + entry.getAlgorithm()); holder.algorithm.setVisibility(View.VISIBLE); } else { holder.algorithm.setVisibility(View.INVISIBLE); } - if (entry.revoked) { + if (entry.isRevoked()) { holder.status.setVisibility(View.VISIBLE); holder.status.setText(R.string.revoked); } else { holder.status.setVisibility(View.GONE); } - if (entry.userIds.size() == 1) { + if (entry.getUserIds().size() == 1) { holder.userIdsList.setVisibility(View.GONE); } else { holder.userIdsList.setVisibility(View.VISIBLE); @@ -174,7 +173,7 @@ public class ImportKeysAdapter extends ArrayAdapter { // clear view from holder holder.userIdsList.removeAllViews(); - Iterator it = entry.userIds.iterator(); + Iterator it = entry.getUserIds().iterator(); // skip primary user id it.next(); while (it.hasNext()) { -- cgit v1.2.3 From 80e99986401b635f4eeef5d13740911d10740aef Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 23:22:21 +0200 Subject: Show keys with android contacts This means to sync userid + keyid into contact storage. Android will merge them to normal contacts based on primary userid. --- OpenKeychain/src/main/AndroidManifest.xml | 6 ++ .../org/sufficientlysecure/keychain/Constants.java | 2 + .../keychain/KeychainApplication.java | 11 +-- .../keychain/helper/ContactHelper.java | 89 +++++++++++++++++++++- .../service/ContactSyncAdapterService.java | 4 + .../keychain/ui/ViewKeyActivity.java | 13 +++- OpenKeychain/src/main/res/values/strings.xml | 1 + .../main/res/xml/custom_pgp_contacts_structure.xml | 4 +- 8 files changed, 119 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 9a2011205..31c809334 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -58,6 +58,7 @@ + + + + + + getMailAccounts(Context context) { final Account[] accounts = AccountManager.get(context).getAccounts(); final Set emailSet = new HashSet(); @@ -60,4 +75,74 @@ public class ContactHelper { mailCursor.close(); return new ArrayList(mails); } + + public static Uri dataUriFromContactUri(Context context, Uri contactUri) { + Cursor contactMasterKey = context.getContentResolver().query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null, null); + if (contactMasterKey != null) { + if (contactMasterKey.moveToNext()) { + return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0)); + } + contactMasterKey.close(); + } + return null; + } + + public static void writeKeysToContacts(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, + null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + String[] userId = PgpKeyHelper.splitUserId(cursor.getString(0)); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)); + String keyIdShort = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(2)); + long masterKeyId = cursor.getLong(3); + int rawContactId = -1; + Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, RAW_CONTACT_ID_PROJECTION, + FIND_RAW_CONTACT_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null); + if (raw != null) { + if (raw.moveToNext()) { + rawContactId = raw.getInt(0); + } + raw.close(); + } + ArrayList ops = new ArrayList(); + if (rawContactId == -1) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name)) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME) + .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint) + .build()); + if (userId[0] != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, userId[0]) + .build()); + } + if (userId[1] != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId[1]) + .build()); + } + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) + .withValue(ContactsContract.Data.DATA1, String.format(context.getString(R.string.contact_show_key), keyIdShort)) + .withValue(ContactsContract.Data.DATA2, masterKeyId) + .build()); + } + try { + resolver.applyBatch(ContactsContract.AUTHORITY, ops); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + } + cursor.close(); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 4d0397196..a52dbfa27 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -24,6 +24,8 @@ import android.content.ContentProviderClient; import android.content.Intent; import android.content.SyncResult; import android.os.*; +import org.sufficientlysecure.keychain.KeychainApplication; +import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper; import org.sufficientlysecure.keychain.util.Log; @@ -60,6 +62,8 @@ public class ContactSyncAdapterService extends Service { } } }))); + KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); + ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index bed116f5f..010144851 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -32,6 +33,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -47,6 +49,7 @@ import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -125,7 +128,7 @@ public class ViewKeyActivity extends ActionBarActivity implements switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); } - Uri dataUri = getIntent().getData(); + Uri dataUri = getDataUri(); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); finish(); @@ -163,6 +166,14 @@ public class ViewKeyActivity extends ActionBarActivity implements mViewPager.setCurrentItem(switchToTab); } + private Uri getDataUri() { + Uri dataUri = getIntent().getData(); + if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) { + dataUri = ContactHelper.dataUriFromContactUri(this, dataUri); + } + return dataUri; + } + private void loadData(Uri dataUri) { mDataUri = dataUri; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 09f76c675..ec6b389ed 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -521,5 +521,6 @@ No encryption subkey available! Do not create OpenKeychain-Accounts manually. For more information, see Help. + Show key (%s) diff --git a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml index 3318f3b45..5f5f2be80 100644 --- a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml +++ b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml @@ -1,7 +1,5 @@ + android:detailColumn="data1"/> \ No newline at end of file -- cgit v1.2.3 From 36312b950aa2e3150ec9381c91850c63ed1c604b Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 23:46:14 +0200 Subject: Add dnsjava as submodule --- .gitmodules | 3 +++ OpenKeychain/build.gradle | 2 +- extern/dnsjava | 1 + settings.gradle | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) create mode 160000 extern/dnsjava diff --git a/.gitmodules b/.gitmodules index 572293f94..20a0f60e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "extern/openkeychain-api-lib"] path = extern/openkeychain-api-lib url = https://github.com/open-keychain/openkeychain-api-lib.git +[submodule "extern/dnsjava"] + path = extern/dnsjava + url = https://github.com/open-keychain/dnsjava.git diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 374a63b28..20e5ca594 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -14,7 +14,6 @@ dependencies { compile 'com.android.support:support-v4:19.1.0' compile 'com.android.support:appcompat-v7:19.1.0' - compile 'dnsjava:dnsjava:2.1.1' compile project(':extern:openpgp-api-lib') compile project(':extern:openkeychain-api-lib') compile project(':extern:html-textview') @@ -27,6 +26,7 @@ dependencies { compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:prov') compile project(':extern:AppMsg:library') + compile project(':extern:dnsjava') // Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well androidTestCompile 'junit:junit:4.10' diff --git a/extern/dnsjava b/extern/dnsjava new file mode 160000 index 000000000..71c8a9e56 --- /dev/null +++ b/extern/dnsjava @@ -0,0 +1 @@ +Subproject commit 71c8a9e56b19b34907e7e2e810ca15b57e3edc2b diff --git a/settings.gradle b/settings.gradle index b0162875a..d7ca15f82 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,3 +11,4 @@ include ':extern:spongycastle:pg' include ':extern:spongycastle:pkix' include ':extern:spongycastle:prov' include ':extern:AppMsg:library' +include ':extern:dnsjava' -- cgit v1.2.3 From 9d02bc85e2c3fffbb18c4bbd8889db827ac2e8f0 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 00:51:24 +0200 Subject: Fix compile error introduced during merge --- .../java/org/sufficientlysecure/keychain/helper/ContactHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index 4b85d7e80..f50ccf6f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract; import android.util.Patterns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; @@ -93,7 +94,7 @@ public class ContactHelper { null, null, null); if (cursor != null) { while (cursor.moveToNext()) { - String[] userId = PgpKeyHelper.splitUserId(cursor.getString(0)); + String[] userId = KeyRing.splitUserId(cursor.getString(0)); String fingerprint = PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)); String keyIdShort = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(2)); long masterKeyId = cursor.getLong(3); -- cgit v1.2.3 From 55ca0841f6ac9be91fa185442a5c11cebc016441 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 17:39:11 +0200 Subject: Fixing TAG and string resource --- .../keychain/service/ContactSyncAdapterService.java | 5 +++-- OpenKeychain/src/main/res/values/strings.xml | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index a52dbfa27..8db9294df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -24,6 +24,7 @@ import android.content.ContentProviderClient; import android.content.Intent; import android.content.SyncResult; import android.os.*; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper; @@ -51,13 +52,13 @@ public class ContactSyncAdapterService extends Service { case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { - Log.d("Keychain/ContactSync/DownloadKeys", "Progress: " + + Log.d(Constants.TAG, "Progress: " + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); return false; } default: - Log.d("Keychain/ContactSync/DownloadKeys", "Syncing... " + msg.toString()); + Log.d(Constants.TAG, "Syncing... " + msg.toString()); return false; } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index ec6b389ed..9545c1ed4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -519,8 +519,7 @@ cannot sign Encoding error No encryption subkey available! - Do not create OpenKeychain-Accounts manually. - For more information, see Help. + Do not create OpenKeychain-Accounts manually.\nFor more information, see Help. Show key (%s) -- cgit v1.2.3 From 5601f1b76f42276950487d635c9ea87006f15621 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 17:42:28 +0200 Subject: Fix TAG in account service as well --- .../keychain/service/DummyAccountService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java index d3b29d5cf..008502ce7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; @@ -73,7 +74,7 @@ public class DummyAccountService extends Service { @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - Log.d("DummyAccountService", "editProperties"); + Log.d(Constants.TAG, "DummyAccountService.editProperties"); return null; } @@ -82,41 +83,41 @@ public class DummyAccountService extends Service { String[] requiredFeatures, Bundle options) throws NetworkErrorException { response.onResult(new Bundle()); toaster.toast(R.string.info_no_manual_account_creation); - Log.d("DummyAccountService", "addAccount"); + Log.d(Constants.TAG, "DummyAccountService.addAccount"); return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "confirmCredentials"); + Log.d(Constants.TAG, "DummyAccountService.confirmCredentials"); return null; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "getAuthToken"); + Log.d(Constants.TAG, "DummyAccountService.getAuthToken"); return null; } @Override public String getAuthTokenLabel(String authTokenType) { - Log.d("DummyAccountService", "getAuthTokenLabel"); + Log.d(Constants.TAG, "DummyAccountService.getAuthTokenLabel"); return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "updateCredentials"); + Log.d(Constants.TAG, "DummyAccountService.updateCredentials"); return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - Log.d("DummyAccountService", "hasFeatures"); + Log.d(Constants.TAG, "DummyAccountService.hasFeatures"); return null; } } -- cgit v1.2.3 From 341247d4467997aa22850f5b82ac906bb28296cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 6 Jun 2014 22:46:39 +0200 Subject: Hide subkeys, certs tabs in key view, checkable menu item to show/hide them --- .../keychain/ui/ViewKeyActivity.java | 87 +++++++++++++++++++--- .../keychain/ui/adapter/PagerTabStripAdapter.java | 10 +++ OpenKeychain/src/main/res/menu/key_view.xml | 6 ++ OpenKeychain/src/main/res/values/strings.xml | 1 + 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index f27a4ad27..b396cd849 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -92,6 +92,8 @@ public class ViewKeyActivity extends ActionBarActivity implements private static final int LOADER_ID_UNIFIED = 0; + private boolean mShowAdvancedTabs; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -116,9 +118,6 @@ public class ViewKeyActivity extends ActionBarActivity implements mViewPager = (ViewPager) findViewById(R.id.view_key_pager); mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout); - mTabsAdapter = new PagerTabStripAdapter(this); - mViewPager.setAdapter(mTabsAdapter); - int switchToTab = TAB_MAIN; Intent intent = getIntent(); if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { @@ -136,6 +135,18 @@ public class ViewKeyActivity extends ActionBarActivity implements initNfc(dataUri); + mShowAdvancedTabs = false; + + initTabs(dataUri); + + // switch to tab selected by extra + mViewPager.setCurrentItem(switchToTab); + } + + private void initTabs(Uri dataUri) { + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); + Bundle mainBundle = new Bundle(); mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); mTabsAdapter.addTab(ViewKeyMainFragment.class, @@ -146,6 +157,11 @@ public class ViewKeyActivity extends ActionBarActivity implements mTabsAdapter.addTab(ViewKeyShareFragment.class, mainBundle, getString(R.string.key_view_tab_share)); + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); + } + + private void addAdvancedTabs(Uri dataUri) { Bundle keyDetailsBundle = new Bundle(); keyDetailsBundle.putParcelable(ViewKeyKeysFragment.ARG_DATA_URI, dataUri); mTabsAdapter.addTab(ViewKeyKeysFragment.class, @@ -156,11 +172,46 @@ public class ViewKeyActivity extends ActionBarActivity implements mTabsAdapter.addTab(ViewKeyCertsFragment.class, certBundle, getString(R.string.key_view_tab_certs)); - // NOTE: must be after adding the tabs! + // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); + } - // switch to tab selected by extra - mViewPager.setCurrentItem(switchToTab); + private void removeAdvancedTabs() { + // before removing, switch to the first tab if necessary + if (mViewPager.getCurrentItem() >= TAB_KEYS) { + // remove _after_ switching to the main tab + mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + } + + @Override + public void onPageScrollStateChanged(int state) { + if (ViewPager.SCROLL_STATE_SETTLING == state) { + mTabsAdapter.removeTab(TAB_CERTS); + mTabsAdapter.removeTab(TAB_KEYS); + + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); + + // remove this listener again +// mViewPager.setOnPageChangeListener(null); + } + } + }); + + mViewPager.setCurrentItem(TAB_MAIN); + } else { + mTabsAdapter.removeTab(TAB_CERTS); + mTabsAdapter.removeTab(TAB_KEYS); + } + + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); } private void loadData(Uri dataUri) { @@ -177,6 +228,9 @@ public class ViewKeyActivity extends ActionBarActivity implements public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.key_view, menu); + + MenuItem showAdvancedInfoItem = menu.findItem(R.id.menu_key_view_advanced); + showAdvancedInfoItem.setChecked(mShowAdvancedTabs); return true; } @@ -184,24 +238,37 @@ public class ViewKeyActivity extends ActionBarActivity implements public boolean onOptionsItemSelected(MenuItem item) { try { switch (item.getItemId()) { - case android.R.id.home: + case android.R.id.home: { Intent homeIntent = new Intent(this, KeyListActivity.class); homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(homeIntent); return true; - case R.id.menu_key_view_update: + } + case R.id.menu_key_view_update: { updateFromKeyserver(mDataUri, mProviderHelper); return true; - case R.id.menu_key_view_export_keyserver: + } + case R.id.menu_key_view_export_keyserver: { uploadToKeyserver(mDataUri); return true; - case R.id.menu_key_view_export_file: + } + case R.id.menu_key_view_export_file: { exportToFile(mDataUri, mExportHelper, mProviderHelper); return true; + } case R.id.menu_key_view_delete: { deleteKey(mDataUri, mExportHelper); return true; } + case R.id.menu_key_view_advanced: { + mShowAdvancedTabs = !mShowAdvancedTabs; + item.setChecked(mShowAdvancedTabs); + if (mShowAdvancedTabs) { + addAdvancedTabs(mDataUri); + } else { + removeAdvancedTabs(); + } + } } } catch (ProviderHelper.NotFoundException e) { AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java index 977740567..3e3098b10 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java @@ -20,8 +20,13 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBarActivity; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.Constants; import java.util.ArrayList; @@ -52,6 +57,11 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter { notifyDataSetChanged(); } + public void removeTab(int index) { + mTabs.remove(index); + notifyDataSetChanged(); + } + @Override public int getCount() { return mTabs.size(); diff --git a/OpenKeychain/src/main/res/menu/key_view.xml b/OpenKeychain/src/main/res/menu/key_view.xml index 864016801..64877d725 100644 --- a/OpenKeychain/src/main/res/menu/key_view.xml +++ b/OpenKeychain/src/main/res/menu/key_view.xml @@ -31,4 +31,10 @@ app:showAsAction="never" android:title="@string/menu_delete_key" /> + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index eeb6b3742..82095a611 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ Select all Add keys Export all keys + Show advanced info Sign -- cgit v1.2.3 From d2430fe0e37f5996e3dbe758ea8c00ebde61445e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 6 Jun 2014 22:47:28 +0200 Subject: Move SlidingTabLayout and SlidingTabStrip into appropriate subpackage --- .../keychain/ui/HelpActivity.java | 2 +- .../keychain/ui/ViewKeyActivity.java | 2 +- .../keychain/ui/widget/SlidingTabLayout.java | 318 +++++++++++++++++++++ .../keychain/ui/widget/SlidingTabStrip.java | 211 ++++++++++++++ .../keychain/util/SlidingTabLayout.java | 318 --------------------- .../keychain/util/SlidingTabStrip.java | 211 -------------- OpenKeychain/src/main/res/layout/help_activity.xml | 2 +- .../src/main/res/layout/view_key_activity.xml | 2 +- 8 files changed, 533 insertions(+), 533 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabStrip.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index 9edaff19a..bbc1e4b1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -25,7 +25,7 @@ import android.support.v7.app.ActionBarActivity; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; -import org.sufficientlysecure.keychain.util.SlidingTabLayout; +import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; public class HelpActivity extends ActionBarActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index b396cd849..7a0e83d9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -54,7 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.SlidingTabLayout; +import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import java.util.Date; import java.util.HashMap; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java new file mode 100644 index 000000000..17471c86c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabLayout.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.TextView; + +/** + * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html + */ + +/** + * To be used with ViewPager to provide a tab indicator component which give constant feedback as to + * the user's scroll progress. + *

+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. + *

+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors + * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The + * alternative is via the {@link TabColorizer} interface which provides you complete control over + * which color is used for any individual position. + *

+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, + * providing the layout ID of your custom layout. + */ +public class SlidingTabLayout extends HorizontalScrollView { + + /** + * Allows complete control over the colors drawn in the tab layout. Set with + * {@link #setCustomTabColorizer(TabColorizer)}. + */ + public interface TabColorizer { + + /** + * @return return the color of the indicator used when {@code position} is selected. + */ + int getIndicatorColor(int position); + + /** + * @return return the color of the divider drawn to the right of {@code position}. + */ + int getDividerColor(int position); + + } + + private static final int TITLE_OFFSET_DIPS = 24; + private static final int TAB_VIEW_PADDING_DIPS = 16; + private static final int TAB_VIEW_TEXT_SIZE_SP = 12; + + private int mTitleOffset; + + private int mTabViewLayoutId; + private int mTabViewTextViewId; + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; + + private final SlidingTabStrip mTabStrip; + + public SlidingTabLayout(Context context) { + this(context, null); + } + + public SlidingTabLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Disable the Scroll Bar + setHorizontalScrollBarEnabled(false); + // Make sure that the Tab Strips fills this View + setFillViewport(true); + + mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); + + mTabStrip = new SlidingTabStrip(context); + addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + /** + * Set the custom {@link TabColorizer} to be used. + *

+ * If you only require simple custmisation then you can use + * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve + * similar effects. + */ + public void setCustomTabColorizer(TabColorizer tabColorizer) { + mTabStrip.setCustomTabColorizer(tabColorizer); + } + + /** + * Sets the colors to be used for indicating the selected tab. These colors are treated as a + * circular array. Providing one color will mean that all tabs are indicated with the same color. + */ + public void setSelectedIndicatorColors(int... colors) { + mTabStrip.setSelectedIndicatorColors(colors); + } + + /** + * Sets the colors to be used for tab dividers. These colors are treated as a circular array. + * Providing one color will mean that all tabs are indicated with the same color. + */ + public void setDividerColors(int... colors) { + mTabStrip.setDividerColors(colors); + } + + /** + * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are + * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so + * that the layout can update it's scroll position correctly. + * + * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mViewPagerPageChangeListener = listener; + } + + /** + * Set the custom layout to be inflated for the tab views. + * + * @param layoutResId Layout id to be inflated + * @param textViewId id of the {@link TextView} in the inflated view + */ + public void setCustomTabView(int layoutResId, int textViewId) { + mTabViewLayoutId = layoutResId; + mTabViewTextViewId = textViewId; + } + + /** + * Sets the associated view pager. Note that the assumption here is that the pager content + * (number of tabs and tab titles) does not change after this call has been made. + */ + public void setViewPager(ViewPager viewPager) { + mTabStrip.removeAllViews(); + + mViewPager = viewPager; + if (viewPager != null) { + viewPager.setOnPageChangeListener(new InternalViewPagerListener()); + populateTabStrip(); + } + } + + /** + * Create a default view to be used for tabs. This is called if a custom tab view is not set via + * {@link #setCustomTabView(int, int)}. + */ + protected TextView createDefaultTabView(Context context) { + TextView textView = new TextView(context); + textView.setGravity(Gravity.CENTER); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); + textView.setTypeface(Typeface.DEFAULT_BOLD); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // If we're running on Honeycomb or newer, then we can use the Theme's + // selectableItemBackground to ensure that the View has a pressed state + TypedValue outValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, + outValue, true); + textView.setBackgroundResource(outValue.resourceId); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style + textView.setAllCaps(true); + } + + int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); + textView.setPadding(padding, padding, padding, padding); + + return textView; + } + + private void populateTabStrip() { + final PagerAdapter adapter = mViewPager.getAdapter(); + final View.OnClickListener tabClickListener = new TabClickListener(); + + for (int i = 0; i < adapter.getCount(); i++) { + View tabView = null; + TextView tabTitleView = null; + + if (mTabViewLayoutId != 0) { + // If there is a custom tab view layout id set, try and inflate it + tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, + false); + tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); + } + + if (tabView == null) { + tabView = createDefaultTabView(getContext()); + } + + if (tabTitleView == null && TextView.class.isInstance(tabView)) { + tabTitleView = (TextView) tabView; + } + + tabTitleView.setText(adapter.getPageTitle(i)); + tabView.setOnClickListener(tabClickListener); + + mTabStrip.addView(tabView); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mViewPager != null) { + scrollToTab(mViewPager.getCurrentItem(), 0); + } + } + + private void scrollToTab(int tabIndex, int positionOffset) { + final int tabStripChildCount = mTabStrip.getChildCount(); + if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { + return; + } + + View selectedChild = mTabStrip.getChildAt(tabIndex); + if (selectedChild != null) { + int targetScrollX = selectedChild.getLeft() + positionOffset; + + if (tabIndex > 0 || positionOffset > 0) { + // If we're not at the first child and are mid-scroll, make sure we obey the offset + targetScrollX -= mTitleOffset; + } + + scrollTo(targetScrollX, 0); + } + } + + private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { + private int mScrollState; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + int tabStripChildCount = mTabStrip.getChildCount(); + if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { + return; + } + + mTabStrip.onViewPagerPageChanged(position, positionOffset); + + View selectedTitle = mTabStrip.getChildAt(position); + int extraOffset = (selectedTitle != null) + ? (int) (positionOffset * selectedTitle.getWidth()) + : 0; + scrollToTab(position, extraOffset); + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, + positionOffsetPixels); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mTabStrip.onViewPagerPageChanged(position, 0f); + scrollToTab(position, 0); + } + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageSelected(position); + } + } + + } + + private class TabClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + if (v == mTabStrip.getChildAt(i)) { + mViewPager.setCurrentItem(i); + return; + } + } + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java new file mode 100644 index 000000000..4c41e12c5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SlidingTabStrip.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.R; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; + +/** + * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html + */ +class SlidingTabStrip extends LinearLayout { + + private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2; + private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; + private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8; + private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFFAA66CC; + + private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1; + private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20; + private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f; + + private final int mBottomBorderThickness; + private final Paint mBottomBorderPaint; + + private final int mSelectedIndicatorThickness; + private final Paint mSelectedIndicatorPaint; + + private final int mDefaultBottomBorderColor; + + private final Paint mDividerPaint; + private final float mDividerHeight; + + private int mSelectedPosition; + private float mSelectionOffset; + + private SlidingTabLayout.TabColorizer mCustomTabColorizer; + private final SimpleTabColorizer mDefaultTabColorizer; + + SlidingTabStrip(Context context) { + this(context, null); + } + + SlidingTabStrip(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + + final float density = getResources().getDisplayMetrics().density; + + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); + final int themeForegroundColor = outValue.data; + + mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, + DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); + + mDefaultTabColorizer = new SimpleTabColorizer(); + mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); + mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor, + DEFAULT_DIVIDER_COLOR_ALPHA)); + + mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); + mBottomBorderPaint = new Paint(); + mBottomBorderPaint.setColor(mDefaultBottomBorderColor); + + mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); + mSelectedIndicatorPaint = new Paint(); + + mDividerHeight = DEFAULT_DIVIDER_HEIGHT; + mDividerPaint = new Paint(); + mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density)); + } + + void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { + mCustomTabColorizer = customTabColorizer; + invalidate(); + } + + void setSelectedIndicatorColors(int... colors) { + // Make sure that the custom colorizer is removed + mCustomTabColorizer = null; + mDefaultTabColorizer.setIndicatorColors(colors); + invalidate(); + } + + void setDividerColors(int... colors) { + // Make sure that the custom colorizer is removed + mCustomTabColorizer = null; + mDefaultTabColorizer.setDividerColors(colors); + invalidate(); + } + + void onViewPagerPageChanged(int position, float positionOffset) { + mSelectedPosition = position; + mSelectionOffset = positionOffset; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + final int height = getHeight(); + final int childCount = getChildCount(); + final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height); + final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null + ? mCustomTabColorizer + : mDefaultTabColorizer; + + // Thick colored underline below the current selection + if (childCount > 0) { + View selectedTitle = getChildAt(mSelectedPosition); + int left = selectedTitle.getLeft(); + int right = selectedTitle.getRight(); + int color = tabColorizer.getIndicatorColor(mSelectedPosition); + + if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { + int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); + if (color != nextColor) { + color = blendColors(nextColor, color, mSelectionOffset); + } + + // Draw the selection partway between the tabs + View nextTitle = getChildAt(mSelectedPosition + 1); + left = (int) (mSelectionOffset * nextTitle.getLeft() + + (1.0f - mSelectionOffset) * left); + right = (int) (mSelectionOffset * nextTitle.getRight() + + (1.0f - mSelectionOffset) * right); + } + + mSelectedIndicatorPaint.setColor(color); + + canvas.drawRect(left, height - mSelectedIndicatorThickness, right, + height, mSelectedIndicatorPaint); + } + + // Thin underline along the entire bottom edge + canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); + + // Vertical separators between the titles + int separatorTop = (height - dividerHeightPx) / 2; + for (int i = 0; i < childCount - 1; i++) { + View child = getChildAt(i); + mDividerPaint.setColor(tabColorizer.getDividerColor(i)); + canvas.drawLine(child.getRight(), separatorTop, child.getRight(), + separatorTop + dividerHeightPx, mDividerPaint); + } + } + + /** + * Set the alpha value of the {@code color} to be the given {@code alpha} value. + */ + private static int setColorAlpha(int color, byte alpha) { + return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + /** + * Blend {@code color1} and {@code color2} using the given ratio. + * + * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, + * 0.0 will return {@code color2}. + */ + private static int blendColors(int color1, int color2, float ratio) { + final float inverseRation = 1f - ratio; + float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); + float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); + float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); + return Color.rgb((int) r, (int) g, (int) b); + } + + private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { + private int[] mIndicatorColors; + private int[] mDividerColors; + + @Override + public final int getIndicatorColor(int position) { + return mIndicatorColors[position % mIndicatorColors.length]; + } + + @Override + public final int getDividerColor(int position) { + return mDividerColors[position % mDividerColors.length]; + } + + void setIndicatorColors(int... colors) { + mIndicatorColors = colors; + } + + void setDividerColors(int... colors) { + mDividerColors = colors; + } + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java deleted file mode 100644 index 065034be1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.util; - -import android.content.Context; -import android.graphics.Typeface; -import android.os.Build; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.HorizontalScrollView; -import android.widget.TextView; - -/** - * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html - */ - -/** - * To be used with ViewPager to provide a tab indicator component which give constant feedback as to - * the user's scroll progress. - *

- * To use the component, simply add it to your view hierarchy. Then in your - * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call - * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. - *

- * The colors can be customized in two ways. The first and simplest is to provide an array of colors - * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The - * alternative is via the {@link TabColorizer} interface which provides you complete control over - * which color is used for any individual position. - *

- * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, - * providing the layout ID of your custom layout. - */ -public class SlidingTabLayout extends HorizontalScrollView { - - /** - * Allows complete control over the colors drawn in the tab layout. Set with - * {@link #setCustomTabColorizer(TabColorizer)}. - */ - public interface TabColorizer { - - /** - * @return return the color of the indicator used when {@code position} is selected. - */ - int getIndicatorColor(int position); - - /** - * @return return the color of the divider drawn to the right of {@code position}. - */ - int getDividerColor(int position); - - } - - private static final int TITLE_OFFSET_DIPS = 24; - private static final int TAB_VIEW_PADDING_DIPS = 16; - private static final int TAB_VIEW_TEXT_SIZE_SP = 12; - - private int mTitleOffset; - - private int mTabViewLayoutId; - private int mTabViewTextViewId; - - private ViewPager mViewPager; - private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; - - private final SlidingTabStrip mTabStrip; - - public SlidingTabLayout(Context context) { - this(context, null); - } - - public SlidingTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // Disable the Scroll Bar - setHorizontalScrollBarEnabled(false); - // Make sure that the Tab Strips fills this View - setFillViewport(true); - - mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); - - mTabStrip = new SlidingTabStrip(context); - addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - - /** - * Set the custom {@link TabColorizer} to be used. - *

- * If you only require simple custmisation then you can use - * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve - * similar effects. - */ - public void setCustomTabColorizer(TabColorizer tabColorizer) { - mTabStrip.setCustomTabColorizer(tabColorizer); - } - - /** - * Sets the colors to be used for indicating the selected tab. These colors are treated as a - * circular array. Providing one color will mean that all tabs are indicated with the same color. - */ - public void setSelectedIndicatorColors(int... colors) { - mTabStrip.setSelectedIndicatorColors(colors); - } - - /** - * Sets the colors to be used for tab dividers. These colors are treated as a circular array. - * Providing one color will mean that all tabs are indicated with the same color. - */ - public void setDividerColors(int... colors) { - mTabStrip.setDividerColors(colors); - } - - /** - * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are - * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so - * that the layout can update it's scroll position correctly. - * - * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) - */ - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { - mViewPagerPageChangeListener = listener; - } - - /** - * Set the custom layout to be inflated for the tab views. - * - * @param layoutResId Layout id to be inflated - * @param textViewId id of the {@link TextView} in the inflated view - */ - public void setCustomTabView(int layoutResId, int textViewId) { - mTabViewLayoutId = layoutResId; - mTabViewTextViewId = textViewId; - } - - /** - * Sets the associated view pager. Note that the assumption here is that the pager content - * (number of tabs and tab titles) does not change after this call has been made. - */ - public void setViewPager(ViewPager viewPager) { - mTabStrip.removeAllViews(); - - mViewPager = viewPager; - if (viewPager != null) { - viewPager.setOnPageChangeListener(new InternalViewPagerListener()); - populateTabStrip(); - } - } - - /** - * Create a default view to be used for tabs. This is called if a custom tab view is not set via - * {@link #setCustomTabView(int, int)}. - */ - protected TextView createDefaultTabView(Context context) { - TextView textView = new TextView(context); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); - textView.setTypeface(Typeface.DEFAULT_BOLD); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - // If we're running on Honeycomb or newer, then we can use the Theme's - // selectableItemBackground to ensure that the View has a pressed state - TypedValue outValue = new TypedValue(); - getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, - outValue, true); - textView.setBackgroundResource(outValue.resourceId); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style - textView.setAllCaps(true); - } - - int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); - textView.setPadding(padding, padding, padding, padding); - - return textView; - } - - private void populateTabStrip() { - final PagerAdapter adapter = mViewPager.getAdapter(); - final View.OnClickListener tabClickListener = new TabClickListener(); - - for (int i = 0; i < adapter.getCount(); i++) { - View tabView = null; - TextView tabTitleView = null; - - if (mTabViewLayoutId != 0) { - // If there is a custom tab view layout id set, try and inflate it - tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, - false); - tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); - } - - if (tabView == null) { - tabView = createDefaultTabView(getContext()); - } - - if (tabTitleView == null && TextView.class.isInstance(tabView)) { - tabTitleView = (TextView) tabView; - } - - tabTitleView.setText(adapter.getPageTitle(i)); - tabView.setOnClickListener(tabClickListener); - - mTabStrip.addView(tabView); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager != null) { - scrollToTab(mViewPager.getCurrentItem(), 0); - } - } - - private void scrollToTab(int tabIndex, int positionOffset) { - final int tabStripChildCount = mTabStrip.getChildCount(); - if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { - return; - } - - View selectedChild = mTabStrip.getChildAt(tabIndex); - if (selectedChild != null) { - int targetScrollX = selectedChild.getLeft() + positionOffset; - - if (tabIndex > 0 || positionOffset > 0) { - // If we're not at the first child and are mid-scroll, make sure we obey the offset - targetScrollX -= mTitleOffset; - } - - scrollTo(targetScrollX, 0); - } - } - - private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { - private int mScrollState; - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - int tabStripChildCount = mTabStrip.getChildCount(); - if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { - return; - } - - mTabStrip.onViewPagerPageChanged(position, positionOffset); - - View selectedTitle = mTabStrip.getChildAt(position); - int extraOffset = (selectedTitle != null) - ? (int) (positionOffset * selectedTitle.getWidth()) - : 0; - scrollToTab(position, extraOffset); - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, - positionOffsetPixels); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrollStateChanged(state); - } - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - mTabStrip.onViewPagerPageChanged(position, 0f); - scrollToTab(position, 0); - } - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageSelected(position); - } - } - - } - - private class TabClickListener implements View.OnClickListener { - @Override - public void onClick(View v) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - if (v == mTabStrip.getChildAt(i)) { - mViewPager.setCurrentItem(i); - return; - } - } - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabStrip.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabStrip.java deleted file mode 100644 index 9ce6f943e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabStrip.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.util; - -import android.R; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.widget.LinearLayout; - -/** - * Copied from http://developer.android.com/samples/SlidingTabsColors/index.html - */ -class SlidingTabStrip extends LinearLayout { - - private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2; - private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; - private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8; - private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFFAA66CC; - - private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1; - private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20; - private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f; - - private final int mBottomBorderThickness; - private final Paint mBottomBorderPaint; - - private final int mSelectedIndicatorThickness; - private final Paint mSelectedIndicatorPaint; - - private final int mDefaultBottomBorderColor; - - private final Paint mDividerPaint; - private final float mDividerHeight; - - private int mSelectedPosition; - private float mSelectionOffset; - - private SlidingTabLayout.TabColorizer mCustomTabColorizer; - private final SimpleTabColorizer mDefaultTabColorizer; - - SlidingTabStrip(Context context) { - this(context, null); - } - - SlidingTabStrip(Context context, AttributeSet attrs) { - super(context, attrs); - setWillNotDraw(false); - - final float density = getResources().getDisplayMetrics().density; - - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); - final int themeForegroundColor = outValue.data; - - mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, - DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); - - mDefaultTabColorizer = new SimpleTabColorizer(); - mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); - mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor, - DEFAULT_DIVIDER_COLOR_ALPHA)); - - mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); - mBottomBorderPaint = new Paint(); - mBottomBorderPaint.setColor(mDefaultBottomBorderColor); - - mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); - mSelectedIndicatorPaint = new Paint(); - - mDividerHeight = DEFAULT_DIVIDER_HEIGHT; - mDividerPaint = new Paint(); - mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density)); - } - - void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { - mCustomTabColorizer = customTabColorizer; - invalidate(); - } - - void setSelectedIndicatorColors(int... colors) { - // Make sure that the custom colorizer is removed - mCustomTabColorizer = null; - mDefaultTabColorizer.setIndicatorColors(colors); - invalidate(); - } - - void setDividerColors(int... colors) { - // Make sure that the custom colorizer is removed - mCustomTabColorizer = null; - mDefaultTabColorizer.setDividerColors(colors); - invalidate(); - } - - void onViewPagerPageChanged(int position, float positionOffset) { - mSelectedPosition = position; - mSelectionOffset = positionOffset; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - final int height = getHeight(); - final int childCount = getChildCount(); - final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height); - final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null - ? mCustomTabColorizer - : mDefaultTabColorizer; - - // Thick colored underline below the current selection - if (childCount > 0) { - View selectedTitle = getChildAt(mSelectedPosition); - int left = selectedTitle.getLeft(); - int right = selectedTitle.getRight(); - int color = tabColorizer.getIndicatorColor(mSelectedPosition); - - if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { - int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); - if (color != nextColor) { - color = blendColors(nextColor, color, mSelectionOffset); - } - - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - left = (int) (mSelectionOffset * nextTitle.getLeft() + - (1.0f - mSelectionOffset) * left); - right = (int) (mSelectionOffset * nextTitle.getRight() + - (1.0f - mSelectionOffset) * right); - } - - mSelectedIndicatorPaint.setColor(color); - - canvas.drawRect(left, height - mSelectedIndicatorThickness, right, - height, mSelectedIndicatorPaint); - } - - // Thin underline along the entire bottom edge - canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); - - // Vertical separators between the titles - int separatorTop = (height - dividerHeightPx) / 2; - for (int i = 0; i < childCount - 1; i++) { - View child = getChildAt(i); - mDividerPaint.setColor(tabColorizer.getDividerColor(i)); - canvas.drawLine(child.getRight(), separatorTop, child.getRight(), - separatorTop + dividerHeightPx, mDividerPaint); - } - } - - /** - * Set the alpha value of the {@code color} to be the given {@code alpha} value. - */ - private static int setColorAlpha(int color, byte alpha) { - return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - /** - * Blend {@code color1} and {@code color2} using the given ratio. - * - * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, - * 0.0 will return {@code color2}. - */ - private static int blendColors(int color1, int color2, float ratio) { - final float inverseRation = 1f - ratio; - float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); - float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); - float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); - return Color.rgb((int) r, (int) g, (int) b); - } - - private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { - private int[] mIndicatorColors; - private int[] mDividerColors; - - @Override - public final int getIndicatorColor(int position) { - return mIndicatorColors[position % mIndicatorColors.length]; - } - - @Override - public final int getDividerColor(int position) { - return mDividerColors[position % mDividerColors.length]; - } - - void setIndicatorColors(int... colors) { - mIndicatorColors = colors; - } - - void setDividerColors(int... colors) { - mDividerColors = colors; - } - } -} \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/help_activity.xml b/OpenKeychain/src/main/res/layout/help_activity.xml index 76ba183b7..3ad087da3 100644 --- a/OpenKeychain/src/main/res/layout/help_activity.xml +++ b/OpenKeychain/src/main/res/layout/help_activity.xml @@ -4,7 +4,7 @@ android:layout_height="match_parent" android:orientation="vertical" > - diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml index f43aade25..5aa1cd167 100644 --- a/OpenKeychain/src/main/res/layout/view_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml @@ -35,7 +35,7 @@ android:visibility="gone" android:id="@+id/status_divider" /> - -- cgit v1.2.3 From d530a7b0ee0489c81ed841de5a5d94ebceb9d2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 21:52:17 +0200 Subject: Update buildTools to 19.1 --- OpenKeychain/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 20e5ca594..a9c7aa282 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -51,7 +51,7 @@ dependencies { android { compileSdkVersion 19 - buildToolsVersion "19.0.3" + buildToolsVersion "19.1" defaultConfig { minSdkVersion 9 -- cgit v1.2.3 From fe1b8468a150679d572cf1cca1e6c99e2ded659c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 21:55:32 +0200 Subject: Update buildTools to 19.1 --- extern/AndroidBootstrap | 2 +- extern/AppMsg | 2 +- extern/StickyListHeaders | 2 +- extern/html-textview | 2 +- extern/openkeychain-api-lib | 2 +- extern/openpgp-api-lib | 2 +- extern/zxing-android-integration | 2 +- extern/zxing-qr-code | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extern/AndroidBootstrap b/extern/AndroidBootstrap index bfa160c4e..02f02391e 160000 --- a/extern/AndroidBootstrap +++ b/extern/AndroidBootstrap @@ -1 +1 @@ -Subproject commit bfa160c4ef3a1a53aebd68aca9c05b8e546219e0 +Subproject commit 02f02391e3eee9331e07d7690d3b533a8b0f69e2 diff --git a/extern/AppMsg b/extern/AppMsg index ca714df97..61e747419 160000 --- a/extern/AppMsg +++ b/extern/AppMsg @@ -1 +1 @@ -Subproject commit ca714df97bfce67a7a9a1efefd2c49645b6f22e4 +Subproject commit 61e74741909a712db2e0d31ddffa5b7cf37c21f2 diff --git a/extern/StickyListHeaders b/extern/StickyListHeaders index efe46c211..706c0e447 160000 --- a/extern/StickyListHeaders +++ b/extern/StickyListHeaders @@ -1 +1 @@ -Subproject commit efe46c21143cc54a2394303a67822f14580d1d20 +Subproject commit 706c0e447229226b6edc82ab10630d39fd0f6c38 diff --git a/extern/html-textview b/extern/html-textview index c31ef2aff..eedaa334e 160000 --- a/extern/html-textview +++ b/extern/html-textview @@ -1 +1 @@ -Subproject commit c31ef2aff4282ad00af98e879e3e0a6000885b55 +Subproject commit eedaa334e761273efbfc49ded2124df58c8a4d88 diff --git a/extern/openkeychain-api-lib b/extern/openkeychain-api-lib index 26497acb2..175a3cb77 160000 --- a/extern/openkeychain-api-lib +++ b/extern/openkeychain-api-lib @@ -1 +1 @@ -Subproject commit 26497acb27e9f6349c0557b15cd24a5b0b735e74 +Subproject commit 175a3cb772c88c9b50985abc98f81c9ea69c3659 diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 650e1ebda..a77887d32 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 650e1ebda82596cd4fbfaae406e6eccf189f4f63 +Subproject commit a77887d32fae68171fcd0d2989bf537c0c11f0b9 diff --git a/extern/zxing-android-integration b/extern/zxing-android-integration index 34029d4dc..1d7878456 160000 --- a/extern/zxing-android-integration +++ b/extern/zxing-android-integration @@ -1 +1 @@ -Subproject commit 34029d4dcac81ec06137a046a189dac608e76efe +Subproject commit 1d787845663fd232f98f5e8e0923733c1a188f2a diff --git a/extern/zxing-qr-code b/extern/zxing-qr-code index 50193905f..9ef2f3b66 160000 --- a/extern/zxing-qr-code +++ b/extern/zxing-qr-code @@ -1 +1 @@ -Subproject commit 50193905fd8cef92140ea242f77b04bb31391c9e +Subproject commit 9ef2f3b66ea7cc283e865ec39434d023a18d17f3 -- cgit v1.2.3 From de6fe46ecab01a55e20ba7e7479c31bf20f51af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 22:02:04 +0200 Subject: Fix travis for buildTools 19.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index df700368c..19a36272d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ before_install: - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools # Install required Android components. - - echo "y" | android update sdk -a --filter build-tools-19.0.3,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force + - echo "y" | android update sdk -a --filter build-tools-19.1,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force install: echo "Installation done" script: gradle assemble -S -q -- cgit v1.2.3 From cb3c2b20086bbce4f338e458a00ab18e4dccc99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 22:09:30 +0200 Subject: Disable robolectric, update to android gradle 0.11.1, update gradle to 1.12 --- OpenKeychain/build.gradle | 8 ++++---- build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index a9c7aa282..dca9faba6 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'android' -apply plugin: 'android-test' +//apply plugin: 'android-test' sourceSets { - androidTest { - java.srcDir file('src/test/java') + //androidTest { + //java.srcDir file('src/test/java') // configure the set of classes for JUnit tests // include '**/*Test.class' - } + //} } dependencies { diff --git a/build.gradle b/build.gradle index 815a8ff9a..e3cafe3ce 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { dependencies { // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information - classpath 'com.android.tools.build:gradle:0.10.0' - classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0' + classpath 'com.android.tools.build:gradle:0.11.1' + //classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0' } } @@ -17,5 +17,5 @@ allprojects { } task wrapper(type: Wrapper) { - gradleVersion = '1.10' + gradleVersion = '1.12' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d8e0b5b29..cc3d5d9a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 06 22:23:44 CET 2014 +#Mon Jun 09 22:04:23 CEST 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip -- cgit v1.2.3 From 73a825a152ef0d8bad0bbe2568cb2c38e6fa9581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 22:14:30 +0200 Subject: Really fix travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19a36272d..36e8f8fcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,13 @@ before_install: # Install base Android SDK - sudo apt-get update -qq - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi - - wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz - - tar xzf android-sdk_r22.3-linux.tgz + - wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz + - tar xzf android-sdk_r22.6.2-linux.tgz - export ANDROID_HOME=$PWD/android-sdk-linux - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools # Install required Android components. - - echo "y" | android update sdk -a --filter build-tools-19.1,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force + - echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force install: echo "Installation done" script: gradle assemble -S -q -- cgit v1.2.3 From 2b825c8df73d8f662b4fc9e896add1c0217ee6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 22:30:12 +0200 Subject: Update README for build tools --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8327c66b..ee755d2b7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev 1. Get all external submodules with ``git submodule update --init --recursive`` 2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) 3. Open the Android SDK Manager (shell command: ``android``). -Expand the Tools directory and select "Android SDK Build-tools (Version 19.0.3)". +Expand the Tools directory and select "Android SDK Build-tools (Version 19.1)". Expand the Extras directory and install "Android Support Repository" Select everything for the newest SDK Platform (API-Level 19) 4. Export ANDROID_HOME pointing to your Android SDK -- cgit v1.2.3 From 70d454785fc87dfaf8731a6eb7cebe3fc0056e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 9 Jun 2014 22:51:13 +0200 Subject: Update example code instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee755d2b7..231dc7f16 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ Select everything for the newest SDK Platform (API-Level 19) ### Build API Demo with Gradle -1. Follow 1-3 from above -2. Change to API Demo directory ``cd OpenKeychain-API`` +1. Follow 1-4 from above +2. The example code is available at https://github.com/open-keychain/api-example 3. Execute ``./gradlew build`` ### Development with Android Studio -- cgit v1.2.3