diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2014-05-14 16:02:28 +0200 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2014-05-14 16:02:28 +0200 |
commit | 2f95100d88954db389cba8e615390795d121c1c8 (patch) | |
tree | 1bf4ffb29bb83fac33ba2ed7c84fb1d6a8af2bd4 /OpenKeychain/src/main/java/org | |
parent | 90ac60b6db0637fbefbce5cb2cd80a64f5bb708d (diff) | |
parent | 638554f2560b3c5fd36ca9b4ba205cb5a999b84b (diff) | |
download | open-keychain-2f95100d88954db389cba8e615390795d121c1c8.tar.gz open-keychain-2f95100d88954db389cba8e615390795d121c1c8.tar.bz2 open-keychain-2f95100d88954db389cba8e615390795d121c1c8.zip |
Merge remote-tracking branch 'origin/master' into wrapped-key-ring
Conflicts:
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
OpenKeychain/src/main/res/values/strings.xml
Diffstat (limited to 'OpenKeychain/src/main/java/org')
72 files changed, 2531 insertions, 1048 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 866888bb2..f911318a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain; import android.app.Application; +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; @@ -70,5 +73,22 @@ public class KeychainApplication extends Application { // that the directory doesn't exist at this point } } + + brandGlowEffect(getApplicationContext(), + getApplicationContext().getResources().getColor(R.color.emphasis)); + } + + static void brandGlowEffect(Context context, int brandColor) { + // terrible hack to brand the edge overscroll glow effect + // https://gist.github.com/menny/7878762#file-brandgloweffect_full-java + + //glow + int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android"); + Drawable androidGlow = context.getResources().getDrawable(glowDrawableId); + androidGlow.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN); + //edge + int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android"); + Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId); + androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java index 72b6d9e1d..46e7936ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java @@ -33,25 +33,6 @@ import org.sufficientlysecure.keychain.util.Log; public class ActionBarHelper { /** - * Set actionbar without home button if called from another app - * - * @param activity - */ - public static void setBackButton(ActionBarActivity activity) { - final ActionBar actionBar = activity.getSupportActionBar(); - Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)=" - + activity.getCallingPackage()); - if (activity.getCallingPackage() != null - && activity.getCallingPackage().equals(Constants.PACKAGE_NAME)) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeButtonEnabled(true); - } else { - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - } - } - - /** * Sets custom view on ActionBar for Done/Cancel activities * * @param actionBar diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java index f22fcd4b8..d7d73cf3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -112,16 +112,18 @@ public class FileHelper { if ("content".equalsIgnoreCase(uri.getScheme())) { String[] projection = {"_data"}; - Cursor cursor = null; - + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); try { - cursor = context.getContentResolver().query(uri, projection, null, null, null); - int columnIndex = cursor.getColumnIndexOrThrow("_data"); - if (cursor.moveToFirst()) { + if (cursor != null && cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow("_data"); return cursor.getString(columnIndex); } } catch (Exception e) { // Eat it + } finally { + if (cursor != null) { + cursor.close(); + } } } else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java index cf658b0b6..85ce6bfcc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.keyimport; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -32,9 +32,8 @@ import org.apache.http.util.EntityUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +import org.sufficientlysecure.keychain.util.Log; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -167,21 +166,6 @@ public class HkpKeyServer extends KeyServer { mPort = port; } - private static String readAll(InputStream in, String encoding) throws IOException { - ByteArrayOutputStream raw = new ByteArrayOutputStream(); - - byte buffer[] = new byte[1 << 16]; - int n = 0; - while ((n = in.read(buffer)) != -1) { - raw.write(buffer, 0, n); - } - - if (encoding == null) { - encoding = "utf8"; - } - return raw.toString(encoding); - } - private String query(String request) throws QueryException, HttpError { InetAddress ips[]; try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index b06852af4..1199290e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -15,12 +15,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package org.sufficientlysecure.keychain.ui.adapter; +package org.sufficientlysecure.keychain.keyimport; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; -import android.util.SparseArray; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.openpgp.PGPKeyRing; @@ -203,7 +202,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { * Constructor for later querying from keyserver */ public ImportKeysListEntry() { - // keys from keyserver are always public keys + // keys from keyserver are always public keys; from keybase too secretKey = false; // do not select by default mSelected = false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java index 2b97165ac..d6ebca5a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java @@ -16,10 +16,11 @@ * limitations under the License. */ -package org.sufficientlysecure.keychain.util; - -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +package org.sufficientlysecure.keychain.keyimport; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.List; public abstract class KeyServer { @@ -49,4 +50,19 @@ public abstract class KeyServer { abstract String get(String keyIdHex) throws QueryException; abstract void add(String armoredKey) throws AddKeyException; + + public static String readAll(InputStream in, String encoding) throws IOException { + ByteArrayOutputStream raw = new ByteArrayOutputStream(); + + byte buffer[] = new byte[1 << 16]; + int n = 0; + while ((n = in.read(buffer)) != -1) { + raw.write(buffer, 0, n); + } + + if (encoding == null) { + encoding = "utf8"; + } + return raw.toString(encoding); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java new file mode 100644 index 000000000..7ffe123c0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 Tim Bray <tbray@textuality.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.keyimport; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.JWalk; +import org.sufficientlysecure.keychain.util.Log; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.WeakHashMap; + +public class KeybaseKeyServer extends KeyServer { + + private WeakHashMap<String, String> mKeyCache = new WeakHashMap<String, String>(); + + @Override + public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses, + InsufficientQuery { + ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>(); + + JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query); + try { + + JSONArray matches = JWalk.getArray(fromQuery, "completions"); + for (int i = 0; i < matches.length(); i++) { + JSONObject match = matches.getJSONObject(i); + + // only list them if they have a key + if (JWalk.optObject(match, "components", "key_fingerprint") != null) { + results.add(makeEntry(match)); + } + } + } catch (Exception e) { + throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage()); + } + + return results; + } + + private JSONObject getUser(String keybaseID) throws QueryException { + try { + return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID); + } catch (Exception e) { + String detail = ""; + if (keybaseID != null) { + detail = ". Query was for user '" + keybaseID + "'"; + } + throw new QueryException(e.getMessage() + detail); + } + } + + private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { + + String keybaseID = JWalk.getString(match, "components", "username", "val"); + String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); + key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase(); + match = getUser(keybaseID); + + final ImportKeysListEntry entry = new ImportKeysListEntry(); + + // TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls + entry.setBitStrength(4096); + entry.setAlgorithm("RSA"); + entry.setKeyIdHex("0x" + key_fingerprint); + entry.setRevoked(false); + + // ctime + final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime"); + final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + tmpGreg.setTimeInMillis(creationDate * 1000); + entry.setDate(tmpGreg.getTime()); + + // key bits + // we have to fetch the user object to construct the search-result list, so we might as + // well (weakly) remember the key, in case they try to import it + mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle")); + + // String displayName = JWalk.getString(match, "them", "profile", "full_name"); + ArrayList<String> userIds = new ArrayList<String>(); + String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>"; + userIds.add(name); + userIds.add(keybaseID); + entry.setUserIds(userIds); + entry.setPrimaryUserId(name); + return entry; + } + + private JSONObject getFromKeybase(String path, String query) throws QueryException { + try { + String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8"); + Log.d(Constants.TAG, "keybase query: " + url); + + URL realUrl = new URL(url); + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase + conn.setReadTimeout(25000); + conn.connect(); + int response = conn.getResponseCode(); + if (response >= 200 && response < 300) { + String text = readAll(conn.getInputStream(), conn.getContentEncoding()); + try { + JSONObject json = new JSONObject(text); + if (JWalk.getInt(json, "status", "code") != 0) { + throw new QueryException("Keybase autocomplete search failed"); + } + return json; + } catch (JSONException e) { + throw new QueryException("Keybase.io query returned broken JSON"); + } + } else { + String message = readAll(conn.getErrorStream(), conn.getContentEncoding()); + throw new QueryException("Keybase.io query error (status=" + response + + "): " + message); + } + } catch (Exception e) { + throw new QueryException("Keybase.io query error"); + } + } + + @Override + public String get(String id) throws QueryException { + String key = mKeyCache.get(id); + if (key == null) { + try { + JSONObject user = getUser(id); + key = JWalk.getString(user, "them", "public_keys", "primary", "bundle"); + } catch (Exception e) { + throw new QueryException(e.getMessage()); + } + } + return key; + } + + @Override + public void add(String armoredKey) throws AddKeyException { + throw new AddKeyException(); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index a7e1bd603..a0d2d5cea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -36,11 +36,10 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import org.sufficientlysecure.keychain.util.HkpKeyServer; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; import org.sufficientlysecure.keychain.util.IterableIterator; -import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; -import org.sufficientlysecure.keychain.util.KeychainServiceListener; +import org.sufficientlysecure.keychain.keyimport.KeyServer.AddKeyException; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; @@ -51,6 +50,11 @@ import java.util.List; public class PgpImportExport { + // TODO: is this really used? + public interface KeychainServiceListener { + boolean hasServiceStopped(); + } + private Context mContext; private Progressable mProgressable; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index ccbbb3719..96ee3f10a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -181,8 +181,6 @@ public class PgpSignEncrypt { } /** - * TODO: test this option! - * * @param encryptToSigner * @return */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 061f91176..68726d3e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -255,53 +255,60 @@ public class KeychainDatabase extends SQLiteOpenHelper { } }.getReadableDatabase(); - Cursor c = null; + Cursor cursor = null; try { // we insert in two steps: first, all public keys that have secret keys - c = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS (" + cursor = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS (" + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id" + " AND d2.type = 1) ORDER BY type ASC", null); - Log.d(Constants.TAG, "Importing " + c.getCount() + " secret keyrings from apg.db..."); - for (int i = 0; i < c.getCount(); i++) { - c.moveToPosition(i); - byte[] data = c.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - else if (ring instanceof PGPSecretKeyRing) - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - else { - Log.e(Constants.TAG, "Unknown blob data type!"); + Log.d(Constants.TAG, "Importing " + cursor.getCount() + " secret keyrings from apg.db..."); + if (cursor != null) { + for (int i = 0; i < cursor.getCount(); i++) { + cursor.moveToPosition(i); + byte[] data = cursor.getBlob(0); + PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); + ProviderHelper providerHelper = new ProviderHelper(context); + if (ring instanceof PGPPublicKeyRing) + providerHelper.saveKeyRing((PGPPublicKeyRing) ring); + else if (ring instanceof PGPSecretKeyRing) + providerHelper.saveKeyRing((PGPSecretKeyRing) ring); + else { + Log.e(Constants.TAG, "Unknown blob data type!"); + } } } + if (cursor != null) { + cursor.close(); + } // afterwards, insert all keys, starting with public keys that have secret keys, then // secret keys, then all others. this order is necessary to ensure all certifications // are recognized properly. - c = db.rawQuery("SELECT key_ring_data FROM key_rings ORDER BY (type = 0 AND EXISTS (" + cursor = db.rawQuery("SELECT key_ring_data FROM key_rings ORDER BY (type = 0 AND EXISTS (" + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id AND" + " d2.type = 1)) DESC, type DESC", null); // import from old database - Log.d(Constants.TAG, "Importing " + c.getCount() + " keyrings from apg.db..."); - for (int i = 0; i < c.getCount(); i++) { - c.moveToPosition(i); - byte[] data = c.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) { - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - } else if (ring instanceof PGPSecretKeyRing) { - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - } else { - Log.e(Constants.TAG, "Unknown blob data type!"); + Log.d(Constants.TAG, "Importing " + cursor.getCount() + " keyrings from apg.db..."); + if (cursor != null) { + for (int i = 0; i < cursor.getCount(); i++) { + cursor.moveToPosition(i); + byte[] data = cursor.getBlob(0); + PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); + ProviderHelper providerHelper = new ProviderHelper(context); + if (ring instanceof PGPPublicKeyRing) { + providerHelper.saveKeyRing((PGPPublicKeyRing) ring); + } else if (ring instanceof PGPSecretKeyRing) { + providerHelper.saveKeyRing((PGPSecretKeyRing) ring); + } else { + Log.e(Constants.TAG, "Unknown blob data type!"); + } } } } catch (IOException e) { Log.e(Constants.TAG, "Error importing apg.db!", e); } finally { - if (c != null) { - c.close(); + if (cursor != null) { + cursor.close(); } if (db != null) { db.close(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index c67c2eca4..9f6314329 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -567,20 +567,21 @@ public class KeychainProvider extends ContentProvider { } SQLiteDatabase db = getDb().getReadableDatabase(); - Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); - - // Tell the cursor what uri to watch, so it knows when its source data changes - c.setNotificationUri(getContext().getContentResolver(), uri); + Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); + if (cursor != null) { + // Tell the cursor what uri to watch, so it knows when its source data changes + cursor.setNotificationUri(getContext().getContentResolver(), uri); + } if (Constants.DEBUG) { Log.d(Constants.TAG, "Query: " + qb.buildQuery(projection, selection, selectionArgs, null, null, orderBy, null)); - Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c)); + Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(cursor)); } - return c; + return cursor; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 0784a0f33..18035eefe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; +import android.support.v4.util.LongSparseArray; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.S2K; @@ -103,36 +104,38 @@ public class ProviderHelper { throws NotFoundException { Cursor cursor = mContentResolver.query(uri, proj, null, null, null); - HashMap<String, Object> result = new HashMap<String, Object>(proj.length); - if (cursor != null && cursor.moveToFirst()) { - int pos = 0; - for (String p : proj) { - switch (types[pos]) { - case FIELD_TYPE_NULL: - result.put(p, cursor.isNull(pos)); - break; - case FIELD_TYPE_INTEGER: - result.put(p, cursor.getLong(pos)); - break; - case FIELD_TYPE_FLOAT: - result.put(p, cursor.getFloat(pos)); - break; - case FIELD_TYPE_STRING: - result.put(p, cursor.getString(pos)); - break; - case FIELD_TYPE_BLOB: - result.put(p, cursor.getBlob(pos)); - break; + try { + HashMap<String, Object> result = new HashMap<String, Object>(proj.length); + if (cursor != null && cursor.moveToFirst()) { + int pos = 0; + for (String p : proj) { + switch (types[pos]) { + case FIELD_TYPE_NULL: + result.put(p, cursor.isNull(pos)); + break; + case FIELD_TYPE_INTEGER: + result.put(p, cursor.getLong(pos)); + break; + case FIELD_TYPE_FLOAT: + result.put(p, cursor.getFloat(pos)); + break; + case FIELD_TYPE_STRING: + result.put(p, cursor.getString(pos)); + break; + case FIELD_TYPE_BLOB: + result.put(p, cursor.getBlob(pos)); + break; + } + pos += 1; } - pos += 1; } - } - if (cursor != null) { - cursor.close(); + return result; + } finally { + if (cursor != null) { + cursor.close(); + } } - - return result; } public Object getUnifiedData(long masterKeyId, String column, int type) @@ -172,22 +175,24 @@ public class ProviderHelper { } @Deprecated - public Map<Long, PGPKeyRing> getPGPKeyRings(Uri queryUri) { + public LongSparseArray<PGPKeyRing> getPGPKeyRings(Uri queryUri) { Cursor cursor = mContentResolver.query(queryUri, new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA}, null, null, null); - Map<Long, PGPKeyRing> result = new HashMap<Long, PGPKeyRing>(cursor.getCount()); - if (cursor != null && cursor.moveToFirst()) do { - long masterKeyId = cursor.getLong(0); - byte[] data = cursor.getBlob(1); - if (data != null) { - result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); + LongSparseArray<PGPKeyRing> result = new LongSparseArray<PGPKeyRing>(cursor.getCount()); + try { + if (cursor != null && cursor.moveToFirst()) do { + long masterKeyId = cursor.getLong(0); + byte[] data = cursor.getBlob(1); + if (data != null) { + result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); + } + } while (cursor.moveToNext()); + } finally { + if (cursor != null) { + cursor.close(); } - } while (cursor.moveToNext()); - - if (cursor != null) { - cursor.close(); } return result; @@ -255,11 +260,11 @@ public class ProviderHelper { @Deprecated public PGPKeyRing getPGPKeyRing(Uri queryUri) throws NotFoundException { - Map<Long, PGPKeyRing> result = getPGPKeyRings(queryUri); - if (result.isEmpty()) { + LongSparseArray<PGPKeyRing> result = getPGPKeyRings(queryUri); + if (result.size() == 0) { throw new NotFoundException("PGPKeyRing object not found!"); } else { - return result.values().iterator().next(); + return result.valueAt(0); } } @@ -312,7 +317,7 @@ public class ProviderHelper { } // get a list of owned secret keys, for verification filtering - Map<Long, PGPKeyRing> allKeyRings = getPGPKeyRings(KeyRingData.buildSecretKeyRingUri()); + LongSparseArray<PGPKeyRing> allKeyRings = getPGPKeyRings(KeyRingData.buildSecretKeyRingUri()); // special case: available secret keys verify themselves! if (secretRing != null) allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing); @@ -350,7 +355,7 @@ public class ProviderHelper { } } // verify signatures from known private keys - if (allKeyRings.containsKey(certId)) { + if (allKeyRings.indexOfKey(certId) >= 0) { // mark them as verified cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME), @@ -634,27 +639,29 @@ public class ProviderHelper { }, inMasterKeyList, null, null); } - if (cursor != null) { - int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID); - int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); - if (cursor.moveToFirst()) { - do { - Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); - - byte[] data = cursor.getBlob(dataCol); - - // get actual keyring data blob and write it to ByteArrayOutputStream - try { - output.add(getKeyRingAsArmoredString(data)); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); - } - } while (cursor.moveToNext()); + try { + if (cursor != null) { + int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID); + int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); + if (cursor.moveToFirst()) { + do { + Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); + + byte[] data = cursor.getBlob(dataCol); + + // get actual keyring data blob and write it to ByteArrayOutputStream + try { + output.add(getKeyRingAsArmoredString(data)); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException", e); + } + } while (cursor.moveToNext()); + } + } + } finally { + if (cursor != null) { + cursor.close(); } - } - - if (cursor != null) { - cursor.close(); } if (output.size() > 0) { @@ -668,17 +675,19 @@ public class ProviderHelper { Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); ArrayList<String> packageNames = new ArrayList<String>(); - if (cursor != null) { - int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); - if (cursor.moveToFirst()) { - do { - packageNames.add(cursor.getString(packageNameCol)); - } while (cursor.moveToNext()); + try { + if (cursor != null) { + int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); + if (cursor.moveToFirst()) { + do { + packageNames.add(cursor.getString(packageNameCol)); + } while (cursor.moveToNext()); + } + } + } finally { + if (cursor != null) { + cursor.close(); } - } - - if (cursor != null) { - cursor.close(); } return packageNames; @@ -726,13 +735,19 @@ public class ProviderHelper { public AppSettings getApiAppSettings(Uri uri) { AppSettings settings = null; - Cursor cur = mContentResolver.query(uri, null, null, null, null); - if (cur != null && cur.moveToFirst()) { - settings = new AppSettings(); - settings.setPackageName(cur.getString( - cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); - settings.setPackageSignature(cur.getBlob( - cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); + Cursor cursor = mContentResolver.query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + settings = new AppSettings(); + settings.setPackageName(cursor.getString( + cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); + settings.setPackageSignature(cursor.getBlob( + cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); + } + } finally { + if (cursor != null) { + cursor.close(); + } } return settings; @@ -741,20 +756,26 @@ public class ProviderHelper { public AccountSettings getApiAccountSettings(Uri accountUri) { AccountSettings settings = null; - Cursor cur = mContentResolver.query(accountUri, null, null, null, null); - if (cur != null && cur.moveToFirst()) { - settings = new AccountSettings(); - - settings.setAccountName(cur.getString( - cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); - settings.setKeyId(cur.getLong( - cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); - settings.setCompression(cur.getInt( - cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION))); - settings.setHashAlgorithm(cur.getInt( - cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM))); - settings.setEncryptionAlgorithm(cur.getInt( - cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM))); + Cursor cursor = mContentResolver.query(accountUri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + settings = new AccountSettings(); + + settings.setAccountName(cursor.getString( + cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); + settings.setKeyId(cursor.getLong( + cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); + settings.setCompression(cursor.getInt( + cursor.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION))); + settings.setHashAlgorithm(cursor.getInt( + cursor.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM))); + settings.setEncryptionAlgorithm(cursor.getInt( + cursor.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM))); + } + } finally { + if (cursor != null) { + cursor.close(); + } } return settings; @@ -764,10 +785,16 @@ public class ProviderHelper { Set<Long> keyIds = new HashSet<Long>(); Cursor cursor = mContentResolver.query(uri, null, null, null, null); - if (cursor != null) { - int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); - while (cursor.moveToNext()) { - keyIds.add(cursor.getLong(keyIdColumn)); + try { + if (cursor != null) { + int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); + while (cursor.moveToNext()) { + keyIds.add(cursor.getLong(keyIdColumn)); + } + } + } finally { + if (cursor != null) { + cursor.close(); } } @@ -780,18 +807,18 @@ public class ProviderHelper { String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE}; Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null); + try { + byte[] signature = null; + if (cursor != null && cursor.moveToFirst()) { + int signatureCol = 0; - byte[] signature = null; - if (cursor != null && cursor.moveToFirst()) { - int signatureCol = 0; - - signature = cursor.getBlob(signatureCol); - } - - if (cursor != null) { - cursor.close(); + signature = cursor.getBlob(signatureCol); + } + return signature; + } finally { + if (cursor != null) { + cursor.close(); + } } - - return signature; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 8a247a11b..58aa2ece8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.ImportKeysActivity; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -69,19 +70,25 @@ public class OpenPgpService extends RemoteService { for (String email : encryptionUserIds) { Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); - Cursor cur = getContentResolver().query(uri, null, null, null, null); - if (cur.moveToFirst()) { - long id = cur.getLong(cur.getColumnIndex(KeyRings.MASTER_KEY_ID)); - keyIds.add(id); - } else { - missingUserIdsCheck = true; - missingUserIds.add(email); - Log.d(Constants.TAG, "user id missing"); - } - if (cur.moveToNext()) { - duplicateUserIdsCheck = true; - duplicateUserIds.add(email); - Log.d(Constants.TAG, "more than one user id with the same email"); + Cursor cursor = getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); + keyIds.add(id); + } else { + missingUserIdsCheck = true; + missingUserIds.add(email); + Log.d(Constants.TAG, "user id missing"); + } + if (cursor != null && cursor.moveToNext()) { + duplicateUserIdsCheck = true; + duplicateUserIds.add(email); + Log.d(Constants.TAG, "more than one user id with the same email"); + } + } finally { + if (cursor != null) { + cursor.close(); + } } } @@ -417,7 +424,15 @@ public class OpenPgpService extends RemoteService { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - // TODO: also return PendingIntent that opens the key view activity + // also return PendingIntent that opens the key view activity + Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(Long.toString(masterKeyId))); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); + + result.putExtra(OpenPgpApi.RESULT_INTENT, pi); return result; } catch (ProviderHelper.NotFoundException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index c6637c058..8df341f9e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -40,6 +40,9 @@ public class AppSettingsActivity extends ActionBarActivity { private AppSettingsFragment mSettingsFragment; private AccountsListFragment mAccountsListFragment; + // model + AppSettings mAppSettings; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -80,22 +83,39 @@ public class AppSettingsActivity extends ActionBarActivity { case R.id.menu_api_settings_revoke: revokeAccess(); return true; + case R.id.menu_api_settings_start: + startApp(); + return true; } return super.onOptionsItemSelected(item); } + private void startApp() { + Intent i; + PackageManager manager = getPackageManager(); + try { + i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName()); + if (i == null) + throw new PackageManager.NameNotFoundException(); + i.addCategory(Intent.CATEGORY_LAUNCHER); + startActivity(i); + } catch (PackageManager.NameNotFoundException e) { + Log.e(Constants.TAG, "startApp", e); + } + } + private void loadData(Bundle savedInstanceState, Uri appUri) { - AppSettings settings = new ProviderHelper(this).getApiAppSettings(appUri); - mSettingsFragment.setAppSettings(settings); + mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri); + mSettingsFragment.setAppSettings(mAppSettings); String appName; PackageManager pm = getPackageManager(); try { - ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0); + ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0); appName = (String) pm.getApplicationLabel(ai); } catch (PackageManager.NameNotFoundException e) { // fallback - appName = settings.getPackageName(); + appName = mAppSettings.getPackageName(); } setTitle(appName); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index 2fd1ad3b5..d0b958844 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -250,7 +250,7 @@ public class RemoteServiceActivity extends ActionBarActivity { // set text on view HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); - textView.setHtmlFromString(text); + textView.setHtmlFromString(text, true); /* Load select pub keys fragment */ // Check that the activity is using the layout version with @@ -292,7 +292,7 @@ public class RemoteServiceActivity extends ActionBarActivity { // set text on view HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); - textView.setHtmlFromString(text); + textView.setHtmlFromString(text, true); } else { Log.e(Constants.TAG, "Action does not exist!"); setResult(RESULT_CANCELED); 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 5365ed62c..ff599aaa1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -50,10 +50,10 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import org.sufficientlysecure.keychain.util.HkpKeyServer; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.KeychainServiceListener; +import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -75,7 +75,7 @@ import java.util.List; * after doing them. */ public class KeychainIntentService extends IntentService - implements Progressable, KeychainServiceListener { + implements Progressable, PgpImportExport.KeychainServiceListener { /* extras that can be given by intent */ public static final String EXTRA_MESSENGER = "messenger"; @@ -99,6 +99,7 @@ public class KeychainIntentService extends IntentService public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING"; public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING"; + public static final String ACTION_IMPORT_KEYBASE_KEYS = Constants.INTENT_PREFIX + "DOWNLOAD_KEYBASE"; public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; @@ -319,6 +320,7 @@ public class KeychainIntentService extends IntentService .setEncryptionMasterKeyIds(encryptionKeyIds) .setSymmetricPassphrase(symmetricPassphrase) .setSignatureMasterKeyId(signatureKeyId) + .setEncryptToSigner(true) .setSignatureHashAlgorithm( Preferences.getPreferences(this).getDefaultHashAlgorithm()) .setSignaturePassphrase( @@ -681,8 +683,7 @@ public class KeychainIntentService extends IntentService new String[]{KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET}, selection, null, null); try { - cursor.moveToFirst(); - do { + if (cursor != null && cursor.moveToFirst()) do { // export public either way publicMasterKeyIds.add(cursor.getLong(0)); // add secret if available (and requested) @@ -690,7 +691,9 @@ public class KeychainIntentService extends IntentService secretMasterKeyIds.add(cursor.getLong(0)); } while (cursor.moveToNext()); } finally { - cursor.close(); + if (cursor != null) { + cursor.close(); + } } PgpImportExport pgpImportExport = new PgpImportExport(this, this, this); @@ -729,6 +732,55 @@ public class KeychainIntentService extends IntentService } catch (Exception e) { sendErrorToHandler(e); } + } else if (ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { + ArrayList<ImportKeysListEntry> 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.getUserIds().get(1); + 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)) { try { ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 8f0a3ab8d..d5d02081a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -24,7 +24,8 @@ import android.os.Handler; import android.os.Message; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; -import android.widget.Toast; + +import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; @@ -99,9 +100,9 @@ public class KeychainIntentServiceHandler extends Handler { // show error from service if (data.containsKey(DATA_ERROR)) { - Toast.makeText(mActivity, + AppMsg.makeText(mActivity, mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)), - Toast.LENGTH_SHORT).show(); + AppMsg.STYLE_ALERT).show(); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index eb8ef003c..6c74818a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -26,10 +26,11 @@ import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.LoaderManager; +import android.support.v4.app.NavUtils; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; @@ -40,7 +41,6 @@ import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; @@ -64,7 +64,7 @@ import java.util.ArrayList; */ public class CertifyKeyActivity extends ActionBarActivity implements SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> { - private BootstrapButton mSignButton; + private View mSignButton; private CheckBox mUploadKeyCheckbox; private Spinner mSelectKeyserverSpinner; @@ -86,20 +86,16 @@ public class CertifyKeyActivity extends ActionBarActivity implements setContentView(R.layout.certify_key_activity); - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager() .findFragmentById(R.id.sign_key_select_key_fragment); mSelectKeyFragment.setCallback(this); mSelectKeyFragment.setFilterCertify(true); - mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver); + mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) - .getKeyServers()); + .getKeyServers() + ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSelectKeyserverSpinner.setAdapter(adapter); @@ -122,14 +118,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements } }); - mSignButton = (BootstrapButton) findViewById(R.id.sign_key_sign_button); + mSignButton = findViewById(R.id.sign_key_sign_button); mSignButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mPubKeyId != 0) { if (mMasterKeyId == 0) { - mSelectKeyFragment.setError(getString(R.string.select_key_to_sign)); + mSelectKeyFragment.setError(getString(R.string.select_key_to_certify)); } else { initiateSigning(); } @@ -145,7 +141,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements } Log.e(Constants.TAG, "uri: " + mDataUri); - mUserIds = (ListView) findViewById(R.id.user_ids); + mUserIds = (ListView) findViewById(R.id.view_key_user_ids); mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true); mUserIds.setAdapter(mUserIdsAdapter); @@ -201,7 +197,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT); String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob); - ((TextView) findViewById(R.id.fingerprint)) + ((TextView) findViewById(R.id.view_key_fingerprint)) .setText(PgpKeyHelper.colorizeFingerprint(fingerprint)); } break; @@ -318,7 +314,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements // fill values for this action Bundle data = new Bundle(); - Spinner keyServer = (Spinner) findViewById(R.id.sign_key_keyserver); + Spinner keyServer = (Spinner) findViewById(R.id.upload_key_keyserver); String server = (String) keyServer.getSelectedItem(); data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server); @@ -359,4 +355,17 @@ public class CertifyKeyActivity extends ActionBarActivity implements public void onKeySelected(long secretKeyId) { mMasterKeyId = secretKeyId; } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + Intent viewIntent = NavUtils.getParentActivityIntent(this); + viewIntent.setData(KeyRings.buildGenericKeyRingUri(mDataUri)); + NavUtils.navigateUpTo(this, viewIntent); + return true; + } + } + return super.onOptionsItemSelected(item); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 1877a43b5..5b21be6e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -27,7 +27,6 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; @@ -68,9 +67,6 @@ public class DecryptActivity extends DrawerActivity { setContentView(R.layout.decrypt_activity); - // set actionbar without home button if called from another app - ActionBarHelper.setBackButton(this); - initView(); setupDrawerNavigation(savedInstanceState); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index 0f88ee753..d953e2591 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -54,7 +54,7 @@ public class DecryptFileFragment extends DecryptFragment { private EditText mFilename; private CheckBox mDeleteAfter; private BootstrapButton mBrowse; - private BootstrapButton mDecryptButton; + private View mDecryptButton; private String mInputFilename = null; private String mOutputFilename = null; @@ -71,7 +71,7 @@ public class DecryptFileFragment extends DecryptFragment { mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename); mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse); mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); - mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt); + mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 6bb2209ea..955f8324e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -192,7 +192,7 @@ public class DecryptFragment extends Fragment { mLookupKey.setVisibility(View.GONE); // successful decryption-only - mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_blue)); + mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_purple)); mResultText.setText(R.string.decrypt_result_decrypted); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java index 454ee4415..daa48b526 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java @@ -47,8 +47,8 @@ public class DecryptMessageFragment extends DecryptFragment { // view private EditText mMessage; - private BootstrapButton mDecryptButton; - private BootstrapButton mDecryptFromCLipboardButton; + private View mDecryptButton; + private View mDecryptFromCLipboardButton; // model private String mCiphertext; @@ -61,8 +61,8 @@ public class DecryptMessageFragment extends DecryptFragment { View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false); mMessage = (EditText) view.findViewById(R.id.message); - mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt); - mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard); + mDecryptButton = view.findViewById(R.id.action_decrypt); + mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard); mDecryptButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -108,11 +108,11 @@ public class DecryptMessageFragment extends DecryptFragment { mCiphertext = matcher.group(1); decryptStart(null); } else { - AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO) + AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT) .show(); } } else { - AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO) + AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT) .show(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 529ea62a3..fdcb98976 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.widget.Editor; @@ -502,7 +503,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener int curID = 0; for (String userID : userIDs) { if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) { - AlertDialog.Builder alert = new AlertDialog.Builder( + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder( EditKeyActivity.this); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); @@ -525,7 +526,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener } ); alert.setCancelable(false); - alert.create().show(); + alert.show(); return; } curID++; @@ -614,7 +615,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener private void cancelClicked() { if (needsSaving()) { //ask if we want to save - AlertDialog.Builder alert = new AlertDialog.Builder( + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder( EditKeyActivity.this); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); @@ -637,7 +638,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener } }); alert.setCancelable(false); - alert.create().show(); + alert.show(); } else { setResult(RESULT_CANCELED); finish(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 6c71c641f..39d4a09bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -27,7 +27,6 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; @@ -145,26 +144,28 @@ public class EncryptActivity extends DrawerActivity implements setContentView(R.layout.encrypt_activity); - // set actionbar without home button if called from another app - ActionBarHelper.setBackButton(this); - initView(); - setupDrawerNavigation(savedInstanceState); + // if called with an intent action, do not init drawer navigation + if (ACTION_ENCRYPT.equals(getIntent().getAction())) { + // TODO: back button to key? + } else { + setupDrawerNavigation(savedInstanceState); + } // Handle intent actions handleActions(getIntent()); mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, - mAsymmetricFragmentBundle, getString(R.string.label_asymmetric)); + mAsymmetricFragmentBundle, getString(R.string.label_asymmetric)); mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, - mSymmetricFragmentBundle, getString(R.string.label_symmetric)); + mSymmetricFragmentBundle, getString(R.string.label_symmetric)); mViewPagerMode.setCurrentItem(mSwitchToMode); mTabsAdapterContent.addTab(EncryptMessageFragment.class, - mMessageFragmentBundle, getString(R.string.label_message)); + mMessageFragmentBundle, getString(R.string.label_message)); mTabsAdapterContent.addTab(EncryptFileFragment.class, - mFileFragmentBundle, getString(R.string.label_file)); + mFileFragmentBundle, getString(R.string.label_file)); mViewPagerContent.setCurrentItem(mSwitchToContent); } @@ -217,9 +218,9 @@ public class EncryptActivity extends DrawerActivity implements // preselect keys given by intent mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS, - encryptionKeyIds); + encryptionKeyIds); mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID, - signatureKeyId); + signatureKeyId); mSwitchToMode = PAGER_MODE_ASYMMETRIC; /** @@ -241,9 +242,10 @@ public class EncryptActivity extends DrawerActivity implements } else { Log.e(Constants.TAG, "Direct binary data without actual file in filesystem is not supported " + - "by Intents. Please use the Remote Service API!"); - Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) - .show(); + "by Intents. Please use the Remote Service API!" + ); + Toast.makeText(this, R.string.error_only_files_are_supported, + Toast.LENGTH_LONG).show(); // end activity finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index 0b6449cda..2b0c94f22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -220,9 +220,6 @@ public class EncryptAsymmetricFragment extends Fragment { private void selectPublicKeys() { Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class); Vector<Long> keyIds = new Vector<Long>(); - if (mSecretKeyId != 0) { - keyIds.add(mSecretKeyId); - } if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { for (int i = 0; i < mEncryptionKeyIds.length; ++i) { keyIds.add(mEncryptionKeyIds[i]); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index b8ac59dac..d150abdeb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -67,7 +67,7 @@ public class EncryptFileFragment extends Fragment { private CheckBox mDeleteAfter = null; private CheckBox mShareAfter = null; private BootstrapButton mBrowse = null; - private BootstrapButton mEncryptFile; + private View mEncryptFile; private FileDialogFragment mFileDialog; @@ -92,7 +92,7 @@ public class EncryptFileFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false); - mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file); + mEncryptFile = view.findViewById(R.id.action_encrypt_file); mEncryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java index 0a1a3474a..4c35806e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -28,9 +28,8 @@ import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; +import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; @@ -46,9 +45,9 @@ import org.sufficientlysecure.keychain.util.Log; public class EncryptMessageFragment extends Fragment { public static final String ARG_TEXT = "text"; - private EditText mMessage = null; - private BootstrapButton mEncryptShare; - private BootstrapButton mEncryptClipboard; + private TextView mMessage = null; + private View mEncryptShare; + private View mEncryptClipboard; private EncryptActivityInterface mEncryptInterface; @@ -70,9 +69,9 @@ public class EncryptMessageFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false); - mMessage = (EditText) view.findViewById(R.id.message); - mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard); - mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share); + mMessage = (TextView) view.findViewById(R.id.message); + mEncryptClipboard = view.findViewById(R.id.action_encrypt_clipboard); + mEncryptShare = view.findViewById(R.id.action_encrypt_share); mEncryptClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java index ec9f3a759..5909970af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java @@ -45,7 +45,7 @@ public class HelpAboutFragment extends Fragment { HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text); // load html from raw resource (Parsing handled by HtmlTextView library) - aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about); + aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about, true); // no flickering when clicking textview for Android < 4 aboutTextView.setTextColor(getResources().getColor(android.R.color.black)); 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 e81bc1563..cf7446a58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -24,7 +24,9 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; +import org.sufficientlysecure.keychain.util.SlidingTabLayout; public class HelpActivity extends ActionBarActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; @@ -37,25 +39,27 @@ public class HelpActivity extends ActionBarActivity { public static final int TAB_ABOUT = 5; ViewPager mViewPager; - TabsAdapter mTabsAdapter; + private PagerTabStripAdapter mTabsAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.help_activity); - - mViewPager = (ViewPager) findViewById(R.id.pager); - final ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setHomeButtonEnabled(false); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - mTabsAdapter = new TabsAdapter(this, mViewPager); + setContentView(R.layout.help_activity); + + mViewPager = (ViewPager) findViewById(R.id.pager); + SlidingTabLayout slidingTabLayout = + (SlidingTabLayout) findViewById(R.id.sliding_tab_layout); + + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); - int selectedTab = 0; + int selectedTab = TAB_START; Intent intent = getIntent(); if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); @@ -63,30 +67,36 @@ public class HelpActivity extends ActionBarActivity { Bundle startBundle = new Bundle(); startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), - HelpHtmlFragment.class, startBundle, (selectedTab == TAB_START)); + mTabsAdapter.addTab(HelpHtmlFragment.class, startBundle, + getString(R.string.help_tab_start)); Bundle faqBundle = new Bundle(); faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_faq)), - HelpHtmlFragment.class, faqBundle, (selectedTab == TAB_FAQ)); + mTabsAdapter.addTab(HelpHtmlFragment.class, faqBundle, + getString(R.string.help_tab_faq)); Bundle wotBundle = new Bundle(); wotBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_wot); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_wot)), - HelpHtmlFragment.class, wotBundle, (selectedTab == TAB_WOT)); + mTabsAdapter.addTab(HelpHtmlFragment.class, wotBundle, + getString(R.string.help_tab_wot)); Bundle nfcBundle = new Bundle(); nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), - HelpHtmlFragment.class, nfcBundle, (selectedTab == TAB_NFC)); + mTabsAdapter.addTab(HelpHtmlFragment.class, nfcBundle, + getString(R.string.help_tab_nfc_beam)); Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), - HelpHtmlFragment.class, changelogBundle, (selectedTab == TAB_CHANGELOG)); + mTabsAdapter.addTab(HelpHtmlFragment.class, changelogBundle, + getString(R.string.help_tab_changelog)); + + mTabsAdapter.addTab(HelpAboutFragment.class, null, + getString(R.string.help_tab_about)); + + // NOTE: must be after adding the tabs! + slidingTabLayout.setViewPager(mViewPager); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), - HelpAboutFragment.class, null, (selectedTab == TAB_ABOUT)); + // switch to tab selected by extra + mViewPager.setCurrentItem(selectedTab); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java index 98c84e5e3..a3f0ef614 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java @@ -66,7 +66,7 @@ public class HelpHtmlFragment extends Fragment { scroller.addView(text); // load html from raw resource (Parsing handled by HtmlTextView library) - text.setHtmlFromRawResource(getActivity(), mHtmlFile); + text.setHtmlFromRawResource(getActivity(), mHtmlFile, true); // no flickering when clicking textview for Android < 4 text.setTextColor(getResources().getColor(android.R.color.black)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 0fccd668f..35076287b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -38,16 +38,14 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; -import com.beardedhen.androidbootstrap.BootstrapButton; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.dialog.BadImportKeyDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -62,6 +60,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O + "IMPORT_KEY_FROM_KEYSERVER"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN"; + public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX + + "IMPORT_KEY_FROM_KEYBASE"; // Actions for internal use only: public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX @@ -85,20 +85,22 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O private ImportKeysListFragment mListFragment; private String[] mNavigationStrings; private Fragment mCurrentFragment; - private BootstrapButton mImportButton; + private View mImportButton; private static final Class[] NAVIGATION_CLASSES = new Class[]{ ImportKeysServerFragment.class, ImportKeysFileFragment.class, ImportKeysQrCodeFragment.class, ImportKeysClipboardFragment.class, - ImportKeysNFCFragment.class + ImportKeysNFCFragment.class, + ImportKeysKeybaseFragment.class }; private static final int NAV_SERVER = 0; private static final int NAV_FILE = 1; private static final int NAV_QR_CODE = 2; private static final int NAV_CLIPBOARD = 3; private static final int NAV_NFC = 4; + private static final int NAV_KEYBASE = 5; private int mCurrentNavPosition = -1; @@ -108,7 +110,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O setContentView(R.layout.import_keys_activity); - mImportButton = (BootstrapButton) findViewById(R.id.import_import); + mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -121,7 +123,6 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { setTitle(R.string.nav_import); } else { - ActionBarHelper.setBackButton(this); getSupportActionBar().setDisplayShowTitleEnabled(false); // set drop down navigation @@ -238,6 +239,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // no immediate actions! startListFragment(savedInstanceState, null, null, null); + } else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) { + // NOTE: this only displays the appropriate fragment, no actions are taken + loadNavFragment(NAV_KEYBASE, null); + + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); } else { startListFragment(savedInstanceState, null, null, null); } @@ -340,8 +347,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O startListFragment(savedInstanceState, null, null, query); } - public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) { - mListFragment.loadNew(importData, dataUri, serverQuery, keyServer); + public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) { + mListFragment.loadNew(importData, dataUri, serverQuery, keyServer, keybaseQuery); } /** @@ -449,6 +456,31 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // start service with intent startService(intent); + } else if (mListFragment.getKeybaseQuery() != null) { + // Send all information needed to service to query keys in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYBASE_KEYS); + + // fill values for this action + Bundle data = new Bundle(); + + // get selected key entries + ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData(); + data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + } else { AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java index 412fbddd8..f331358fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java @@ -71,7 +71,7 @@ public class ImportKeysClipboardFragment extends Fragment { return; } } - mImportActivity.loadCallback(sendText.getBytes(), null, null, null); + mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index dc5333a8f..51f961aab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -85,7 +85,7 @@ public class ImportKeysFileFragment extends Fragment { if (resultCode == Activity.RESULT_OK && data != null) { // load data - mImportActivity.loadCallback(null, data.getData(), null, null); + mImportActivity.loadCallback(null, data.getData(), null, null, null); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java new file mode 100644 index 000000000..a996079c9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Context; +import android.support.v4.app.Fragment; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +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. + * TODO: make a pick list of the people you’re following on keybase + */ +public class ImportKeysKeybaseFragment extends Fragment { + + private ImportKeysActivity mImportActivity; + private BootstrapButton mSearchButton; + private EditText mQueryEditText; + + public static final String ARG_QUERY = "query"; + + /** + * Creates new instance of this fragment + */ + public static ImportKeysKeybaseFragment newInstance() { + ImportKeysKeybaseFragment frag = new ImportKeysKeybaseFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.import_keys_keybase_fragment, container, false); + + mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query); + + mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search); + mSearchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String query = mQueryEditText.getText().toString(); + search(query); + + // close keyboard after pressing search + InputMethodManager imm = + (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mQueryEditText.getWindowToken(), 0); + } + }); + + mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + String query = mQueryEditText.getText().toString(); + search(query); + + // Don't return true to let the keyboard close itself after pressing search + return false; + } + return false; + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mImportActivity = (ImportKeysActivity) getActivity(); + + // set displayed values + if (getArguments() != null) { + if (getArguments().containsKey(ARG_QUERY)) { + String query = getArguments().getString(ARG_QUERY); + mQueryEditText.setText(query, TextView.BufferType.EDITABLE); + } + } + } + + private void search(String query) { + mImportActivity.loadCallback(null, null, null, null, query); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 3a6c384e8..bf6abcd6f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -33,11 +33,12 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader; import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.KeyServer; +import org.sufficientlysecure.keychain.keyimport.KeyServer; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayInputStream; @@ -60,9 +61,11 @@ public class ImportKeysListFragment extends ListFragment implements private Uri mDataUri; private String mServerQuery; private String mKeyServer; + private String mKeybaseQuery; private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_SERVER_QUERY = 1; + private static final int LOADER_ID_KEYBASE = 2; public byte[] getKeyBytes() { return mKeyBytes; @@ -76,6 +79,10 @@ public class ImportKeysListFragment extends ListFragment implements return mServerQuery; } + public String getKeybaseQuery() { + return mKeybaseQuery; + } + public String getKeyServer() { return mKeyServer; } @@ -148,6 +155,16 @@ public class ImportKeysListFragment extends ListFragment implements // give arguments to onCreateLoader() getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this); } + + if (mKeybaseQuery != null) { + // Start out with a progress indicator. + setListShown(false); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + // give arguments to onCreateLoader() + getLoaderManager().initLoader(LOADER_ID_KEYBASE, null, this); + } } @Override @@ -157,16 +174,18 @@ public class ImportKeysListFragment extends ListFragment implements // Select checkbox! // Update underlying data and notify adapter of change. The adapter will // update the view automatically. + ImportKeysListEntry entry = mAdapter.getItem(position); entry.setSelected(!entry.isSelected()); mAdapter.notifyDataSetChanged(); } - public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer) { + public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) { mKeyBytes = keyBytes; mDataUri = dataUri; mServerQuery = serverQuery; mKeyServer = keyServer; + mKeybaseQuery = keybaseQuery; if (mKeyBytes != null || mDataUri != null) { // Start out with a progress indicator. @@ -181,6 +200,13 @@ public class ImportKeysListFragment extends ListFragment implements getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this); } + + if (mKeybaseQuery != null) { + // Start out with a progress indicator. + setListShown(false); + + getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this); + } } @Override @@ -194,6 +220,9 @@ public class ImportKeysListFragment extends ListFragment implements case LOADER_ID_SERVER_QUERY: { return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer); } + case LOADER_ID_KEYBASE: { + return new ImportKeysListKeybaseLoader(getActivity(), mKeybaseQuery); + } default: return null; @@ -248,7 +277,7 @@ public class ImportKeysListFragment extends ListFragment implements if (error == null) { AppMsg.makeText( getActivity(), getResources().getQuantityString(R.plurals.keys_found, - mAdapter.getCount(), mAdapter.getCount()), + mAdapter.getCount(), mAdapter.getCount()), AppMsg.STYLE_INFO ).show(); } else if (error instanceof KeyServer.InsufficientQuery) { @@ -263,6 +292,19 @@ public class ImportKeysListFragment extends ListFragment implements } break; + case LOADER_ID_KEYBASE: + + if (error == null) { + AppMsg.makeText( + getActivity(), getResources().getQuantityString(R.plurals.keys_found, + mAdapter.getCount(), mAdapter.getCount()), + AppMsg.STYLE_INFO + ).show(); + } else if (error instanceof KeyServer.QueryException) { + AppMsg.makeText(getActivity(), R.string.error_keyserver_query, + AppMsg.STYLE_ALERT).show(); + } + default: break; } @@ -279,6 +321,10 @@ public class ImportKeysListFragment extends ListFragment implements // Clear the data in the adapter. mAdapter.clear(); break; + case LOADER_ID_KEYBASE: + // Clear the data in the adapter. + mAdapter.clear(); + break; default: break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index 65d463456..22b56e1ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -117,7 +117,7 @@ public class ImportKeysQrCodeFragment extends Fragment { // is this a full key encoded as qr code? if (scannedContent.startsWith("-----BEGIN PGP")) { - mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null); + mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null, null); return; } @@ -197,7 +197,7 @@ public class ImportKeysQrCodeFragment extends Fragment { for (String in : mScannedContent) { result += in; } - mImportActivity.loadCallback(result.getBytes(), null, null, null); + mImportActivity.loadCallback(result.getBytes(), null, null, null, null); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 82fe2fc4c..9e3d88ff5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -151,7 +151,7 @@ public class ImportKeysServerFragment extends Fragment { } private void search(String query, String keyServer) { - mImportActivity.loadCallback(null, null, query, keyServer); + mImportActivity.loadCallback(null, null, query, keyServer, null); } } 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 82f65a962..3fd958bcc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -29,7 +29,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -45,11 +44,10 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.animation.AnimationUtils; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; -import android.widget.Button; import android.widget.FrameLayout; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; @@ -77,18 +75,13 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersListView; * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. */ -public class KeyListFragment extends Fragment +public class KeyListFragment extends LoaderFragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; - // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - boolean mListShown; - View mProgressContainer; - View mListContainer; - private String mCurQuery; private SearchView mSearchView; // empty list layout @@ -100,14 +93,15 @@ public class KeyListFragment extends Fragment * Load custom layout with StickyListView from library */ @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.key_list_fragment, container, false); + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.key_list_fragment, getContainer()); - mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list); + mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list); mStickyList.setOnItemClickListener(this); // empty view - mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create); + mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create); mButtonEmptyCreate.setOnClickListener(new OnClickListener() { @Override @@ -119,7 +113,7 @@ public class KeyListFragment extends Fragment startActivityForResult(intent, 0); } }); - mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import); + mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import); mButtonEmptyImport.setOnClickListener(new OnClickListener() { @Override @@ -130,11 +124,6 @@ public class KeyListFragment extends Fragment } }); - // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - mListContainer = root.findViewById(R.id.key_list_list_container); - mProgressContainer = root.findViewById(R.id.key_list_progress_container); - mListShown = true; - return root; } @@ -233,9 +222,8 @@ public class KeyListFragment extends Fragment // We have a menu item to show in action bar. setHasOptionsMenu(true); - // NOTE: Not supported by StickyListHeader, but reimplemented here // Start out with a progress indicator. - setListShown(false); + setContentShown(false); // Create an empty adapter we will use to display the loaded data. mAdapter = new KeyListAdapter(getActivity(), null, 0); @@ -297,12 +285,11 @@ public class KeyListFragment extends Fragment // this view is made visible if no data is available mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); - // NOTE: Not supported by StickyListHeader, but reimplemented here // The list should now be shown. if (isResumed()) { - setListShown(true); + setContentShown(true); } else { - setListShownNoAnimation(true); + setContentShownNoAnimation(true); } } @@ -380,6 +367,9 @@ public class KeyListFragment extends Fragment // Execute this when searching mSearchView.setOnQueryTextListener(this); + View searchPlate = mSearchView.findViewById(android.support.v7.appcompat.R.id.search_plate); + searchPlate.setBackgroundResource(R.drawable.keychaintheme_searchview_holo_light); + // Erase search result without focus MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { @Override @@ -414,43 +404,6 @@ public class KeyListFragment extends Fragment return true; } - // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - public void setListShown(boolean shown, boolean animate) { - if (mListShown == shown) { - return; - } - mListShown = shown; - if (shown) { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - } - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } else { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - } - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } - } - - // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - public void setListShown(boolean shown) { - setListShown(shown, true); - } - - // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097 - public void setListShownNoAnimation(boolean shown) { - setListShown(shown, false); - } - /** * Implements StickyListHeadersAdapter from library */ @@ -475,7 +428,7 @@ public class KeyListFragment extends Fragment TextView mMainUserIdRest; View mStatusDivider; FrameLayout mStatusLayout; - Button mButton; + ImageButton mButton; TextView mRevoked; ImageView mVerified; } @@ -488,7 +441,7 @@ public class KeyListFragment extends Fragment holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); holder.mStatusDivider = (View) view.findViewById(R.id.status_divider); holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout); - holder.mButton = (Button) view.findViewById(R.id.edit); + holder.mButton = (ImageButton) view.findViewById(R.id.edit); holder.mRevoked = (TextView) view.findViewById(R.id.revoked); holder.mVerified = (ImageView) view.findViewById(R.id.verified); view.setTag(holder); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LoaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LoaderFragment.java new file mode 100644 index 000000000..87ab1bb8c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LoaderFragment.java @@ -0,0 +1,78 @@ +package org.sufficientlysecure.keychain.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import org.sufficientlysecure.keychain.R; + +/** This is a fragment helper class, which implements a generic + * progressbar/container view. + * + * To use it in a fragment, simply subclass, use onCreateView to create the + * layout's root view, and ues getContainer() as root view of your subclass. + * The layout shows a progress bar by default, and can be switched to the + * actual contents by calling setContentShown(). + * + */ +public class LoaderFragment extends Fragment { + private boolean mContentShown; + private View mProgressContainer; + private ViewGroup mContainer; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.loader_layout, container, false); + + mContentShown = true; + mContainer = (ViewGroup) root.findViewById(R.id.loader_container); + mProgressContainer = root.findViewById(R.id.loader_progress); + + // content is not shown (by visibility statuses in the layout files) + mContentShown = false; + + return root; + + } + + protected ViewGroup getContainer() { + return mContainer; + } + + public void setContentShown(boolean shown, boolean animate) { + if (mContentShown == shown) { + return; + } + mContentShown = shown; + if (shown) { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + mContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + } + mProgressContainer.setVisibility(View.GONE); + mContainer.setVisibility(View.VISIBLE); + } else { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + mContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + } + mProgressContainer.setVisibility(View.VISIBLE); + mContainer.setVisibility(View.INVISIBLE); + } + } + + public void setContentShown(boolean shown) { + setContentShown(shown, true); + } + + public void setContentShownNoAnimation(boolean shown) { + setContentShown(shown, false); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java index 6de03198e..38a0c8478 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java @@ -56,7 +56,7 @@ public class SelectSecretKeyFragment extends ListFragment implements Bundle args = new Bundle(); args.putBoolean(ARG_FILTER_CERTIFY, filterCertify); - args.putBoolean(ARG_FILTER_CERTIFY, filterSign); + args.putBoolean(ARG_FILTER_SIGN, filterSign); frag.setArguments(args); return frag; @@ -124,7 +124,8 @@ public class SelectSecretKeyFragment extends ListFragment implements KeyRings.CAN_CERTIFY, // has sign may be any subkey KeyRings.HAS_SIGN, - KeyRings.HAS_ANY_SECRET + KeyRings.HAS_ANY_SECRET, + KeyRings.HAS_SECRET }; String where = KeyRings.HAS_ANY_SECRET + " = 1"; @@ -158,7 +159,7 @@ public class SelectSecretKeyFragment extends ListFragment implements private class SelectSecretKeyCursorAdapter extends SelectKeyCursorAdapter { - private int mIndexHasSign, mIndexCanCertify; + private int mIndexHasSign, mIndexCanCertify, mIndexHasSecret; public SelectSecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { super(context, c, flags, listView); @@ -170,6 +171,7 @@ public class SelectSecretKeyFragment extends ListFragment implements if (cursor != null) { mIndexCanCertify = cursor.getColumnIndexOrThrow(KeyRings.CAN_CERTIFY); mIndexHasSign = cursor.getColumnIndexOrThrow(KeyRings.HAS_SIGN); + mIndexHasSecret = cursor.getColumnIndexOrThrow(KeyRings.HAS_SECRET); } } @@ -187,7 +189,8 @@ public class SelectSecretKeyFragment extends ListFragment implements // Check if key is viable for our purposes (certify or sign) if(mFilterCertify) { // Only enable if can certify - if (cursor.getInt(mIndexCanCertify) == 0) { + if (cursor.getInt(mIndexCanCertify) == 0 + || cursor.getInt(mIndexHasSecret) == 0) { h.status.setText(R.string.can_certify_not); } else { h.status.setText(R.string.can_certify); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 97a2af645..dbd1b7507 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -23,15 +23,15 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBarActivity; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.Toast; -import com.beardedhen.androidbootstrap.BootstrapButton; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; @@ -44,7 +44,7 @@ import org.sufficientlysecure.keychain.util.Log; * Sends the selected public key to a keyserver */ public class UploadKeyActivity extends ActionBarActivity { - private BootstrapButton mUploadButton; + private View mUploadButton; private Spinner mKeyServerSpinner; private Uri mDataUri; @@ -53,10 +53,10 @@ public class UploadKeyActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.key_server_export); + setContentView(R.layout.upload_key_activity); - mUploadButton = (BootstrapButton) findViewById(R.id.btn_export_to_server); - mKeyServerSpinner = (Spinner) findViewById(R.id.sign_key_keyserver); + mUploadButton = findViewById(R.id.upload_key_action_upload); + mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) @@ -129,4 +129,17 @@ public class UploadKeyActivity extends ActionBarActivity { // start service with intent startService(intent); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + Intent viewIntent = NavUtils.getParentActivityIntent(this); + viewIntent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(mDataUri)); + NavUtils.navigateUpTo(this, viewIntent); + return true; + } + } + return super.onOptionsItemSelected(item); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index 1d2b50faa..cf7fdcd89 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -28,7 +28,6 @@ import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.text.format.DateFormat; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; @@ -73,11 +72,12 @@ public class ViewCertActivity extends ActionBarActivity private Uri mDataUri; - private long mSignerKeyId; + private long mCertifierKeyId; - private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mRReason, mCreation; - private TextView mSignerKey, mSignerUid, mStatus; + private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mReason, mCreation; + private TextView mCertifierKey, mCertifierUid, mStatus; private View mRowReason; + private View mViewCertifierButton; @Override protected void onCreate(Bundle savedInstanceState) { @@ -93,14 +93,16 @@ public class ViewCertActivity extends ActionBarActivity mSigneeUid = (TextView) findViewById(R.id.signee_uid); mAlgorithm = (TextView) findViewById(R.id.algorithm); mType = (TextView) findViewById(R.id.signature_type); - mRReason = (TextView) findViewById(R.id.reason); + mReason = (TextView) findViewById(R.id.reason); mCreation = (TextView) findViewById(R.id.creation); - mSignerKey = (TextView) findViewById(R.id.signer_key_id); - mSignerUid = (TextView) findViewById(R.id.signer_uid); + mCertifierKey = (TextView) findViewById(R.id.signer_key_id); + mCertifierUid = (TextView) findViewById(R.id.signer_uid); mRowReason = findViewById(R.id.row_reason); + mViewCertifierButton = findViewById(R.id.view_cert_view_cert_key); + mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); @@ -121,7 +123,7 @@ public class ViewCertActivity extends ActionBarActivity @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (data.moveToFirst()) { - String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID)); + String signeeKey = PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID)); mSigneeKey.setText(signeeKey); String signeeUid = data.getString(INDEX_USER_ID); @@ -130,22 +132,24 @@ public class ViewCertActivity extends ActionBarActivity Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000); mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate)); - mSignerKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER); - String signerKey = "0x" + PgpKeyHelper.convertKeyIdToHex(mSignerKeyId); - mSignerKey.setText(signerKey); + mCertifierKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER); + String certifierKey = PgpKeyHelper.convertKeyIdToHex(mCertifierKeyId); + mCertifierKey.setText(certifierKey); - String signerUid = data.getString(INDEX_SIGNER_UID); - if (signerUid != null) { - mSignerUid.setText(signerUid); + String certifierUid = data.getString(INDEX_SIGNER_UID); + if (certifierUid != null) { + mCertifierUid.setText(certifierUid); } else { - mSignerUid.setText(R.string.unknown_uid); + mCertifierUid.setText(R.string.unknown_uid); } PGPSignature sig = PgpConversionHelper.BytesToPGPSignature(data.getBlob(INDEX_DATA)); try { ProviderHelper providerHelper = new ProviderHelper(this); + CachedPublicKeyRing signeeRing = providerHelper.getCachedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID)); CachedPublicKeyRing signerRing = providerHelper.getCachedPublicKeyRing(sig.getKeyID()); + try { signerRing.getSubkey().initSignature(sig); if (signeeRing.getSubkey().verifySignature(sig, signeeUid)) { @@ -191,25 +195,39 @@ public class ViewCertActivity extends ActionBarActivity p = new RevocationReason(false, p.getData()); } String reason = ((RevocationReason) p).getRevocationDescription(); - mRReason.setText(reason); + mReason.setText(reason); mRowReason.setVisibility(View.VISIBLE); } break; } } } - } - @Override - public void onLoaderReset(Loader<Cursor> loader) { - } + // can't do this before the data is initialized + mViewCertifierButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent viewIntent = new Intent(ViewCertActivity.this, ViewKeyActivity.class); + try { + ProviderHelper providerHelper = new ProviderHelper(ViewCertActivity.this); + long signerMasterKeyId = providerHelper.getMasterKeyId( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mCertifierKeyId)) + ); + viewIntent.setData(KeyRings.buildGenericKeyRingUri( + Long.toString(signerMasterKeyId)) + ); + startActivity(viewIntent); + } catch (ProviderHelper.NotFoundException e) { + // TODO notify user of this, maybe offer download? + Log.e(Constants.TAG, "key not found!", e); + } + } + }); + } @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.view_cert, menu); - return true; + public void onLoaderReset(Loader<Cursor> loader) { } @Override @@ -221,25 +239,6 @@ public class ViewCertActivity extends ActionBarActivity NavUtils.navigateUpTo(this, viewIntent); return true; } - case R.id.menu_view_cert_view_signer: - // can't do this before the data is initialized - Intent viewIntent = new Intent(this, ViewKeyActivity.class); - - try { - ProviderHelper providerHelper = new ProviderHelper(this); - long signerMasterKeyId = providerHelper.getMasterKeyId( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId)) - ); - viewIntent.setData(KeyRings.buildGenericKeyRingUri( - Long.toString(signerMasterKeyId)) - ); - startActivity(viewIntent); - } catch (ProviderHelper.NotFoundException e) { - // TODO notify user of this, maybe offer download? - Log.e(Constants.TAG, "key not found!", e); - } - - return true; } return super.onOptionsItemSelected(item); } 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 79f409663..bed116f5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.Activity; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -31,41 +32,54 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.Window; import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; -import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.SlidingTabLayout; import java.io.IOException; +import java.util.Date; import java.util.HashMap; -public class ViewKeyActivity extends ActionBarActivity { +public class ViewKeyActivity extends ActionBarActivity implements + LoaderManager.LoaderCallbacks<Cursor> { ExportHelper mExportHelper; ProviderHelper mProviderHelper; protected Uri mDataUri; - public static final String EXTRA_SELECTED_TAB = "selectedTab"; + public static final String EXTRA_SELECTED_TAB = "selected_tab"; + public static final int TAB_MAIN = 0; + public static final int TAB_SHARE = 1; + public static final int TAB_KEYS = 2; + public static final int TAB_CERTS = 3; - ViewPager mViewPager; - TabsAdapter mTabsAdapter; + // view + private ViewPager mViewPager; + private SlidingTabLayout mSlidingTabLayout; + private PagerTabStripAdapter mTabsAdapter; + private View mStatusDivider; + private View mStatusRevoked; + private View mStatusExpired; public static final int REQUEST_CODE_LOOKUP_KEY = 0x00007006; @@ -76,6 +90,9 @@ public class ViewKeyActivity extends ActionBarActivity { private byte[] mNfcKeyringBytes; private static final int NFC_SENT = 1; + private static final int LOADER_ID_UNIFIED = 0; + + @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); @@ -89,33 +106,71 @@ public class ViewKeyActivity extends ActionBarActivity { actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setIcon(android.R.color.transparent); actionBar.setHomeButtonEnabled(true); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); setContentView(R.layout.view_key_activity); - mViewPager = (ViewPager) findViewById(R.id.pager); + mStatusDivider = findViewById(R.id.status_divider); + mStatusRevoked = findViewById(R.id.view_key_revoked); + mStatusExpired = findViewById(R.id.view_key_expired); - mTabsAdapter = new TabsAdapter(this, mViewPager); + mViewPager = (ViewPager) findViewById(R.id.view_key_pager); + mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout); - int selectedTab = 0; + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); + + int switchToTab = TAB_MAIN; Intent intent = getIntent(); if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { - selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); + switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); + } + + Uri dataUri = getIntent().getData(); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + finish(); + return; } - mDataUri = getIntent().getData(); + loadData(dataUri); - initNfc(mDataUri); + initNfc(dataUri); Bundle mainBundle = new Bundle(); - mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)), - ViewKeyMainFragment.class, mainBundle, (selectedTab == 0)); + mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyMainFragment.class, + mainBundle, getString(R.string.key_view_tab_main)); + + Bundle shareBundle = new Bundle(); + shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyShareFragment.class, + mainBundle, getString(R.string.key_view_tab_share)); + + Bundle keyDetailsBundle = new Bundle(); + keyDetailsBundle.putParcelable(ViewKeyKeysFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyKeysFragment.class, + keyDetailsBundle, getString(R.string.key_view_tab_keys)); Bundle certBundle = new Bundle(); - certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri); - mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)), - ViewKeyCertsFragment.class, certBundle, (selectedTab == 1)); + certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, dataUri); + mTabsAdapter.addTab(ViewKeyCertsFragment.class, + certBundle, getString(R.string.key_view_tab_certs)); + + // NOTE: must be after adding the tabs! + mSlidingTabLayout.setViewPager(mViewPager); + + // switch to tab selected by extra + mViewPager.setCurrentItem(switchToTab); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); } @Override @@ -143,24 +198,6 @@ public class ViewKeyActivity extends ActionBarActivity { case R.id.menu_key_view_export_file: exportToFile(mDataUri, mExportHelper, mProviderHelper); return true; - case R.id.menu_key_view_share_default_fingerprint: - shareKey(mDataUri, true, mProviderHelper); - return true; - case R.id.menu_key_view_share_default: - shareKey(mDataUri, false, mProviderHelper); - return true; - case R.id.menu_key_view_share_qr_code_fingerprint: - shareKeyQrCode(mDataUri, true); - return true; - case R.id.menu_key_view_share_qr_code: - shareKeyQrCode(mDataUri, false); - return true; - case R.id.menu_key_view_share_nfc: - shareNfc(); - return true; - case R.id.menu_key_view_share_clipboard: - copyToClipboard(mDataUri, mProviderHelper); - return true; case R.id.menu_key_view_delete: { deleteKey(mDataUri, mExportHelper); return true; @@ -209,84 +246,6 @@ public class ViewKeyActivity extends ActionBarActivity { startActivityForResult(queryIntent, REQUEST_CODE_LOOKUP_KEY); } - private void shareKey(Uri dataUri, boolean fingerprintOnly, ProviderHelper providerHelper) - throws ProviderHelper.NotFoundException { - String content = null; - if (fingerprintOnly) { - byte[] data = (byte[]) providerHelper.getGenericData( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), - KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - if (data != null) { - String fingerprint = PgpKeyHelper.convertFingerprintToHex(data); - content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; - } else { - AppMsg.makeText(this, "Bad key selected!", - AppMsg.STYLE_ALERT).show(); - return; - } - } else { - // get public keyring as ascii armored string - try { - Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); - content = providerHelper.getKeyRingAsArmoredString(uri); - - // Android will fail with android.os.TransactionTooLargeException if key is too big - // see http://www.lonestarprod.com/?p=34 - if (content.length() >= 86389) { - AppMsg.makeText(this, R.string.key_too_big_for_sharing, - AppMsg.STYLE_ALERT).show(); - return; - } - } catch (IOException e) { - Log.e(Constants.TAG, "error processing key!", e); - AppMsg.makeText(this, R.string.error_invalid_data, AppMsg.STYLE_ALERT).show(); - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); - } - } - - if (content != null) { - // let user choose application - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, content); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, - getResources().getText(R.string.action_share_key_with))); - } else { - Log.e(Constants.TAG, "content is null!"); - } - } - - private void shareKeyQrCode(Uri dataUri, boolean fingerprintOnly) { - ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(dataUri, - fingerprintOnly); - dialog.show(getSupportFragmentManager(), "shareQrCodeDialog"); - } - - private void copyToClipboard(Uri dataUri, ProviderHelper providerHelper) { - // get public keyring as ascii armored string - try { - Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); - String keyringArmored = providerHelper.getKeyRingAsArmoredString(uri); - - ClipboardReflection.copyToClipboard(this, keyringArmored); - AppMsg.makeText(this, R.string.key_copied_to_clipboard, AppMsg.STYLE_INFO) - .show(); - } catch (IOException e) { - Log.e(Constants.TAG, "error processing key!", e); - AppMsg.makeText(this, R.string.error_key_processing, AppMsg.STYLE_ALERT).show(); - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); - } - } - - private void shareNfc() { - ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance(); - dialog.show(getSupportFragmentManager(), "shareNfcDialog"); - } - private void deleteKey(Uri dataUri, ExportHelper exportHelper) { // Message is received after key is deleted Handler returnHandler = new Handler() { @@ -409,4 +368,85 @@ public class ViewKeyActivity extends ActionBarActivity { } }; + static final String[] UNIFIED_PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.EXPIRY, + + }; + static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; + static final int INDEX_UNIFIED_USER_ID = 2; + static final int INDEX_UNIFIED_IS_REVOKED = 3; + static final int INDEX_UNIFIED_EXPIRY = 4; + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(this, baseUri, UNIFIED_PROJECTION, null, null, null); + } + + default: + return null; + } + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + if (data.moveToFirst()) { + // get name, email, and comment from USER_ID + String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_USER_ID)); + if (mainUserId[0] != null) { + setTitle(mainUserId[0]); + } else { + setTitle(R.string.user_id_no_name); + } + + // get key id from MASTER_KEY_ID + long masterKeyId = data.getLong(INDEX_UNIFIED_MASTER_KEY_ID); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId); + getSupportActionBar().setSubtitle(keyIdStr); + + // If this key is revoked, it cannot be used for anything! + if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) { + mStatusDivider.setVisibility(View.VISIBLE); + mStatusRevoked.setVisibility(View.VISIBLE); + mStatusExpired.setVisibility(View.GONE); + } else { + mStatusRevoked.setVisibility(View.GONE); + + Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000); + if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) { + mStatusDivider.setVisibility(View.VISIBLE); + mStatusExpired.setVisibility(View.VISIBLE); + } else { + mStatusDivider.setVisibility(View.GONE); + mStatusExpired.setVisibility(View.GONE); + } + } + + break; + } + } + } + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java index 3c4135715..d5658586d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -23,7 +23,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -46,7 +45,7 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; -public class ViewKeyCertsFragment extends Fragment +public class ViewKeyCertsFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener { // These are the rows that we will retrieve. @@ -75,19 +74,23 @@ public class ViewKeyCertsFragment extends Fragment private Uri mDataUri; + // starting with 4 for this fragment + private static final int LOADER_ID = 4; + @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false); + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_certs_fragment, getContainer()); + + mStickyList = (StickyListHeadersListView) view.findViewById(R.id.list); - return view; + return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); - if (!getArguments().containsKey(ARG_DATA_URI)) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); @@ -112,11 +115,12 @@ public class ViewKeyCertsFragment extends Fragment mAdapter = new CertListAdapter(getActivity(), null); mStickyList.setAdapter(mAdapter); - getLoaderManager().initLoader(0, null, this); + getLoaderManager().initLoader(LOADER_ID, null, this); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, SORT_ORDER); @@ -129,6 +133,8 @@ public class ViewKeyCertsFragment extends Fragment mAdapter.swapCursor(data); mStickyList.setAdapter(mAdapter); + + setContentShown(true); } /** @@ -208,11 +214,18 @@ public class ViewKeyCertsFragment extends Fragment // set name and stuff, common to both key types TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId); - TextView wSignerUserId = (TextView) view.findViewById(R.id.signerUserId); + TextView wSignerName = (TextView) view.findViewById(R.id.signerName); TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); String signerKeyId = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexSignerKeyId)); - String signerUserId = cursor.getString(mIndexSignerUserId); + String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexSignerUserId)); + if (userId[0] != null) { + wSignerName.setText(userId[0]); + } else { + wSignerName.setText(R.string.user_id_no_name); + } + wSignerKeyId.setText(signerKeyId); + switch (cursor.getInt(mIndexType)) { case PGPSignature.DEFAULT_CERTIFICATION: // 0x10 wSignStatus.setText(R.string.cert_default); @@ -231,8 +244,6 @@ public class ViewKeyCertsFragment extends Fragment break; } - wSignerUserId.setText(signerUserId); - wSignerKeyId.setText(signerKeyId); view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId)); view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java new file mode 100644 index 000000000..fb228f032 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.database.Cursor; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; + + +public class ViewKeyKeysFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "uri"; + + private ListView mKeys; + + private ViewKeyKeysAdapter mKeysAdapter; + + private Uri mDataUri; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_keys_fragment, getContainer()); + + mKeys = (ListView) view.findViewById(R.id.keys); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0); + mKeys.setAdapter(mKeysAdapter); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(0, null, this); + } + + static final String[] KEYS_PROJECTION = new String[] { + Keys._ID, + Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET, + Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED, + Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT + }; + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + Uri baseUri = Keys.buildKeysUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null); + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Avoid NullPointerExceptions, if we get an empty result set. + if(data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mKeysAdapter.swapCursor(data); + + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + mKeysAdapter.swapCursor(null); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index ef4da3010..026417776 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -19,90 +19,64 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; -import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.TextView; -import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; +import org.sufficientlysecure.keychain.R;import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; - -public class ViewKeyMainFragment extends Fragment implements +public class ViewKeyMainFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_DATA_URI = "uri"; - private LinearLayout mContainer; - private TextView mName; - private TextView mEmail; - private TextView mComment; - private TextView mAlgorithm; - private TextView mKeyId; - private TextView mExpiry; - private TextView mCreation; - private TextView mFingerprint; - private TextView mSecretKey; - private BootstrapButton mActionEdit; - private BootstrapButton mActionEncrypt; - private BootstrapButton mActionCertify; + private View mActionEdit; + private View mActionEditDivider; + private View mActionEncrypt; + private View mActionCertify; + private View mActionCertifyDivider; private ListView mUserIds; - private ListView mKeys; private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; - private static final int LOADER_ID_KEYS = 2; + + // conservative attitude + private boolean mHasEncrypt = true; private ViewKeyUserIdsAdapter mUserIdsAdapter; - private ViewKeyKeysAdapter mKeysAdapter; private Uri mDataUri; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.view_key_main_fragment, container, false); - - mContainer = (LinearLayout) view.findViewById(R.id.container); - mName = (TextView) view.findViewById(R.id.name); - mEmail = (TextView) view.findViewById(R.id.email); - mComment = (TextView) view.findViewById(R.id.comment); - mKeyId = (TextView) view.findViewById(R.id.key_id); - mAlgorithm = (TextView) view.findViewById(R.id.algorithm); - mCreation = (TextView) view.findViewById(R.id.creation); - mExpiry = (TextView) view.findViewById(R.id.expiry); - mFingerprint = (TextView) view.findViewById(R.id.fingerprint); - mSecretKey = (TextView) view.findViewById(R.id.secret_key); - mUserIds = (ListView) view.findViewById(R.id.user_ids); - mKeys = (ListView) view.findViewById(R.id.keys); - mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit); - mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt); - mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify); - - return view; + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_main_fragment, getContainer()); + + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mActionEdit = view.findViewById(R.id.view_key_action_edit); + mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider); + mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt); + mActionCertify = view.findViewById(R.id.view_key_action_certify); + mActionCertifyDivider = view.findViewById(R.id.view_key_action_certify_divider); + + return root; } @Override @@ -120,14 +94,6 @@ public class ViewKeyMainFragment extends Fragment implements } private void loadData(Uri dataUri) { - if (dataUri.equals(mDataUri)) { - Log.d(Constants.TAG, "Same URI, no need to load the data again!"); - return; - } - - getActivity().setProgressBarIndeterminateVisibility(true); - mContainer.setVisibility(View.GONE); - mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); @@ -135,52 +101,42 @@ public class ViewKeyMainFragment extends Fragment implements mActionEncrypt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - encryptToContact(mDataUri); + encrypt(mDataUri); } }); mActionCertify.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { - certifyKey(mDataUri); + certify(mDataUri); + } + }); + mActionEdit.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + editKey(mDataUri); } }); mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0); mUserIds.setAdapter(mUserIdsAdapter); - mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0); - mKeys.setAdapter(mKeysAdapter); - // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); - getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this); + getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); } - static final String[] UNIFIED_PROJECTION = new String[] { - KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET, - KeyRings.USER_ID, KeyRings.FINGERPRINT, - KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY, - + static final String[] UNIFIED_PROJECTION = new String[]{ + KeyRings._ID, KeyRings.MASTER_KEY_ID, + KeyRings.HAS_ANY_SECRET, KeyRings.IS_REVOKED, KeyRings.EXPIRY, KeyRings.HAS_ENCRYPT }; - static final int INDEX_UNIFIED_MKI = 1; + static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2; - static final int INDEX_UNIFIED_UID = 3; - static final int INDEX_UNIFIED_FINGERPRINT = 4; - static final int INDEX_UNIFIED_ALGORITHM = 5; - static final int INDEX_UNIFIED_KEY_SIZE = 6; - static final int INDEX_UNIFIED_CREATION = 7; - static final int INDEX_UNIFIED_EXPIRY = 8; - - static final String[] KEYS_PROJECTION = new String[] { - Keys._ID, - Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET, - Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED, - Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT - }; - static final int KEYS_INDEX_CAN_ENCRYPT = 7; + static final int INDEX_UNIFIED_IS_REVOKED = 3; + static final int INDEX_UNIFIED_EXPIRY = 4; + static final int INDEX_UNIFIED_HAS_ENCRYPT = 5; public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); @@ -188,11 +144,8 @@ public class ViewKeyMainFragment extends Fragment implements } case LOADER_ID_USER_IDS: { Uri baseUri = UserIds.buildUserIdsUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null); - } - case LOADER_ID_KEYS: { - Uri baseUri = Keys.buildKeysUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null); + return new CursorLoader(getActivity(), baseUri, + ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null); } default: @@ -205,7 +158,7 @@ public class ViewKeyMainFragment extends Fragment implements * because the notification triggers faster than the activity closes. */ // Avoid NullPointerExceptions... - if(data.getCount() == 0) { + if (data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the @@ -213,80 +166,43 @@ public class ViewKeyMainFragment extends Fragment implements switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { - // get name, email, and comment from USER_ID - String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID)); - if (mainUserId[0] != null) { - getActivity().setTitle(mainUserId[0]); - mName.setText(mainUserId[0]); - } else { - getActivity().setTitle(R.string.user_id_no_name); - mName.setText(R.string.user_id_no_name); - } - mEmail.setText(mainUserId[1]); - mComment.setText(mainUserId[2]); - if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) { - mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); - mSecretKey.setText(R.string.secret_key_yes); + // certify button + mActionCertify.setVisibility(View.GONE); + mActionCertifyDivider.setVisibility(View.GONE); // edit button mActionEdit.setVisibility(View.VISIBLE); - mActionEdit.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); - editIntent.setData( - KeyRingData.buildSecretKeyRingUri(mDataUri)); - editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); - startActivityForResult(editIntent, 0); - } - }); + mActionEditDivider.setVisibility(View.VISIBLE); } else { - mSecretKey.setTextColor(Color.BLACK); - mSecretKey.setText(getResources().getString(R.string.secret_key_no)); - // certify button mActionCertify.setVisibility(View.VISIBLE); + mActionCertifyDivider.setVisibility(View.VISIBLE); + // edit button mActionEdit.setVisibility(View.GONE); + mActionEditDivider.setVisibility(View.GONE); } - // get key id from MASTER_KEY_ID - long masterKeyId = data.getLong(INDEX_UNIFIED_MKI); - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId); - mKeyId.setText(keyIdStr); - - // get creation date from CREATION - if (data.isNull(INDEX_UNIFIED_CREATION)) { - mCreation.setText(R.string.none); + // If this key is revoked, it cannot be used for anything! + if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) { + mActionEdit.setEnabled(false); + mActionCertify.setEnabled(false); + mActionEncrypt.setEnabled(false); } else { - Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000); - - mCreation.setText( - DateFormat.getDateFormat(getActivity().getApplicationContext()).format( - creationDate)); - } + mActionEdit.setEnabled(true); - // get expiry date from EXPIRY - if (data.isNull(INDEX_UNIFIED_EXPIRY)) { - mExpiry.setText(R.string.none); - } else { Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000); - - mExpiry.setText( - DateFormat.getDateFormat(getActivity().getApplicationContext()).format( - expiryDate)); + if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) { + mActionCertify.setEnabled(false); + mActionEncrypt.setEnabled(false); + } else { + mActionCertify.setEnabled(true); + mActionEncrypt.setEnabled(true); + } } - String algorithmStr = PgpKeyHelper.getAlgorithmInfo( - getActivity(), - data.getInt(INDEX_UNIFIED_ALGORITHM), - data.getInt(INDEX_UNIFIED_KEY_SIZE) - ); - mAlgorithm.setText(algorithmStr); - - byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob); - mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint)); + mHasEncrypt = data.getInt(INDEX_UNIFIED_HAS_ENCRYPT) != 0; break; } @@ -296,26 +212,8 @@ public class ViewKeyMainFragment extends Fragment implements mUserIdsAdapter.swapCursor(data); break; - case LOADER_ID_KEYS: - // hide encrypt button if no encryption key is available - // TODO: do with subquery! - boolean canEncrypt = false; - data.moveToFirst(); - do { - if (data.getInt(KEYS_INDEX_CAN_ENCRYPT) == 1) { - canEncrypt = true; - break; - } - } while (data.moveToNext()); - if (!canEncrypt) { - mActionEncrypt.setVisibility(View.GONE); - } - - mKeysAdapter.swapCursor(data); - break; } - getActivity().setProgressBarIndeterminateVisibility(false); - mContainer.setVisibility(View.VISIBLE); + setContentShown(true); } /** @@ -327,16 +225,18 @@ public class ViewKeyMainFragment extends Fragment implements case LOADER_ID_USER_IDS: mUserIdsAdapter.swapCursor(null); break; - case LOADER_ID_KEYS: - mKeysAdapter.swapCursor(null); - break; } } - private void encryptToContact(Uri dataUri) { + private void encrypt(Uri dataUri) { + // If there is no encryption key, don't bother. + if (!mHasEncrypt) { + AppMsg.makeText(getActivity(), R.string.error_no_encrypt_subkey, AppMsg.STYLE_ALERT).show(); + return; + } try { long keyId = new ProviderHelper(getActivity()).extractOrGetMasterKeyId(dataUri); - long[] encryptionKeyIds = new long[]{ keyId }; + long[] encryptionKeyIds = new long[]{keyId}; Intent intent = new Intent(getActivity(), EncryptActivity.class); intent.setAction(EncryptActivity.ACTION_ENCRYPT); intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); @@ -347,10 +247,17 @@ public class ViewKeyMainFragment extends Fragment implements } } - private void certifyKey(Uri dataUri) { + private void certify(Uri dataUri) { Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class); signIntent.setData(dataUri); startActivity(signIntent); } + private void editKey(Uri dataUri) { + Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); + editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java new file mode 100644 index 000000000..b3655133d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.devspark.appmsg.AppMsg; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.QrCodeUtils; + +import java.io.IOException; + + +public class ViewKeyShareFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "uri"; + + private TextView mFingerprint; + private ImageView mFingerprintQrCode; + private View mFingerprintShareButton; + private View mFingerprintClipboardButton; + private View mKeyShareButton; + private View mKeyClipboardButton; + private View mNfcHelpButton; + private View mNfcPrefsButton; + + ProviderHelper mProviderHelper; + + private static final int QR_CODE_SIZE = 1000; + + private static final int LOADER_ID_UNIFIED = 0; + + private Uri mDataUri; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_share_fragment, getContainer()); + + mProviderHelper = new ProviderHelper(ViewKeyShareFragment.this.getActivity()); + + mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint); + mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image); + mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); + mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); + mKeyShareButton = view.findViewById(R.id.view_key_action_key_share); + mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); + mNfcHelpButton = view.findViewById(R.id.view_key_action_nfc_help); + mNfcPrefsButton = view.findViewById(R.id.view_key_action_nfc_prefs); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mNfcPrefsButton.setVisibility(View.VISIBLE); + } else { + mNfcPrefsButton.setVisibility(View.GONE); + } + + mFingerprintQrCode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showQrCodeDialog(); + } + }); + + mFingerprintShareButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + share(mDataUri, mProviderHelper, true, false); + } + }); + mFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + share(mDataUri, mProviderHelper, true, true); + } + }); + mKeyShareButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + share(mDataUri, mProviderHelper, false, false); + } + }); + mKeyClipboardButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + share(mDataUri, mProviderHelper, false, true); + } + }); + mNfcHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showNfcHelpDialog(); + } + }); + mNfcPrefsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showNfcPrefs(); + } + }); + + return root; + } + + private void share(Uri dataUri, ProviderHelper providerHelper, boolean fingerprintOnly, + boolean toClipboard) { + try { + String content; + if (fingerprintOnly) { + byte[] data = (byte[]) providerHelper.getGenericData( + KeyRings.buildUnifiedKeyRingUri(dataUri), + Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(data); + content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + } else { + // get public keyring as ascii armored string + Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); + content = providerHelper.getKeyRingAsArmoredString(uri); + } + + if (toClipboard) { + ClipboardReflection.copyToClipboard(getActivity(), content); + String message; + if (fingerprintOnly) { + message = getResources().getString(R.string.fingerprint_copied_to_clipboard); + } else { + message = getResources().getString(R.string.key_copied_to_clipboard); + } + AppMsg.makeText(getActivity(), message, AppMsg.STYLE_INFO).show(); + } else { + // Android will fail with android.os.TransactionTooLargeException if key is too big + // see http://www.lonestarprod.com/?p=34 + if (content.length() >= 86389) { + AppMsg.makeText(getActivity(), R.string.key_too_big_for_sharing, + AppMsg.STYLE_ALERT).show(); + return; + } + + // let user choose application + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, content); + sendIntent.setType("text/plain"); + String title; + if (fingerprintOnly) { + title = getResources().getString(R.string.title_share_fingerprint_with); + } else { + title = getResources().getString(R.string.title_share_key); + } + startActivity(Intent.createChooser(sendIntent, title)); + } + } catch (IOException e) { + Log.e(Constants.TAG, "error processing key!", e); + AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show(); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); + } + } + + private void showQrCodeDialog() { + ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(mDataUri); + dialog.show(ViewKeyShareFragment.this.getActivity().getSupportFragmentManager(), "shareQrCodeDialog"); + } + + private void showNfcHelpDialog() { + ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance(); + dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog"); + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + private void showNfcPrefs() { + Intent intentSettings = new Intent( + Settings.ACTION_NFCSHARING_SETTINGS); + startActivity(intentSettings); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + } + + static final String[] UNIFIED_PROJECTION = new String[]{ + KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET, + KeyRings.USER_ID, KeyRings.FINGERPRINT, + KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY, + + }; + static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; + static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2; + static final int INDEX_UNIFIED_USER_ID = 3; + static final int INDEX_UNIFIED_FINGERPRINT = 4; + static final int INDEX_UNIFIED_ALGORITHM = 5; + static final int INDEX_UNIFIED_KEY_SIZE = 6; + static final int INDEX_UNIFIED_CREATION = 7; + static final int INDEX_UNIFIED_EXPIRY = 8; + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + setContentShown(false); + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + if (data.moveToFirst()) { + + byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob); + mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint)); + + String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + mFingerprintQrCode.setImageBitmap( + QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE) + ); + + break; + } + } + + } + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + } +} 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 f4fa7f3bf..9d323c822 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 @@ -28,10 +28,10 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import java.util.ArrayList; @@ -106,7 +106,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId); holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest); holder.keyId = (TextView) convertView.findViewById(R.id.keyId); - holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint); + holder.fingerprint = (TextView) convertView.findViewById(R.id.view_key_fingerprint); holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm); holder.status = (TextView) convertView.findViewById(R.id.status); holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.user_ids_list); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java new file mode 100644 index 000000000..420880522 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.support.v4.content.AsyncTaskLoader; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.KeyServer; +import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class ImportKeysListKeybaseLoader + extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { + Context mContext; + + String mKeybaseQuery; + + private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>(); + private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; + + public ImportKeysListKeybaseLoader(Context context, String keybaseQuery) { + super(context); + mContext = context; + mKeybaseQuery = keybaseQuery; + } + + @Override + public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() { + + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null); + + if (mKeybaseQuery == null) { + Log.e(Constants.TAG, "mKeybaseQery is null!"); + return mEntryListWrapper; + } + + queryServer(mKeybaseQuery); + + return mEntryListWrapper; + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) { + super.deliverResult(data); + } + + /** + * Query keybase + */ + private void queryServer(String query) { + + KeybaseKeyServer server = new KeybaseKeyServer(); + try { + ArrayList<ImportKeysListEntry> searchResult = server.search(query); + + mEntryList.clear(); + + mEntryList.addAll(searchResult); + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null); + } catch (KeyServer.InsufficientQuery e) { + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); + } catch (KeyServer.QueryException e) { + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); + } catch (KeyServer.TooManyResponses e) { + mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 3fd5d5daf..b6c829677 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -24,6 +24,7 @@ import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index 838aeefee..4175592d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -21,8 +21,9 @@ import android.content.Context; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.HkpKeyServer; -import org.sufficientlysecure.keychain.util.KeyServer; +import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.KeyServer; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -116,13 +117,10 @@ public class ImportKeysListServerLoader } mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null); } catch (KeyServer.InsufficientQuery e) { - Log.e(Constants.TAG, "InsufficientQuery", e); mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); } catch (KeyServer.QueryException e) { - Log.e(Constants.TAG, "QueryException", e); mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); } catch (KeyServer.TooManyResponses e) { - Log.e(Constants.TAG, "TooManyResponses", e); mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); } } 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 fd864eb09..977740567 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 @@ -17,7 +17,7 @@ package org.sufficientlysecure.keychain.ui.adapter; -import android.content.Context; +import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentPagerAdapter; @@ -26,8 +26,8 @@ import android.support.v7.app.ActionBarActivity; import java.util.ArrayList; public class PagerTabStripAdapter extends FragmentPagerAdapter { - private final Context mContext; - private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + protected final Activity mActivity; + protected final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); static final class TabInfo { public final Class<?> clss; @@ -43,7 +43,7 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter { public PagerTabStripAdapter(ActionBarActivity activity) { super(activity.getSupportFragmentManager()); - mContext = activity; + mActivity = activity; } public void addTab(Class<?> clss, Bundle args, String title) { @@ -60,7 +60,7 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter { @Override public Fragment getItem(int position) { TabInfo info = mTabs.get(position); - return Fragment.instantiate(mContext, info.clss.getName(), info.args); + return Fragment.instantiate(mActivity, info.clss.getName(), info.args); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java index 9e26e559f..f4942a2a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -121,35 +121,17 @@ public class ViewKeyKeysAdapter extends CursorAdapter { keyId.setText(keyIdStr); // may be set with additional "stripped" later on if (hasAnySecret && cursor.getInt(mIndexHasSecret) == 0) { - keyDetails.setText("(" + algorithmStr + ", " + - context.getString(R.string.key_stripped) + ")"); + keyDetails.setText(algorithmStr + ", " + + context.getString(R.string.key_stripped)); } else { - keyDetails.setText("(" + algorithmStr + ")"); + keyDetails.setText(algorithmStr); } - if (cursor.getInt(mIndexRank) == 0) { - masterKeyIcon.setVisibility(View.INVISIBLE); - } else { - masterKeyIcon.setVisibility(View.VISIBLE); - } - - if (cursor.getInt(mIndexCanCertify) != 1) { - certifyIcon.setVisibility(View.GONE); - } else { - certifyIcon.setVisibility(View.VISIBLE); - } - - if (cursor.getInt(mIndexCanEncrypt) != 1) { - encryptIcon.setVisibility(View.GONE); - } else { - encryptIcon.setVisibility(View.VISIBLE); - } - - if (cursor.getInt(mIndexCanSign) != 1) { - signIcon.setVisibility(View.GONE); - } else { - signIcon.setVisibility(View.VISIBLE); - } + // Set icons according to properties + masterKeyIcon.setVisibility(cursor.getInt(mIndexRank) == 0 ? View.VISIBLE : View.INVISIBLE); + certifyIcon.setVisibility(cursor.getInt(mIndexCanCertify) != 0 ? View.VISIBLE : View.GONE); + encryptIcon.setVisibility(cursor.getInt(mIndexCanEncrypt) != 0 ? View.VISIBLE : View.GONE); + signIcon.setVisibility(cursor.getInt(mIndexCanSign) != 0 ? View.VISIBLE : View.GONE); boolean valid = true; if (cursor.getInt(mIndexRevokedKey) > 0) { @@ -168,13 +150,13 @@ public class ViewKeyKeysAdapter extends CursorAdapter { Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000); valid = valid && expiryDate.after(new Date()); - keyExpiry.setText("(" + + keyExpiry.setText( context.getString(R.string.label_expiry) + ": " + - DateFormat.getDateFormat(context).format(expiryDate) + ")"); - - keyExpiry.setVisibility(View.VISIBLE); + DateFormat.getDateFormat(context).format(expiryDate)); } else { - keyExpiry.setVisibility(View.GONE); + keyExpiry.setText( + context.getString(R.string.label_expiry) + ": " + + context.getString(R.string.none)); } // if key is expired or revoked, strike through text diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java index 52e6dec92..64452e8b4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java @@ -27,6 +27,7 @@ import android.widget.AdapterView; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import org.sufficientlysecure.keychain.R; @@ -106,46 +107,58 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter implements AdapterView. @Override public void bindView(View view, Context context, Cursor cursor) { - TextView vRank = (TextView) view.findViewById(R.id.rank); - TextView vUserId = (TextView) view.findViewById(R.id.userId); + TextView vName = (TextView) view.findViewById(R.id.userId); TextView vAddress = (TextView) view.findViewById(R.id.address); + TextView vComment = (TextView) view.findViewById(R.id.comment); ImageView vVerified = (ImageView) view.findViewById(R.id.certified); - if (cursor.getInt(mIsPrimary) > 0) { - vRank.setText("+"); - } else { - vRank.setText(Integer.toString(cursor.getInt(mIndexRank))); - } - String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId)); if (userId[0] != null) { - vUserId.setText(userId[0]); + vName.setText(userId[0]); + } else { + vName.setText(R.string.user_id_no_name); + } + if (userId[1] != null) { + vAddress.setText(userId[1]); + vAddress.setVisibility(View.VISIBLE); } else { - vUserId.setText(R.string.user_id_no_name); + vAddress.setVisibility(View.GONE); + } + if (userId[2] != null) { + vComment.setText(userId[2]); + vComment.setVisibility(View.VISIBLE); + } else { + vComment.setVisibility(View.GONE); } - vAddress.setText(userId[1]); + + // show small star icon for primary user ids + boolean isPrimary = cursor.getInt(mIsPrimary) != 0; if (cursor.getInt(mIsRevoked) > 0) { - vRank.setText(" "); + + // set revocation icon (can this even be primary?) vVerified.setImageResource(R.drawable.key_certify_revoke); // disable and strike through text for revoked user ids - vUserId.setEnabled(false); + vName.setEnabled(false); vAddress.setEnabled(false); - vUserId.setText(OtherHelper.strikeOutText(vUserId.getText())); + vName.setText(OtherHelper.strikeOutText(vName.getText())); vAddress.setText(OtherHelper.strikeOutText(vAddress.getText())); } else { - vUserId.setEnabled(true); + vName.setEnabled(true); vAddress.setEnabled(true); int verified = cursor.getInt(mVerifiedId); - // TODO introduce own resources for this :) switch (verified) { case Certs.VERIFIED_SECRET: - vVerified.setImageResource(R.drawable.key_certify_ok_depth0); + vVerified.setImageResource(isPrimary + ? R.drawable.key_certify_primary_ok_depth0 + : R.drawable.key_certify_ok_depth0); break; case Certs.VERIFIED_SELF: - vVerified.setImageResource(R.drawable.key_certify_ok_self); + vVerified.setImageResource(isPrimary + ? R.drawable.key_certify_primary_ok_self + : R.drawable.key_certify_ok_self); break; default: vVerified.setImageResource(R.drawable.key_certify_error); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java index 35e464423..19cf27259 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; @@ -50,7 +49,7 @@ public class BadImportKeyDialogFragment extends DialogFragment { final FragmentActivity activity = getActivity(); final int badImport = getArguments().getInt(ARG_BAD_IMPORT); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); alert.setTitle(R.string.warning); alert.setMessage(activity.getResources() @@ -63,6 +62,6 @@ public class BadImportKeyDialogFragment extends DialogFragment { }); alert.setCancelable(true); - return alert.create(); + return alert.show(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java index c71bc160a..6c012cb94 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java @@ -83,7 +83,7 @@ public class CreateKeyDialogFragment extends DialogFragment { final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT); mInflater = context.getLayoutInflater(); - AlertDialog.Builder dialog = new AlertDialog.Builder(context); + CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); View view = mInflater.inflate(R.layout.create_key_dialog, null); dialog.setView(view); @@ -146,7 +146,7 @@ public class CreateKeyDialogFragment extends DialogFragment { } }); - final AlertDialog alertDialog = dialog.create(); + final AlertDialog alertDialog = dialog.show(); mCustomKeyEditText.addTextChangedListener(new TextWatcher() { @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java new file mode 100644 index 000000000..4b40b7ef1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java @@ -0,0 +1,40 @@ +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Activity; +import android.app.AlertDialog; +import android.view.View; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; + +/** This class extends AlertDiaog.Builder, styling the header using emphasis color. + * Note that this class is a huge hack, because dialog boxes aren't easily stylable. + * Also, the dialog NEEDS to be called with show() directly, not create(), otherwise + * the order of internal operations will lead to a crash! + */ +public class CustomAlertDialogBuilder extends AlertDialog.Builder { + + public CustomAlertDialogBuilder(Activity activity) { + super(activity); + } + + @Override + public AlertDialog show() { + AlertDialog dialog = super.show(); + + int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null); + View divider = dialog.findViewById(dividerId); + if (divider != null) { + divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.emphasis)); + } + + int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null); + TextView tv = (TextView) dialog.findViewById(textViewId); + if (tv != null) { + tv.setTextColor(dialog.getContext().getResources().getColor(R.color.emphasis)); + } + + return dialog; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index 37dec70cd..b42a79993 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; @@ -59,7 +58,7 @@ public class DeleteFileDialogFragment extends DialogFragment { final String deleteFile = getArguments().getString(ARG_DELETE_FILE); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); @@ -120,6 +119,6 @@ public class DeleteFileDialogFragment extends DialogFragment { }); alert.setCancelable(true); - return alert.create(); + return alert.show(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 93cdef5e3..01d2fae6a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; @@ -73,7 +72,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity); // Setup custom View to display in AlertDialog LayoutInflater inflater = activity.getLayoutInflater(); @@ -144,7 +143,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { } }); - return builder.create(); + return builder.show(); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 6a5baf658..24f93bed7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -97,7 +97,7 @@ public class FileDialogFragment extends DialogFragment { LayoutInflater inflater = (LayoutInflater) activity .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setTitle(title); View view = inflater.inflate(R.layout.file_dialog, null); @@ -157,7 +157,7 @@ public class FileDialogFragment extends DialogFragment { dismiss(); } }); - return alert.create(); + return alert.show(); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index 9b5581cc7..cc653e58c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -134,7 +134,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor final long secretKeyId = getArguments().getLong(ARG_SECRET_KEY_ID); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setTitle(R.string.title_authentication); @@ -243,7 +243,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor }); mCanKB = true; - return alert.create(); + return alert.show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index e5db22a04..04bec3282 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -81,7 +81,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi int title = getArguments().getInt(ARG_TITLE); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setTitle(title); alert.setMessage(R.string.enter_passphrase_twice); @@ -135,7 +135,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi } }); - return alert.create(); + return alert.show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java index 15df53dcc..961f92f03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; @@ -51,7 +50,7 @@ public class ShareNfcDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setTitle(R.string.share_nfc_dialog); alert.setCancelable(true); @@ -78,7 +77,7 @@ public class ShareNfcDialogFragment extends DialogFragment { + getString(R.string.error_nfc_needed)); } else { // nfc works... - textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share); + textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share, true); alert.setNegativeButton(R.string.menu_beam_preferences, new DialogInterface.OnClickListener() { @@ -93,6 +92,6 @@ public class ShareNfcDialogFragment extends DialogFragment { } } - return alert.create(); + return alert.show(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java index fe50c759b..24608784b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java @@ -18,14 +18,12 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -34,37 +32,26 @@ import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.QrCodeUtils; -import java.io.IOException; -import java.util.ArrayList; - public class ShareQrCodeDialogFragment extends DialogFragment { private static final String ARG_KEY_URI = "uri"; - private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only"; private ImageView mImage; private TextView mText; - private boolean mFingerprintOnly; - - private ArrayList<String> mContentList; - private int mCounter; - private static final int QR_CODE_SIZE = 1000; /** * Creates new instance of this dialog fragment */ - public static ShareQrCodeDialogFragment newInstance(Uri dataUri, boolean fingerprintOnly) { + public static ShareQrCodeDialogFragment newInstance(Uri dataUri) { ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_KEY_URI, dataUri); - args.putBoolean(ARG_FINGERPRINT_ONLY, fingerprintOnly); frag.setArguments(args); @@ -79,9 +66,8 @@ public class ShareQrCodeDialogFragment extends DialogFragment { final Activity activity = getActivity(); Uri dataUri = getArguments().getParcelable(ARG_KEY_URI); - mFingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY); - AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity()); alert.setTitle(R.string.share_qr_code_dialog_title); LayoutInflater inflater = activity.getLayoutInflater(); @@ -94,131 +80,32 @@ public class ShareQrCodeDialogFragment extends DialogFragment { ProviderHelper providerHelper = new ProviderHelper(getActivity()); String content; try { - if (mFingerprintOnly) { - alert.setPositiveButton(R.string.btn_okay, null); - - byte[] blob = (byte[]) providerHelper.getGenericData( - KeyRings.buildUnifiedKeyRingUri(dataUri), - KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - if (blob == null) { - Log.e(Constants.TAG, "key not found!"); - AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); - return null; - } - - String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob); - mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint); - content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; - setQrCode(content); - } else { - mText.setText(R.string.share_qr_code_dialog_start); - - try { - Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); - content = providerHelper.getKeyRingAsArmoredString(uri); - } catch (IOException e) { - Log.e(Constants.TAG, "error processing key!", e); - AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT).show(); - return null; - } - - // OnClickListener are set in onResume to prevent automatic dismissing of Dialogs - // http://bit.ly/O5vfaR - alert.setPositiveButton(R.string.btn_next, null); - alert.setNegativeButton(android.R.string.cancel, null); - - mContentList = splitString(content, 1000); - - // start with first - mCounter = 0; - updatePartsQrCode(); + alert.setPositiveButton(R.string.btn_okay, null); + + byte[] blob = (byte[]) providerHelper.getGenericData( + KeyRings.buildUnifiedKeyRingUri(dataUri), + KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + if (blob == null) { + Log.e(Constants.TAG, "key not found!"); + AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); + return null; } + + String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob); + mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint); + content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + setQrCode(content); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show(); return null; } - return alert.create(); - } - - @Override - public void onResume() { - super.onResume(); - - if (!mFingerprintOnly) { - AlertDialog alertDialog = (AlertDialog) getDialog(); - final Button backButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE); - final Button nextButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - - backButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mCounter > 0) { - mCounter--; - updatePartsQrCode(); - updateDialog(backButton, nextButton); - } else { - dismiss(); - } - } - }); - nextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - - if (mCounter < mContentList.size() - 1) { - mCounter++; - updatePartsQrCode(); - updateDialog(backButton, nextButton); - } else { - dismiss(); - } - } - }); - } - } - - private void updatePartsQrCode() { - // Content: <counter>,<size>,<content> - setQrCode(mCounter + "," + mContentList.size() + "," + mContentList.get(mCounter)); + return alert.show(); } private void setQrCode(String data) { mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(data, QR_CODE_SIZE)); } - private void updateDialog(Button backButton, Button nextButton) { - if (mCounter == 0) { - backButton.setText(android.R.string.cancel); - } else { - backButton.setText(R.string.btn_back); - } - if (mCounter == mContentList.size() - 1) { - nextButton.setText(android.R.string.ok); - } else { - nextButton.setText(R.string.btn_next); - } - - mText.setText(getResources().getString(R.string.share_qr_code_dialog_progress, - mCounter + 1, mContentList.size())); - } - - /** - * Split String by number of characters - * - * @param text - * @param size - * @return - */ - private ArrayList<String> splitString(String text, int size) { - ArrayList<String> strings = new ArrayList<String>(); - int index = 0; - while (index < text.length()) { - strings.add(text.substring(index, Math.min(index + size, text.length()))); - index += size; - } - - return strings; - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java deleted file mode 100644 index 937a48e48..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2013 Eric Frohnhoefer - * - * 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.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.TextView; - -/** - * Copied from StickyListHeaders lib example - * - * @author Eric Frohnhoefer - */ -public class UnderlineTextView extends TextView { - private final Paint mPaint = new Paint(); - private int mUnderlineHeight = 0; - - public UnderlineTextView(Context context) { - this(context, null); - } - - public UnderlineTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public UnderlineTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - init(context, attrs); - } - - private void init(Context context, AttributeSet attrs) { - Resources r = getResources(); - mUnderlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, - r.getDisplayMetrics()); - } - - @Override - public void setPadding(int left, int top, int right, int bottom) { - super.setPadding(left, top, right, bottom + mUnderlineHeight); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Draw the underline the same color as the text - mPaint.setColor(getTextColors().getDefaultColor()); - canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java index 5793d7438..f89ffd139 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java @@ -19,6 +19,9 @@ package org.sufficientlysecure.keychain.util; import java.io.InputStream; +/** + * Wrapper to include size besides an InputStream + */ public class InputData { private PositionAwareInputStream mInputStream; private long mSize; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java index ad1aab146..68b402124 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java @@ -22,6 +22,8 @@ import android.support.v4.app.Fragment; import com.google.zxing.integration.android.IntentIntegrator; /** + * Copied from older Barcode Scanner Integration library + * * IntentIntegrator for the V4 Android compatibility package. * * @author Lachezar Dobrev diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java new file mode 100644 index 000000000..7ae1d8fab --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Tim Bray <tbray@textuality.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.util; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Minimal hierarchy selector + */ +public class JWalk { + + public static int getInt(JSONObject json, String... path) throws JSONException { + json = walk(json, path); + return json.getInt(path[path.length - 1]); + } + + public static long getLong(JSONObject json, String... path) throws JSONException { + json = walk(json, path); + return json.getLong(path[path.length - 1]); + } + + public static String getString(JSONObject json, String... path) throws JSONException { + json = walk(json, path); + return json.getString(path[path.length - 1]); + } + + public static JSONArray getArray(JSONObject json, String... path) throws JSONException { + json = walk(json, path); + return json.getJSONArray(path[path.length - 1]); + } + + public static JSONObject optObject(JSONObject json, String... path) throws JSONException { + json = walk(json, path); + return json.optJSONObject(path[path.length - 1]); + } + + private static JSONObject walk(JSONObject json, String... path) throws JSONException { + int len = path.length - 1; + int pathIndex = 0; + try { + while (pathIndex < len) { + json = json.getJSONObject(path[pathIndex]); + pathIndex++; + } + } catch (JSONException e) { + // try to give ’em a nice-looking error + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append(path[i]).append('.'); + } + sb.append(path[len]); + throw new JSONException("JWalk error at step " + pathIndex + " of " + sb); + } + return json; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java deleted file mode 100644 index b205bd556..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.util; - -public interface KeychainServiceListener { - boolean hasServiceStopped(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java index cd9e56394..28e567d76 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java @@ -32,6 +32,9 @@ import org.sufficientlysecure.keychain.Constants; import java.util.Hashtable; +/** + * Copied from Bitcoin Wallet + */ public class QrCodeUtils { public static final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SlidingTabLayout.java new file mode 100644 index 000000000..065034be1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/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.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. + * <p/> + * 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. + * <p/> + * 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. + * <p/> + * 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. + * <p/> + * 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 new file mode 100644 index 000000000..9ce6f943e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/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.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 |