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:
Vincent Breitmoser
2014-05-14 16:02:28 +02:00
505 changed files with 4694 additions and 5904 deletions

View File

@@ -10,8 +10,8 @@ sourceSets {
dependencies {
compile 'com.android.support:support-v4:19.1.0'
compile 'com.android.support:appcompat-v7:19.1.0'
compile project(':OpenKeychain-API:libraries:openpgp-api-library')
compile project(':OpenKeychain-API:libraries:openkeychain-api-library')
compile project(':extern:openpgp-api-lib')
compile project(':extern:openkeychain-api-lib')
compile project(':extern:html-textview')
compile project(':extern:StickyListHeaders:library')
compile project(':extern:AndroidBootstrap:AndroidBootstrap')
@@ -29,8 +29,8 @@ dependencies {
testLocalCompile 'com.google.android:android:4.1.1.4'
testLocalCompile 'com.android.support:support-v4:19.1.0'
testLocalCompile 'com.android.support:appcompat-v7:19.1.0'
testLocalCompile project(':OpenKeychain-API:libraries:openpgp-api-library')
testLocalCompile project(':OpenKeychain-API:libraries:openkeychain-api-library')
testLocalCompile project(':extern:openpgp-api-lib')
testLocalCompile project(':extern:openkeychain-api-lib')
testLocalCompile project(':extern:html-textview')
testLocalCompile project(':extern:StickyListHeaders:library')
testLocalCompile project(':extern:AndroidBootstrap:AndroidBootstrap')

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.sufficientlysecure.keychain"
android:installLocation="auto"
android:versionCode="26100"
android:versionName="2.6.1">
android:versionCode="27000"
android:versionName="2.7">
<!--
General remarks
@@ -232,7 +232,12 @@
<activity
android:name=".ui.UploadKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_send_key" />
android:label="@string/title_send_key"
android:parentActivityName=".ui.ViewKeyActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.ViewKeyActivity" />
</activity>
<activity
android:name=".ui.PreferencesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@@ -254,7 +259,12 @@
<activity
android:name=".ui.CertifyKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_certify_key" />
android:label="@string/title_certify_key"
android:parentActivityName=".ui.ViewKeyActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.ViewKeyActivity" />
</activity>
<activity
android:name=".ui.ImportKeysActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"

View File

@@ -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);
}
}

View File

@@ -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
*

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -181,8 +181,6 @@ public class PgpSignEncrypt {
}
/**
* TODO: test this option!
*
* @param encryptToSigner
* @return
*/

View File

@@ -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();

View File

@@ -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;
}
/**

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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(), "*/*",

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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]);

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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));

View File

@@ -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();
}

View File

@@ -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);
}
});

View File

@@ -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;

View File

@@ -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 youre 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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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) {
}
}

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
/**

View File

@@ -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();
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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;
}
}
}
}
}

View File

@@ -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;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Some files were not shown because too many files have changed in this diff Show More