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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,25 +32,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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -181,8 +181,6 @@ public class PgpSignEncrypt {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: test this option!
|
||||
*
|
||||
* @param encryptToSigner
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
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);
|
||||
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());
|
||||
// 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();
|
||||
Cursor cursor = mContentResolver.query(accountUri, null, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.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)));
|
||||
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);
|
||||
signature = cursor.getBlob(signatureCol);
|
||||
}
|
||||
return signature;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(), "*/*",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
int selectedTab = 0;
|
||||
mViewPager = (ViewPager) findViewById(R.id.pager);
|
||||
SlidingTabLayout slidingTabLayout =
|
||||
(SlidingTabLayout) findViewById(R.id.sliding_tab_layout);
|
||||
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
|
||||
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(actionBar.newTab().setText(getString(R.string.help_tab_about)),
|
||||
HelpAboutFragment.class, null, (selectedTab == TAB_ABOUT));
|
||||
mTabsAdapter.addTab(HelpAboutFragment.class, null,
|
||||
getString(R.string.help_tab_about));
|
||||
|
||||
// NOTE: must be after adding the tabs!
|
||||
slidingTabLayout.setViewPager(mViewPager);
|
||||
|
||||
// switch to tab selected by extra
|
||||
mViewPager.setCurrentItem(selectedTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
@@ -236,6 +237,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
loadNavFragment(NAV_NFC, null);
|
||||
|
||||
// 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 {
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,27 +195,41 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 void onLoaderReset(Loader<Cursor> loader) {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.view_cert, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
mDataUri = getIntent().getData();
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initNfc(mDataUri);
|
||||
loadData(dataUri);
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false);
|
||||
// starting with 4 for this fragment
|
||||
private static final int LOADER_ID = 4;
|
||||
|
||||
return view;
|
||||
@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_certs_fragment, getContainer());
|
||||
|
||||
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.list);
|
||||
|
||||
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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
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());
|
||||
|
||||
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);
|
||||
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 view;
|
||||
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);
|
||||
mActionEdit.setEnabled(true);
|
||||
|
||||
mCreation.setText(
|
||||
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
||||
creationDate));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
vUserId.setText(R.string.user_id_no_name);
|
||||
vName.setText(R.string.user_id_no_name);
|
||||
}
|
||||
vAddress.setText(userId[1]);
|
||||
if (userId[1] != null) {
|
||||
vAddress.setText(userId[1]);
|
||||
vAddress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vAddress.setVisibility(View.GONE);
|
||||
}
|
||||
if (userId[2] != null) {
|
||||
vComment.setText(userId[2]);
|
||||
vComment.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vComment.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user