Resolve merge conflicts

This commit is contained in:
Dominik Schürmann
2016-10-27 19:03:37 +02:00
89 changed files with 2152 additions and 1657 deletions

View File

@@ -287,6 +287,10 @@ android {
exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE'
exclude '.readme' exclude '.readme'
} }
dataBinding {
enabled = true
}
} }
task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugWithTestCoverageUnitTest") { task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugWithTestCoverageUnitTest") {

View File

@@ -17,6 +17,8 @@
package org.sufficientlysecure.keychain.keyimport; package org.sufficientlysecure.keychain.keyimport;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.ParcelableProxy;
@@ -26,8 +28,6 @@ import java.net.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
import android.support.annotation.NonNull;
/** /**
* Search two or more types of server for online keys. * Search two or more types of server for online keys.
*/ */
@@ -38,8 +38,8 @@ public class CloudSearch {
public static ArrayList<ImportKeysListEntry> search( public static ArrayList<ImportKeysListEntry> search(
@NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull final ParcelableProxy proxy) @NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull final ParcelableProxy proxy)
throws Keyserver.CloudSearchFailureException { throws Keyserver.CloudSearchFailureException {
final ArrayList<Keyserver> servers = new ArrayList<>();
final ArrayList<Keyserver> servers = new ArrayList<>();
// it's a Vector for sync, multiple threads might report problems // it's a Vector for sync, multiple threads might report problems
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>(); final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>();
@@ -52,46 +52,48 @@ public class CloudSearch {
if (cloudPrefs.searchFacebook) { if (cloudPrefs.searchFacebook) {
servers.add(new FacebookKeyserver()); servers.add(new FacebookKeyserver());
} }
final ImportKeysList results = new ImportKeysList(servers.size());
ArrayList<Thread> searchThreads = new ArrayList<>(); int numberOfServers = servers.size();
for (final Keyserver keyserver : servers) { final ImportKeysList results = new ImportKeysList(numberOfServers);
Runnable r = new Runnable() {
@Override if (numberOfServers > 0) {
public void run() { ArrayList<Thread> searchThreads = new ArrayList<>();
try { for (final Keyserver keyserver : servers) {
results.addAll(keyserver.search(query, proxy)); Runnable r = new Runnable() {
} catch (Keyserver.CloudSearchFailureException e) { @Override
problems.add(e); public void run() {
try {
results.addAll(keyserver.search(query, proxy));
} catch (Keyserver.CloudSearchFailureException e) {
problems.add(e);
}
results.finishedAdding(); // notifies if all searchers done
} }
results.finishedAdding(); // notifies if all searchers done };
} Thread searchThread = new Thread(r);
}; searchThreads.add(searchThread);
Thread searchThread = new Thread(r); searchThread.start();
searchThreads.add(searchThread);
searchThread.start();
}
// wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
synchronized (results) {
try {
if (proxy.getProxy() == Proxy.NO_PROXY) {
results.wait(30 * SECONDS);
} else {
results.wait(10 * SECONDS);
}
for (Thread thread : searchThreads) {
// kill threads that haven't returned yet
thread.interrupt();
}
} catch (InterruptedException ignored) {
} }
}
if (results.outstandingSuppliers() > 0) { // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
String message = "Launched " + servers.size() + " cloud searchers, but " + synchronized (results) {
results.outstandingSuppliers() + "failed to complete."; try {
problems.add(new Keyserver.QueryFailedException(message)); results.wait((proxy.getProxy() == Proxy.NO_PROXY ? 30 : 10) * SECONDS);
for (Thread thread : searchThreads) {
// kill threads that haven't returned yet
thread.interrupt();
}
} catch (InterruptedException ignored) {
}
}
if (results.outstandingSuppliers() > 0) {
String message = "Launched " + servers.size() + " cloud searchers, but " +
results.outstandingSuppliers() + "failed to complete.";
problems.add(new Keyserver.QueryFailedException(message));
}
} else {
problems.add(new Keyserver.QueryNoEnabledSourceException());
} }
if (!problems.isEmpty()) { if (!problems.isEmpty()) {

View File

@@ -23,11 +23,6 @@ import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
@@ -40,13 +35,16 @@ import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.util.TlsHelper;
import java.io.IOException; import java.io.IOException;
import java.net.Proxy; import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class FacebookKeyserver extends Keyserver { public class FacebookKeyserver extends Keyserver {
private static final String FB_KEY_URL_FORMAT private static final String FB_KEY_URL_FORMAT
@@ -148,7 +146,6 @@ public class FacebookKeyserver extends Keyserver {
throws UnsupportedOperationException { throws UnsupportedOperationException {
ImportKeysListEntry entry = new ImportKeysListEntry(); ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setSecretKey(false); // keys imported from Facebook must be public entry.setSecretKey(false); // keys imported from Facebook must be public
entry.addOrigin(ORIGIN);
// so we can query for the Facebook username directly, and to identify the source to // so we can query for the Facebook username directly, and to identify the source to
// download the key from // download the key from
@@ -156,17 +153,11 @@ public class FacebookKeyserver extends Keyserver {
UncachedPublicKey key = ring.getPublicKey(); UncachedPublicKey key = ring.getPublicKey();
entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback());
entry.setUserIds(key.getUnorderedUserIds()); entry.setUserIds(key.getUnorderedUserIds());
entry.updateMergedUserIds();
entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback()); entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback());
entry.setKeyId(key.getKeyId()); entry.setKeyId(key.getKeyId());
entry.setKeyIdHex(KeyFormattingUtils.convertKeyIdToHex(key.getKeyId())); entry.setFingerprint(key.getFingerprint());
entry.setFingerprintHex(KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()));
try { try {
if (key.isEC()) { // unsupported key format (ECDH or ECDSA) if (key.isEC()) { // unsupported key format (ECDH or ECDSA)

View File

@@ -70,44 +70,25 @@ public class ImportKeysList extends ArrayList<ImportKeysListEntry> {
modified = true; modified = true;
} }
// keep track if this key result is from a HKP keyserver if (incoming.getKeyserver() != null) {
boolean incomingFromHkpServer = true; existing.setKeyserver(incoming.getKeyserver());
// were going to want to try to fetch the key from everywhere we found it, so remember // Mail addresses returned by HKP servers are preferred over keybase.io IDs
// all the origins existing.setPrimaryUserId(incoming.getPrimaryUserId());
for (String origin : incoming.getOrigins()) {
existing.addOrigin(origin);
modified = true;
} else if (incoming.getKeybaseName() != null) {
// to work properly, Keybase-sourced/Facebook-sourced entries need to pass along the // to work properly, Keybase-sourced/Facebook-sourced entries need to pass along the
// identifying name/id // identifying name/id
if (incoming.getKeybaseName() != null) { existing.setKeybaseName(incoming.getKeybaseName());
existing.setKeybaseName(incoming.getKeybaseName()); modified = true;
// one of the origins is not a HKP keyserver } else if (incoming.getFbUsername() != null) {
incomingFromHkpServer = false; existing.setFbUsername(incoming.getFbUsername());
} modified = true;
if (incoming.getFbUsername() != null) {
existing.setFbUsername(incoming.getFbUsername());
// one of the origins is not a HKP keyserver
incomingFromHkpServer = false;
}
} }
ArrayList<String> incomingIDs = incoming.getUserIds(); if (existing.addUserIds(incoming.getUserIds()))
ArrayList<String> existingIDs = existing.getUserIds(); modified = true;
for (String incomingID : incomingIDs) {
if (!existingIDs.contains(incomingID)) {
// prepend HKP server results to the start of the list,
// so that the UI (for cloud key search, which is picking the first list item)
// shows the right main email address, as mail addresses returned by HKP servers
// are preferred over keybase.io IDs
if (incomingFromHkpServer) {
existingIDs.add(0, incomingID);
} else {
existingIDs.add(incomingID);
}
modified = true;
}
}
existing.updateMergedUserIds();
return modified; return modified;
} }

View File

@@ -21,108 +21,59 @@ import android.content.Context;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
public class ImportKeysListEntry implements Serializable, Parcelable { public class ImportKeysListEntry implements Serializable, Parcelable {
private static final long serialVersionUID = -7797972103284992662L; private static final long serialVersionUID = -7797972103284992662L;
private ParcelableKeyRing mParcelableKeyRing;
private ArrayList<String> mUserIds; private ArrayList<String> mUserIds;
private HashMap<String, HashSet<String>> mMergedUserIds; private HashMap<String, HashSet<String>> mMergedUserIds;
private long mKeyId; private ArrayList<Map.Entry<String, HashSet<String>>> mSortedUserIds;
private String mKeyIdHex; private String mKeyIdHex;
private boolean mSecretKey;
private boolean mRevoked; private boolean mRevoked;
private boolean mExpired; private boolean mExpired;
private Date mDate; // TODO: not displayed private boolean mUpdated;
private Date mDate;
private String mFingerprintHex; private String mFingerprintHex;
private Integer mBitStrength; private Integer mBitStrength;
private String mCurveOid; private String mCurveOid;
private String mAlgorithm; private String mAlgorithm;
private boolean mSecretKey;
private String mPrimaryUserId; private UserId mPrimaryUserId;
private ParcelableHkpKeyserver mKeyserver;
private String mKeybaseName; private String mKeybaseName;
private String mFbUsername; private String mFbUsername;
private String mQuery; private String mQuery;
private ArrayList<String> mOrigins;
private Integer mHashCode = null; private Integer mHashCode = null;
private boolean mSelected; public ParcelableKeyRing getParcelableKeyRing() {
return mParcelableKeyRing;
public int describeContents() {
return 0;
} }
@Override public void setParcelableKeyRing(ParcelableKeyRing parcelableKeyRing) {
public void writeToParcel(Parcel dest, int flags) { this.mParcelableKeyRing = parcelableKeyRing;
dest.writeString(mPrimaryUserId);
dest.writeStringList(mUserIds);
dest.writeSerializable(mMergedUserIds);
dest.writeLong(mKeyId);
dest.writeByte((byte) (mRevoked ? 1 : 0));
dest.writeByte((byte) (mExpired ? 1 : 0));
dest.writeInt(mDate == null ? 0 : 1);
if (mDate != null) {
dest.writeLong(mDate.getTime());
}
dest.writeString(mFingerprintHex);
dest.writeString(mKeyIdHex);
dest.writeInt(mBitStrength == null ? 0 : 1);
if (mBitStrength != null) {
dest.writeInt(mBitStrength);
}
dest.writeString(mAlgorithm);
dest.writeByte((byte) (mSecretKey ? 1 : 0));
dest.writeByte((byte) (mSelected ? 1 : 0));
dest.writeString(mKeybaseName);
dest.writeString(mFbUsername);
dest.writeStringList(mOrigins);
}
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
public ImportKeysListEntry createFromParcel(final Parcel source) {
ImportKeysListEntry vr = new ImportKeysListEntry();
vr.mPrimaryUserId = source.readString();
vr.mUserIds = new ArrayList<>();
source.readStringList(vr.mUserIds);
vr.mMergedUserIds = (HashMap<String, HashSet<String>>) source.readSerializable();
vr.mKeyId = source.readLong();
vr.mRevoked = source.readByte() == 1;
vr.mExpired = source.readByte() == 1;
vr.mDate = source.readInt() != 0 ? new Date(source.readLong()) : null;
vr.mFingerprintHex = source.readString();
vr.mKeyIdHex = source.readString();
vr.mBitStrength = source.readInt() != 0 ? source.readInt() : null;
vr.mAlgorithm = source.readString();
vr.mSecretKey = source.readByte() == 1;
vr.mSelected = source.readByte() == 1;
vr.mKeybaseName = source.readString();
vr.mFbUsername = source.readString();
vr.mOrigins = new ArrayList<>();
source.readStringList(vr.mOrigins);
return vr;
}
public ImportKeysListEntry[] newArray(final int size) {
return new ImportKeysListEntry[size];
}
};
public int hashCode() {
if (mHashCode != null) {
return mHashCode;
}
return super.hashCode();
} }
public boolean hasSameKeyAs(ImportKeysListEntry other) { public boolean hasSameKeyAs(ImportKeysListEntry other) {
@@ -136,12 +87,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
return mKeyIdHex; return mKeyIdHex;
} }
public boolean isSelected() { public void setKeyIdHex(String keyIdHex) {
return mSelected; mKeyIdHex = keyIdHex;
} }
public void setSelected(boolean selected) { public void setKeyId(long keyId) {
mSelected = selected; mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(keyId);
} }
public boolean isExpired() { public boolean isExpired() {
@@ -152,18 +103,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mExpired = expired; mExpired = expired;
} }
public long getKeyId() {
return mKeyId;
}
public void setKeyId(long keyId) {
mKeyId = keyId;
}
public void setKeyIdHex(String keyIdHex) {
mKeyIdHex = keyIdHex;
}
public boolean isRevoked() { public boolean isRevoked() {
return mRevoked; return mRevoked;
} }
@@ -172,10 +111,22 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mRevoked = revoked; mRevoked = revoked;
} }
public boolean isRevokedOrExpired() {
return mRevoked || mExpired;
}
public Date getDate() { public Date getDate() {
return mDate; return mDate;
} }
public boolean isUpdated() {
return mUpdated;
}
public void setUpdated(boolean updated) {
mUpdated = updated;
}
public void setDate(Date date) { public void setDate(Date date) {
mDate = date; mDate = date;
} }
@@ -188,6 +139,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mFingerprintHex = fingerprintHex; mFingerprintHex = fingerprintHex;
} }
public void setFingerprint(byte[] fingerprint) {
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(fingerprint);
}
public Integer getBitStrength() { public Integer getBitStrength() {
return mBitStrength; return mBitStrength;
} }
@@ -216,35 +171,38 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mSecretKey = secretKey; mSecretKey = secretKey;
} }
public ArrayList<String> getUserIds() { public UserId getPrimaryUserId() {
return mUserIds;
}
public void setUserIds(ArrayList<String> userIds) {
mUserIds = userIds;
updateMergedUserIds();
}
public String getPrimaryUserId() {
return mPrimaryUserId; return mPrimaryUserId;
} }
public void setPrimaryUserId(String uid) { public void setPrimaryUserId(String userId) {
mPrimaryUserId = uid; mPrimaryUserId = KeyRing.splitUserId(userId);
}
public void setPrimaryUserId(UserId primaryUserId) {
mPrimaryUserId = primaryUserId;
}
public ParcelableHkpKeyserver getKeyserver() {
return mKeyserver;
}
public void setKeyserver(ParcelableHkpKeyserver keyserver) {
mKeyserver = keyserver;
} }
public String getKeybaseName() { public String getKeybaseName() {
return mKeybaseName; return mKeybaseName;
} }
public String getFbUsername() {
return mFbUsername;
}
public void setKeybaseName(String keybaseName) { public void setKeybaseName(String keybaseName) {
mKeybaseName = keybaseName; mKeybaseName = keybaseName;
} }
public String getFbUsername() {
return mFbUsername;
}
public void setFbUsername(String fbUsername) { public void setFbUsername(String fbUsername) {
mFbUsername = fbUsername; mFbUsername = fbUsername;
} }
@@ -257,16 +215,49 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mQuery = query; mQuery = query;
} }
public ArrayList<String> getOrigins() { public int hashCode() {
return mOrigins; return mHashCode != null ? mHashCode : super.hashCode();
} }
public void addOrigin(String origin) { public List<String> getUserIds() {
mOrigins.add(origin); // To ensure choerency, use methods of this class to edit the list
return Collections.unmodifiableList(mUserIds);
} }
public HashMap<String, HashSet<String>> getMergedUserIds() { public ArrayList<Map.Entry<String, HashSet<String>>> getSortedUserIds() {
return mMergedUserIds; if (mSortedUserIds == null)
sortMergedUserIds();
return mSortedUserIds;
}
public void setUserIds(ArrayList<String> userIds) {
mUserIds = userIds;
updateMergedUserIds();
}
public boolean addUserIds(List<String> userIds) {
boolean modified = false;
for (String uid : userIds) {
if (!mUserIds.contains(uid)) {
mUserIds.add(uid);
modified = true;
}
}
if (modified)
updateMergedUserIds();
return modified;
}
public ArrayList<String> getKeybaseUserIds() {
ArrayList<String> keybaseUserIds = new ArrayList<>();
for (String s : mUserIds) {
if (s.contains(":"))
keybaseUserIds.add(s);
}
return keybaseUserIds;
} }
/** /**
@@ -275,51 +266,44 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
public ImportKeysListEntry() { public ImportKeysListEntry() {
// keys from keyserver are always public keys; from keybase too // keys from keyserver are always public keys; from keybase too
mSecretKey = false; mSecretKey = false;
// do not select by default
mSelected = false;
mUserIds = new ArrayList<>(); mUserIds = new ArrayList<>();
mOrigins = new ArrayList<>();
} }
/** /**
* Constructor based on key object, used for import from NFC, QR Codes, files * Constructor based on key object, used for import from NFC, QR Codes, files
*/ */
@SuppressWarnings("unchecked") public ImportKeysListEntry(Context ctx, UncachedKeyRing ring) {
public ImportKeysListEntry(Context context, UncachedKeyRing ring) {
// selected is default
this.mSelected = true;
mSecretKey = ring.isSecret(); mSecretKey = ring.isSecret();
UncachedPublicKey key = ring.getPublicKey(); UncachedPublicKey key = ring.getPublicKey();
setPrimaryUserId(key.getPrimaryUserIdWithFallback());
mHashCode = key.hashCode(); setKeyId(key.getKeyId());
setFingerprint(key.getFingerprint());
mPrimaryUserId = key.getPrimaryUserIdWithFallback();
mUserIds = key.getUnorderedUserIds();
updateMergedUserIds();
// if there was no user id flagged as primary, use the first one
if (mPrimaryUserId == null) {
mPrimaryUserId = context.getString(R.string.user_id_none);
}
mKeyId = key.getKeyId();
mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId);
// NOTE: Dont use maybe methods for now, they can be wrong. // NOTE: Dont use maybe methods for now, they can be wrong.
mRevoked = false; //key.isMaybeRevoked(); mRevoked = false; //key.isMaybeRevoked();
mExpired = false; //key.isMaybeExpired(); mExpired = false; //key.isMaybeExpired();
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint());
mBitStrength = key.getBitStrength(); mBitStrength = key.getBitStrength();
mCurveOid = key.getCurveOid(); mCurveOid = key.getCurveOid();
final int algorithm = key.getAlgorithm(); int algorithm = key.getAlgorithm();
mAlgorithm = KeyFormattingUtils.getAlgorithmInfo(context, algorithm, mBitStrength, mCurveOid); mAlgorithm = KeyFormattingUtils.getAlgorithmInfo(ctx, algorithm, mBitStrength, mCurveOid);
mHashCode = key.hashCode();
setUserIds(key.getUnorderedUserIds());
try {
byte[] encoded = ring.getEncoded();
mParcelableKeyRing = new ParcelableKeyRing(encoded);
} catch (IOException ignored) {
}
} }
public void updateMergedUserIds() { private void updateMergedUserIds() {
mMergedUserIds = new HashMap<>(); mMergedUserIds = new HashMap<>();
for (String userId : mUserIds) { for (String userId : mUserIds) {
OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); UserId userIdSplit = KeyRing.splitUserId(userId);
// TODO: comment field? // TODO: comment field?
@@ -341,6 +325,87 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mMergedUserIds.put(userId, new HashSet<String>()); mMergedUserIds.put(userId, new HashSet<String>());
} }
} }
mSortedUserIds = null;
}
private void sortMergedUserIds() {
mSortedUserIds = new ArrayList<>(mMergedUserIds.entrySet());
Collections.sort(mSortedUserIds, new Comparator<Map.Entry<String, HashSet<String>>>() {
@Override
public int compare(Map.Entry<String, HashSet<String>> entry1,
Map.Entry<String, HashSet<String>> entry2) {
// sort keybase UserIds after non-Keybase
boolean e1IsKeybase = entry1.getKey().contains(":");
boolean e2IsKeybase = entry2.getKey().contains(":");
if (e1IsKeybase != e2IsKeybase) {
return (e1IsKeybase) ? 1 : -1;
}
return entry1.getKey().compareTo(entry2.getKey());
}
});
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mParcelableKeyRing, flags);
dest.writeSerializable(mPrimaryUserId);
dest.writeStringList(mUserIds);
dest.writeSerializable(mMergedUserIds);
dest.writeByte((byte) (mRevoked ? 1 : 0));
dest.writeByte((byte) (mExpired ? 1 : 0));
dest.writeByte((byte) (mUpdated ? 1 : 0));
dest.writeInt(mDate == null ? 0 : 1);
if (mDate != null) {
dest.writeLong(mDate.getTime());
}
dest.writeString(mFingerprintHex);
dest.writeString(mKeyIdHex);
dest.writeInt(mBitStrength == null ? 0 : 1);
if (mBitStrength != null) {
dest.writeInt(mBitStrength);
}
dest.writeString(mAlgorithm);
dest.writeByte((byte) (mSecretKey ? 1 : 0));
dest.writeParcelable(mKeyserver, flags);
dest.writeString(mKeybaseName);
dest.writeString(mFbUsername);
}
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
public ImportKeysListEntry createFromParcel(final Parcel source) {
ImportKeysListEntry vr = new ImportKeysListEntry();
vr.mParcelableKeyRing = source.readParcelable(ParcelableKeyRing.class.getClassLoader());
vr.mPrimaryUserId = (UserId) source.readSerializable();
vr.mUserIds = new ArrayList<>();
source.readStringList(vr.mUserIds);
vr.mMergedUserIds = (HashMap<String, HashSet<String>>) source.readSerializable();
vr.mRevoked = source.readByte() == 1;
vr.mExpired = source.readByte() == 1;
vr.mUpdated = source.readByte() == 1;
vr.mDate = source.readInt() != 0 ? new Date(source.readLong()) : null;
vr.mFingerprintHex = source.readString();
vr.mKeyIdHex = source.readString();
vr.mBitStrength = source.readInt() != 0 ? source.readInt() : null;
vr.mAlgorithm = source.readString();
vr.mSecretKey = source.readByte() == 1;
vr.mKeyserver = source.readParcelable(ParcelableHkpKeyserver.class.getClassLoader());
vr.mKeybaseName = source.readString();
vr.mFbUsername = source.readString();
return vr;
}
public ImportKeysListEntry[] newArray(final int size) {
return new ImportKeysListEntry[size];
}
};
public int describeContents() {
return 0;
} }
} }

View File

@@ -18,8 +18,8 @@
package org.sufficientlysecure.keychain.keyimport; package org.sufficientlysecure.keychain.keyimport;
import com.textuality.keybase.lib.KeybaseException; import com.textuality.keybase.lib.KeybaseException;
import com.textuality.keybase.lib.Match;
import com.textuality.keybase.lib.KeybaseQuery; import com.textuality.keybase.lib.KeybaseQuery;
import com.textuality.keybase.lib.Match;
import com.textuality.keybase.lib.User; import com.textuality.keybase.lib.User;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
@@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class KeybaseKeyserver extends Keyserver { public class KeybaseKeyserver extends Keyserver {
public static final String ORIGIN = "keybase:keybase.io";
public KeybaseKeyserver() { public KeybaseKeyserver() {
} }
@@ -68,7 +67,6 @@ public class KeybaseKeyserver extends Keyserver {
private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException { private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry(); final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(query); entry.setQuery(query);
entry.addOrigin(ORIGIN);
entry.setRevoked(false); // keybase doesnt say anything about revoked keys entry.setRevoked(false); // keybase doesnt say anything about revoked keys

View File

@@ -66,6 +66,10 @@ public abstract class Keyserver {
private static final long serialVersionUID = 2703768928624654518L; private static final long serialVersionUID = 2703768928624654518L;
} }
public static class QueryNoEnabledSourceException extends QueryNeedsRepairException {
private static final long serialVersionUID = 2703768928624654519L;
}
public static class AddKeyException extends Exception { public static class AddKeyException extends Exception {
private static final long serialVersionUID = -507574859137295530L; private static final long serialVersionUID = -507574859137295530L;
} }

View File

@@ -22,6 +22,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -269,7 +270,6 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable {
while (matcher.find()) { while (matcher.find()) {
final ImportKeysListEntry entry = new ImportKeysListEntry(); final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(query); entry.setQuery(query);
entry.addOrigin(getHostID());
// group 1 contains the full fingerprint (v4) or the long key id if available // group 1 contains the full fingerprint (v4) or the long key id if available
// see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff // see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff
@@ -293,10 +293,10 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable {
int algorithmId = Integer.decode(matcher.group(2)); int algorithmId = Integer.decode(matcher.group(2));
entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null)); entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null));
final long creationDate = Long.parseLong(matcher.group(4)); long creationDate = Long.parseLong(matcher.group(4));
final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
tmpGreg.setTimeInMillis(creationDate * 1000); calendar.setTimeInMillis(creationDate * 1000);
entry.setDate(tmpGreg.getTime()); entry.setDate(calendar.getTime());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(Constants.TAG, "Conversation for bit size, algorithm, or creation date failed.", e); Log.e(Constants.TAG, "Conversation for bit size, algorithm, or creation date failed.", e);
// skip this key // skip this key
@@ -305,7 +305,18 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable {
try { try {
entry.setRevoked(matcher.group(6).contains("r")); entry.setRevoked(matcher.group(6).contains("r"));
entry.setExpired(matcher.group(6).contains("e")); boolean expired = matcher.group(6).contains("e");
// It may be expired even without flag, thus check expiration date
String expiration;
if (!expired && !(expiration = matcher.group(5)).isEmpty()) {
long expirationDate = Long.parseLong(expiration);
TimeZone timeZoneUTC = TimeZone.getTimeZone("UTC");
GregorianCalendar calendar = new GregorianCalendar(timeZoneUTC);
calendar.setTimeInMillis(expirationDate * 1000);
expired = new GregorianCalendar(timeZoneUTC).compareTo(calendar) >= 0;
}
entry.setExpired(expired);
} catch (NullPointerException e) { } catch (NullPointerException e) {
Log.e(Constants.TAG, "Check for revocation or expiry failed.", e); Log.e(Constants.TAG, "Check for revocation or expiry failed.", e);
// skip this key // skip this key
@@ -338,6 +349,7 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable {
} }
entry.setUserIds(userIds); entry.setUserIds(userIds);
entry.setPrimaryUserId(userIds.get(0)); entry.setPrimaryUserId(userIds.get(0));
entry.setKeyserver(this);
results.add(entry); results.add(entry);
} }

View File

@@ -21,7 +21,8 @@ package org.sufficientlysecure.keychain.keyimport;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
/** This class is a parcelable representation of either a keyring as raw data, /**
* This class is a parcelable representation of either a keyring as raw data,
* or a (unique) reference to one as a fingerprint, keyid, or keybase name. * or a (unique) reference to one as a fingerprint, keyid, or keybase name.
*/ */
public class ParcelableKeyRing implements Parcelable { public class ParcelableKeyRing implements Parcelable {
@@ -35,36 +36,23 @@ public class ParcelableKeyRing implements Parcelable {
public final String mFbUsername; public final String mFbUsername;
public ParcelableKeyRing(byte[] bytes) { public ParcelableKeyRing(byte[] bytes) {
this(null, bytes, false); this(bytes, null, null, null, null);
}
/**
* @param disAmbiguator useless parameter intended to distinguish this overloaded constructor
* for when null is passed as first two arguments
*/
public ParcelableKeyRing(String expectedFingerprint, byte[] bytes, boolean disAmbiguator) {
mBytes = bytes;
mExpectedFingerprint = expectedFingerprint;
mKeyIdHex = null;
mKeybaseName = null;
mFbUsername = null;
}
public ParcelableKeyRing(String expectedFingerprint, String keyIdHex) {
mBytes = null;
mExpectedFingerprint = expectedFingerprint;
mKeyIdHex = keyIdHex;
mKeybaseName = null;
mFbUsername = null;
} }
public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName, public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName,
String fbUsername) { String fbUsername) {
mBytes = null;
mExpectedFingerprint = expectedFingerprint; this(null, expectedFingerprint, keyIdHex, keybaseName, fbUsername);
mKeyIdHex = keyIdHex; }
mKeybaseName = keybaseName;
mFbUsername = fbUsername; public ParcelableKeyRing(byte[] bytes, String expectedFingerprint, String keyIdHex,
String keybaseName, String fbUsername) {
this.mBytes = bytes;
this.mExpectedFingerprint = expectedFingerprint;
this.mKeyIdHex = keyIdHex;
this.mKeybaseName = keybaseName;
this.mFbUsername = fbUsername;
} }
private ParcelableKeyRing(Parcel source) { private ParcelableKeyRing(Parcel source) {
@@ -78,6 +66,7 @@ public class ParcelableKeyRing implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(mBytes); dest.writeByteArray(mBytes);
dest.writeString(mExpectedFingerprint); dest.writeString(mExpectedFingerprint);
dest.writeString(mKeyIdHex); dest.writeString(mKeyIdHex);
dest.writeString(mKeybaseName); dest.writeString(mKeybaseName);

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.keyimport.processing;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;

View File

@@ -0,0 +1,20 @@
package org.sufficientlysecure.keychain.keyimport.processing;
import android.net.Uri;
public class BytesLoaderState implements LoaderState {
public byte[] mKeyBytes;
public Uri mDataUri;
public BytesLoaderState(byte[] keyBytes, Uri dataUri) {
mKeyBytes = keyBytes;
mDataUri = dataUri;
}
@Override
public boolean isBasicModeSupported() {
return true;
}
}

View File

@@ -0,0 +1,20 @@
package org.sufficientlysecure.keychain.keyimport.processing;
import org.sufficientlysecure.keychain.util.Preferences;
public class CloudLoaderState implements LoaderState {
public Preferences.CloudSearchPrefs mCloudPrefs;
public String mServerQuery;
public CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
mServerQuery = serverQuery;
mCloudPrefs = cloudPrefs;
}
@Override
public boolean isBasicModeSupported() {
return false;
}
}

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.keyimport.processing;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -25,6 +25,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.CloudSearch; import org.sufficientlysecure.keychain.keyimport.CloudSearch;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -38,11 +39,9 @@ import java.util.ArrayList;
public class ImportKeysListCloudLoader public class ImportKeysListCloudLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
Context mContext;
private Context mContext;
Preferences.CloudSearchPrefs mCloudPrefs; private CloudLoaderState mState;
String mServerQuery;
private ParcelableProxy mParcelableProxy; private ParcelableProxy mParcelableProxy;
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>(); private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>();
@@ -51,18 +50,18 @@ public class ImportKeysListCloudLoader
/** /**
* Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed * Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed
* *
* @param serverQuery string to search on servers for. If is a fingerprint, * @param loaderState state containing the string to search on servers for (if it is a
* will enforce fingerprint check * fingerprint, will enforce fingerprint check) and the keyserver to
* @param cloudPrefs contains keyserver to search on, whether to search on the keyserver, * search on (whether to search on the keyserver, and whether to search
* and whether to search keybase.io * keybase.io)
* @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences * @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences
*/ */
public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, public ImportKeysListCloudLoader(Context context, CloudLoaderState loaderState,
@Nullable ParcelableProxy parcelableProxy) { @Nullable ParcelableProxy parcelableProxy) {
super(context); super(context);
mContext = context; mContext = context;
mServerQuery = serverQuery; mState = loaderState;
mCloudPrefs = cloudPrefs;
mParcelableProxy = parcelableProxy; mParcelableProxy = parcelableProxy;
} }
@@ -70,18 +69,24 @@ public class ImportKeysListCloudLoader
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() { public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, null); mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, null);
if (mServerQuery == null) { if (mState.mServerQuery == null) {
Log.e(Constants.TAG, "mServerQuery is null!"); Log.e(Constants.TAG, "mServerQuery is null!");
return mEntryListWrapper; return mEntryListWrapper;
} }
if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) { if (mState.mServerQuery.startsWith("0x") && mState.mServerQuery.length() == 42) {
Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!"); Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!");
queryServer(true); queryServer(true);
} else { } else {
queryServer(false); queryServer(false);
} }
// Now we have all the data needed to build the parcelable key ring for this key
for (ImportKeysListEntry e : mEntryList) {
e.setParcelableKeyRing(new ParcelableKeyRing(e.getFingerprintHex(), e.getKeyIdHex(),
e.getKeybaseName(), e.getFbUsername()));
}
return mEntryListWrapper; return mEntryListWrapper;
} }
@@ -133,15 +138,15 @@ public class ImportKeysListCloudLoader
try { try {
ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search( ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search(
mServerQuery, mState.mServerQuery,
mCloudPrefs, mState.mCloudPrefs,
proxy proxy
); );
mEntryList.clear(); mEntryList.clear();
// add result to data // add result to data
if (enforceFingerprint) { if (enforceFingerprint) {
String fingerprint = mServerQuery.substring(2); String fingerprint = mState.mServerQuery.substring(2);
Log.d(Constants.TAG, "fingerprint: " + fingerprint); Log.d(Constants.TAG, "fingerprint: " + fingerprint);
// query must return only one result! // query must return only one result!
if (searchResult.size() == 1) { if (searchResult.size() == 1) {
@@ -151,7 +156,6 @@ public class ImportKeysListCloudLoader
* to enforce a check when the key is imported by KeychainService * to enforce a check when the key is imported by KeychainService
*/ */
uniqueEntry.setFingerprintHex(fingerprint); uniqueEntry.setFingerprintHex(fingerprint);
uniqueEntry.setSelected(true);
mEntryList.add(uniqueEntry); mEntryList.add(uniqueEntry);
} }
} else { } else {
@@ -175,6 +179,9 @@ public class ImportKeysListCloudLoader
} else if (e instanceof Keyserver.QueryTooShortOrTooManyResponsesException) { } else if (e instanceof Keyserver.QueryTooShortOrTooManyResponsesException) {
error = GetKeyResult.RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES; error = GetKeyResult.RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES;
logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES; logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES;
} else if (e instanceof Keyserver.QueryNoEnabledSourceException) {
error = GetKeyResult.RESULT_ERROR_NO_ENABLED_SOURCE;
logType = OperationResult.LogType.MSG_GET_NO_ENABLED_SOURCE;
} }
OperationResult.OperationLog log = new OperationResult.OperationLog(); OperationResult.OperationLog log = new OperationResult.OperationLog();
log.add(logType, 0); log.add(logType, 0);

View File

@@ -15,9 +15,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.keyimport.processing;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.content.AsyncTaskLoader;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -25,40 +42,19 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
public class ImportKeysListLoader public class ImportKeysListLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
final Context mContext; private Context mContext;
final BytesLoaderState mLoaderState; private BytesLoaderState mState;
ArrayList<ImportKeysListEntry> mData = new ArrayList<>(); private ArrayList<ImportKeysListEntry> mData = new ArrayList<>();
LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>(); private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
public ImportKeysListLoader(Context context, BytesLoaderState inputData) { public ImportKeysListLoader(Context context, BytesLoaderState loaderState) {
super(context); super(context);
this.mContext = context; mContext = context;
this.mLoaderState = inputData; mState = loaderState;
} }
@Override @Override
@@ -73,13 +69,13 @@ public class ImportKeysListLoader
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
} }
if (mLoaderState == null) { if (mState == null) {
Log.e(Constants.TAG, "Input data is null!"); Log.e(Constants.TAG, "Input data is null!");
return mEntryListWrapper; return mEntryListWrapper;
} }
try { try {
InputData inputData = getInputData(getContext(), mLoaderState); InputData inputData = getInputData(mState);
generateListOfKeyrings(inputData); generateListOfKeyrings(inputData);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
@@ -109,16 +105,9 @@ public class ImportKeysListLoader
super.cancelLoad(); super.cancelLoad();
} }
@Override /**
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) { * Reads all PGPKeyRing objects from the bytes of an InputData object.
super.deliverResult(data); */
}
public LongSparseArray<ParcelableKeyRing> getParcelableRings() {
return mParcelableRings;
}
/** Reads all PGPKeyRing objects from the bytes of an InputData object. */
private void generateListOfKeyrings(InputData inputData) { private void generateListOfKeyrings(InputData inputData) {
PositionAwareInputStream progressIn = new PositionAwareInputStream( PositionAwareInputStream progressIn = new PositionAwareInputStream(
inputData.getInputStream()); inputData.getInputStream());
@@ -131,10 +120,7 @@ public class ImportKeysListLoader
// parse all keyrings // parse all keyrings
IteratorWithIOThrow<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput); IteratorWithIOThrow<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
while (it.hasNext()) { while (it.hasNext()) {
UncachedKeyRing ring = it.next(); mData.add(new ImportKeysListEntry(mContext, it.next()));
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
mData.add(item);
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
} }
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e); Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);
@@ -147,13 +133,15 @@ public class ImportKeysListLoader
} }
@NonNull @NonNull
private static InputData getInputData(Context context, BytesLoaderState loaderState) throws FileNotFoundException { private InputData getInputData(BytesLoaderState ls)
throws FileNotFoundException {
InputData inputData; InputData inputData;
if (loaderState.mKeyBytes != null) { if (ls.mKeyBytes != null) {
inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length); inputData = new InputData(new ByteArrayInputStream(ls.mKeyBytes), ls.mKeyBytes.length);
} else if (loaderState.mDataUri != null) { } else if (ls.mDataUri != null) {
InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri); InputStream inputStream = mContext.getContentResolver().openInputStream(ls.mDataUri);
long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1); long length = FileHelper.getFileSize(mContext, ls.mDataUri, -1);
inputData = new InputData(inputStream, length); inputData = new InputData(inputStream, length);
} else { } else {

View File

@@ -0,0 +1,13 @@
package org.sufficientlysecure.keychain.keyimport.processing;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import java.util.List;
public interface ImportKeysListener extends ImportKeysResultListener {
void loadKeys(LoaderState loaderState);
void importKeys(List<ImportKeysListEntry> entries);
}

View File

@@ -0,0 +1,46 @@
package org.sufficientlysecure.keychain.keyimport.processing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
public class ImportKeysOperationCallback implements
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
private ImportKeysResultListener mResultListener;
private ImportKeyringParcel mKeyringParcel;
public ImportKeysOperationCallback(
ImportKeysResultListener resultListener,
ImportKeyringParcel inputParcel
) {
this.mResultListener = resultListener;
this.mKeyringParcel = inputParcel;
}
@Override
public ImportKeyringParcel createOperationInput() {
return mKeyringParcel;
}
@Override
public void onCryptoOperationSuccess(ImportKeyResult result) {
mResultListener.handleResult(result);
}
@Override
public void onCryptoOperationCancelled() {
// do nothing
}
@Override
public void onCryptoOperationError(ImportKeyResult result) {
mResultListener.handleResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
}

View File

@@ -0,0 +1,10 @@
package org.sufficientlysecure.keychain.keyimport.processing;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
public interface ImportKeysResultListener {
void handleResult(ImportKeyResult result);
}

View File

@@ -0,0 +1,13 @@
package org.sufficientlysecure.keychain.keyimport.processing;
public interface LoaderState {
/**
* Basic mode includes ability to import all keys retrieved from the selected source
* This doesn't make sense for all sources (for example keyservers..)
*
* @return if currently selected source supports basic mode
*/
boolean isBasicModeSupported();
}

View File

@@ -19,20 +19,6 @@
package org.sufficientlysecure.keychain.operations; package org.sufficientlysecure.keychain.operations;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@@ -49,6 +35,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@@ -57,14 +44,28 @@ import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* An operation class which implements high level import * An operation class which implements high level import
* operations. * operations.
@@ -79,13 +80,13 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
* not include self certificates for user ids in the secret keyring. The import * not include self certificates for user ids in the secret keyring. The import
* method here will generally import keyrings in the order given by the * method here will generally import keyrings in the order given by the
* iterator, so this should be ensured beforehand. * iterator, so this should be ensured beforehand.
*
* @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
*/ */
public class ImportOperation extends BaseOperation<ImportKeyringParcel> { public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
private static final int MAX_THREADS = 10; private static final int MAX_THREADS = 10;
public static final String CACHE_FILE_NAME = "key_import.pcl";
public ImportOperation(Context context, ProviderHelper providerHelper, Progressable public ImportOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) { progressable) {
super(context, providerHelper, progressable); super(context, providerHelper, progressable);
@@ -98,20 +99,20 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// Overloaded functions for using progressable supplied in constructor during import // Overloaded functions for using progressable supplied in constructor during import
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
ParcelableHkpKeyserver hkpKeyserver, ParcelableProxy proxy) { ParcelableHkpKeyserver keyserver, ParcelableProxy proxy, boolean skipSave) {
return serialKeyRingImport(entries, num, hkpKeyserver, mProgressable, proxy); return serialKeyRingImport(entries, num, keyserver, mProgressable, proxy, skipSave);
} }
@NonNull @NonNull
private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache, private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
ParcelableHkpKeyserver hkpKeyserver, ParcelableProxy proxy) { ParcelableHkpKeyserver keyserver, ParcelableProxy proxy, boolean skipSave) {
// get entries from cached file // get entries from cached file
try { try {
IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
int numEntries = it.getSize(); int numEntries = it.getSize();
return serialKeyRingImport(it, numEntries, hkpKeyserver, mProgressable, proxy); return serialKeyRingImport(it, numEntries, keyserver, mProgressable, proxy, skipSave);
} catch (IOException e) { } catch (IOException e) {
// Special treatment here, we need a lot // Special treatment here, we need a lot
@@ -137,7 +138,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
@NonNull @NonNull
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
ParcelableHkpKeyserver hkpKeyserver, Progressable progressable, ParcelableHkpKeyserver hkpKeyserver, Progressable progressable,
@NonNull ParcelableProxy proxy) { @NonNull ParcelableProxy proxy, boolean skipSave) {
if (progressable != null) { if (progressable != null) {
progressable.setProgress(R.string.progress_importing, 0, 100); progressable.setProgress(R.string.progress_importing, 0, 100);
} }
@@ -153,6 +154,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0; int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0;
ArrayList<Long> importedMasterKeyIds = new ArrayList<>(); ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
ArrayList<CanonicalizedKeyRing> canKeyRings = new ArrayList<>();
boolean cancelled = false; boolean cancelled = false;
int position = 0; int position = 0;
double progSteps = 100.0 / num; double progSteps = 100.0 / num;
@@ -314,14 +317,14 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// and https://github.com/open-keychain/open-keychain/issues/1480 // and https://github.com/open-keychain/open-keychain/issues/1480
synchronized (mProviderHelper) { synchronized (mProviderHelper) {
mProviderHelper.clearLog(); mProviderHelper.clearLog();
ProgressScaler progressScaler = new ProgressScaler(progressable, (int) (position * progSteps),
(int) ((position + 1) * progSteps), 100);
if (key.isSecret()) { if (key.isSecret()) {
result = mProviderHelper.saveSecretKeyRing(key, result = mProviderHelper.saveSecretKeyRing(key, progressScaler,
new ProgressScaler(progressable, (int) (position * progSteps), canKeyRings, skipSave);
(int) ((position + 1) * progSteps), 100));
} else { } else {
result = mProviderHelper.savePublicKeyRing(key, result = mProviderHelper.savePublicKeyRing(key, progressScaler,
new ProgressScaler(progressable, (int) (position * progSteps), entry.mExpectedFingerprint, canKeyRings, skipSave);
(int) ((position + 1) * progSteps), 100), entry.mExpectedFingerprint);
} }
} }
if (!result.success()) { if (!result.success()) {
@@ -337,7 +340,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
importedMasterKeyIds.add(key.getMasterKeyId()); importedMasterKeyIds.add(key.getMasterKeyId());
} }
if (entry.mBytes == null) { if (!skipSave && (entry.mBytes == null)) {
// synonymous to isDownloadFromKeyserver. // synonymous to isDownloadFromKeyserver.
// If no byte data was supplied, import from keyserver took place // If no byte data was supplied, import from keyserver took place
// this prevents file imports being noted as keyserver imports // this prevents file imports being noted as keyserver imports
@@ -360,7 +363,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// synchronized on mProviderHelper to prevent // synchronized on mProviderHelper to prevent
// https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes // https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes
// and re-inserts keys, which could conflict with a parallel db key update // and re-inserts keys, which could conflict with a parallel db key update
if (secret > 0) { if (!skipSave && (secret > 0)) {
setPreventCancel(); setPreventCancel();
ConsolidateResult result; ConsolidateResult result;
synchronized (mProviderHelper) { synchronized (mProviderHelper) {
@@ -418,8 +421,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
} }
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, ImportKeyResult result = new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
importedMasterKeyIdsArray); importedMasterKeyIdsArray);
result.setCanonicalizedKeyRings(canKeyRings);
return result;
} }
@NonNull @NonNull
@@ -427,19 +433,18 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList; ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
ParcelableHkpKeyserver keyServer = importInput.mKeyserver; ParcelableHkpKeyserver keyServer = importInput.mKeyserver;
boolean skipSave = importInput.mSkipSave;
ImportKeyResult result; ImportKeyResult result;
if (keyList == null) {// import from file, do serially if (keyList == null) {// import from file, do serially
ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext, ParcelableFileCache<ParcelableKeyRing> cache =
"key_import.pcl"); new ParcelableFileCache<>(mContext, CACHE_FILE_NAME);
result = serialKeyRingImport(cache, null, null, skipSave);
result = serialKeyRingImport(cache, null, null);
} else { } else {
ParcelableProxy proxy; ParcelableProxy proxy;
if (cryptoInput.getParcelableProxy() == null) { if (cryptoInput.getParcelableProxy() == null) {
// explicit proxy not set // explicit proxy not set
if(!OrbotHelper.isOrbotInRequiredState(mContext)) { if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
// show dialog to enable/install dialog // show dialog to enable/install dialog
return new ImportKeyResult(null, return new ImportKeyResult(null,
RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
@@ -449,7 +454,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
proxy = cryptoInput.getParcelableProxy(); proxy = cryptoInput.getParcelableProxy();
} }
result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy); result = multiThreadedKeyImport(keyList, keyServer, proxy, skipSave);
} }
ContactSyncAdapterService.requestContactsSync(); ContactSyncAdapterService.requestContactsSync();
@@ -457,44 +462,43 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
@NonNull @NonNull
private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator<ParcelableKeyRing> keyListIterator, private ImportKeyResult multiThreadedKeyImport(ArrayList<ParcelableKeyRing> keyList,
int totKeys, final ParcelableHkpKeyserver hkpKeyserver, final ParcelableHkpKeyserver keyServer, final ParcelableProxy proxy,
final ParcelableProxy proxy) { final boolean skipSave) {
Log.d(Constants.TAG, "Multi-threaded key import starting");
KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
final ProgressScaler ignoreProgressable = new ProgressScaler(); Log.d(Constants.TAG, "Multi-threaded key import starting");
final Iterator<ParcelableKeyRing> keyListIterator = keyList.iterator();
final int totKeys = keyList.size();
ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS, ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()); new LinkedBlockingQueue<Runnable>());
ExecutorCompletionService<ImportKeyResult> importCompletionService = ExecutorCompletionService<ImportKeyResult> importCompletionService =
new ExecutorCompletionService<>(importExecutor); new ExecutorCompletionService<>(importExecutor);
while (keyListIterator.hasNext()) { // submit all key rings to be imported while (keyListIterator.hasNext()) { // submit all key rings to be imported
final ParcelableKeyRing pkRing = keyListIterator.next();
Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult> Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
() { () {
@Override @Override
public ImportKeyResult call() { public ImportKeyResult call() {
if (checkCancelled()) { if (checkCancelled()) {
return null; return null;
} }
ArrayList<ParcelableKeyRing> list = new ArrayList<>(); ArrayList<ParcelableKeyRing> list = new ArrayList<>();
list.add(pkRing); list.add(keyListIterator.next());
ProgressScaler ignoreProgressable = new ProgressScaler();
return serialKeyRingImport(list.iterator(), 1, hkpKeyserver, ignoreProgressable, proxy); return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable,
proxy, skipSave);
} }
}; };
importCompletionService.submit(importOperationCallable); importCompletionService.submit(importOperationCallable);
} }
KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
while (!accumulator.isImportFinished()) { // accumulate the results of each import while (!accumulator.isImportFinished()) { // accumulate the results of each import
try { try {
accumulator.accumulateKeyImport(importCompletionService.take().get()); accumulator.accumulateKeyImport(importCompletionService.take().get());
@@ -511,7 +515,6 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
} }
return accumulator.getConsolidatedResult(); return accumulator.getConsolidatedResult();
} }
/** /**
@@ -519,10 +522,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
*/ */
public static class KeyImportAccumulator { public static class KeyImportAccumulator {
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog(); private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
Progressable mProgressable; private Progressable mProgressable;
private int mTotalKeys; private int mTotalKeys;
private int mImportedKeys = 0; private int mImportedKeys = 0;
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>(); private ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>();
private int mBadKeys = 0; private int mBadKeys = 0;
private int mNewKeys = 0; private int mNewKeys = 0;
private int mUpdatedKeys = 0; private int mUpdatedKeys = 0;
@@ -530,6 +533,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
private int mResultType = 0; private int mResultType = 0;
private boolean mHasCancelledResult; private boolean mHasCancelledResult;
public ArrayList<CanonicalizedKeyRing> mCanonicalizedKeyRings;
/** /**
* Accumulates keyring imports and updates the progressable whenever a new key is imported. * Accumulates keyring imports and updates the progressable whenever a new key is imported.
* Also sets the progress to 0 on instantiation. * Also sets the progress to 0 on instantiation.
@@ -544,6 +549,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (mProgressable != null) { if (mProgressable != null) {
mProgressable.setProgress(0, totalKeys); mProgressable.setProgress(0, totalKeys);
} }
mCanonicalizedKeyRings = new ArrayList<>();
} }
public void accumulateKeyImport(ImportKeyResult result) { public void accumulateKeyImport(ImportKeyResult result) {
@@ -575,6 +582,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
mImportedMasterKeyIds.add(masterKeyId); mImportedMasterKeyIds.add(masterKeyId);
} }
mCanonicalizedKeyRings.addAll(result.mCanonicalizedKeyRings);
// if any key import has been cancelled, set result type to cancelled // if any key import has been cancelled, set result type to cancelled
// resultType is added to in getConsolidatedKayImport to account for remaining factors // resultType is added to in getConsolidatedKayImport to account for remaining factors
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED; mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
@@ -614,8 +623,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
masterKeyIds[i] = mImportedMasterKeyIds.get(i); masterKeyIds[i] = mImportedMasterKeyIds.get(i);
} }
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys, ImportKeyResult result = new ImportKeyResult(mResultType, mImportLog, mNewKeys,
mSecret, masterKeyIds); mUpdatedKeys, mBadKeys, mSecret, masterKeyIds);
result.setCanonicalizedKeyRings(mCanonicalizedKeyRings);
return result;
} }
public boolean isImportFinished() { public boolean isImportFinished() {

View File

@@ -44,13 +44,14 @@ public class GetKeyResult extends InputPendingResult {
super(log, requiredInput, cryptoInputParcel); super(log, requiredInput, cryptoInputParcel);
} }
public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + (1<<4); public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + (1 << 4);
public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + (2<<4); public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + (2 << 4);
public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + (3<<4); public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + (3 << 4);
public static final int RESULT_ERROR_TOO_MANY_RESPONSES = RESULT_ERROR + (4<<4); public static final int RESULT_ERROR_TOO_MANY_RESPONSES = RESULT_ERROR + (4 << 4);
public static final int RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES = RESULT_ERROR + (5<<4); public static final int RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES = RESULT_ERROR + (5 << 4);
public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + (6<<4); public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + (6 << 4);
public static final int RESULT_ERROR_FILE_NOT_FOUND = RESULT_ERROR + (7<<4); public static final int RESULT_ERROR_FILE_NOT_FOUND = RESULT_ERROR + (7 << 4);
public static final int RESULT_ERROR_NO_ENABLED_SOURCE = RESULT_ERROR + (8 << 4);
public GetKeyResult(Parcel source) { public GetKeyResult(Parcel source) {
super(source); super(source);

View File

@@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Parcel; import android.os.Parcel;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
@@ -32,11 +33,16 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import java.util.ArrayList;
public class ImportKeyResult extends InputPendingResult { public class ImportKeyResult extends InputPendingResult {
public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
public final long[] mImportedMasterKeyIds; public final long[] mImportedMasterKeyIds;
// NOT PARCELED
public ArrayList<CanonicalizedKeyRing> mCanonicalizedKeyRings;
// At least one new key // At least one new key
public static final int RESULT_OK_NEWKEYS = 8; public static final int RESULT_OK_NEWKEYS = 8;
// At least one updated key // At least one updated key
@@ -107,6 +113,10 @@ public class ImportKeyResult extends InputPendingResult {
mImportedMasterKeyIds = new long[]{}; mImportedMasterKeyIds = new long[]{};
} }
public void setCanonicalizedKeyRings(ArrayList<CanonicalizedKeyRing> canonicalizedKeyRings) {
this.mCanonicalizedKeyRings = canonicalizedKeyRings;
}
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
@@ -128,7 +138,6 @@ public class ImportKeyResult extends InputPendingResult {
}; };
public Showable createNotify(final Activity activity) { public Showable createNotify(final Activity activity) {
int resultType = getResult(); int resultType = getResult();
String str; String str;
@@ -204,7 +213,6 @@ public class ImportKeyResult extends InputPendingResult {
activity.startActivity(intent); activity.startActivity(intent);
} }
}, R.string.snackbar_details); }, R.string.snackbar_details);
} }
} }

View File

@@ -818,6 +818,7 @@ public abstract class OperationResult implements Parcelable {
MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES (LogLevel.ERROR, R.string.msg_get_query_too_short_or_too_many_responses), MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES (LogLevel.ERROR, R.string.msg_get_query_too_short_or_too_many_responses),
MSG_GET_QUERY_FAILED (LogLevel.ERROR, R.string.msg_download_query_failed), MSG_GET_QUERY_FAILED (LogLevel.ERROR, R.string.msg_download_query_failed),
MSG_GET_FILE_NOT_FOUND (LogLevel.ERROR, R.string.msg_get_file_not_found), MSG_GET_FILE_NOT_FOUND (LogLevel.ERROR, R.string.msg_get_file_not_found),
MSG_GET_NO_ENABLED_SOURCE (LogLevel.ERROR, R.string.msg_get_no_enabled_source),
MSG_DEL_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_del_error_empty), MSG_DEL_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_del_error_empty),
MSG_DEL_ERROR_MULTI_SECRET (LogLevel.ERROR, R.string.msg_del_error_multi_secret), MSG_DEL_ERROR_MULTI_SECRET (LogLevel.ERROR, R.string.msg_del_error_multi_secret),

View File

@@ -29,18 +29,17 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; import java.util.Set;
/** A generic wrapped PGPKeyRing object. /**
* * A generic wrapped PGPKeyRing object.
* <p>
* This class provides implementations for all basic getters which both * This class provides implementations for all basic getters which both
* PublicKeyRing and SecretKeyRing have in common. To make the wrapped keyring * PublicKeyRing and SecretKeyRing have in common. To make the wrapped keyring
* class typesafe in implementing subclasses, the field is stored in the * class typesafe in implementing subclasses, the field is stored in the
* implementing class, providing properly typed access through the getRing * implementing class, providing properly typed access through the getRing
* getter method. * getter method.
*
*/ */
public abstract class CanonicalizedKeyRing extends KeyRing { public abstract class CanonicalizedKeyRing extends KeyRing {
@@ -80,16 +79,24 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
public boolean isRevoked() { public boolean isRevoked() {
// Is the master key revoked? // Is the master key revoked?
return getRing().getPublicKey().isRevoked(); return getRing().getPublicKey().hasRevocation();
}
public Date getCreationDate() {
return getPublicKey().getCreationTime();
}
public Date getExpirationDate() {
return getPublicKey().getExpiryTime();
} }
public boolean isExpired() { public boolean isExpired() {
// Is the master key expired? // Is the master key expired?
Date creationDate = getPublicKey().getCreationTime(); Date creationDate = getCreationDate();
Date expiryDate = getPublicKey().getExpiryTime(); Date expirationDate = getExpirationDate();
Date now = new Date(); Date now = new Date();
return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); return creationDate.after(now) || (expirationDate != null && expirationDate.before(now));
} }
public boolean canCertify() throws PgpKeyNotFoundException { public boolean canCertify() throws PgpKeyNotFoundException {
@@ -98,7 +105,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
public Set<Long> getEncryptIds() { public Set<Long> getEncryptIds() {
HashSet<Long> result = new HashSet<>(); HashSet<Long> result = new HashSet<>();
for(CanonicalizedPublicKey key : publicKeyIterator()) { for (CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) { if (key.canEncrypt() && key.isValid()) {
result.add(key.getKeyId()); result.add(key.getKeyId());
} }
@@ -107,7 +114,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
} }
public long getEncryptId() throws PgpKeyNotFoundException { public long getEncryptId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) { for (CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) { if (key.canEncrypt() && key.isValid()) {
return key.getKeyId(); return key.getKeyId();
} }
@@ -119,7 +126,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
try { try {
getEncryptId(); getEncryptId();
return true; return true;
} catch(PgpKeyNotFoundException e) { } catch (PgpKeyNotFoundException e) {
return false; return false;
} }
} }
@@ -128,8 +135,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
getRing().encode(stream); getRing().encode(stream);
} }
/** Returns an UncachedKeyRing which wraps the same data as this ring. This method should /**
* only be used */ * Returns an UncachedKeyRing which wraps the same data as this ring. This method should
* only be used
*/
public UncachedKeyRing getUncachedKeyRing() { public UncachedKeyRing getUncachedKeyRing() {
return new UncachedKeyRing(getRing()); return new UncachedKeyRing(getRing());
} }

View File

@@ -173,7 +173,7 @@ public class CachedPublicKeyRing extends KeyRing {
Object data = mProviderHelper.getGenericData(mUri, Object data = mProviderHelper.getGenericData(mUri,
KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.VERIFIED,
ProviderHelper.FIELD_TYPE_INTEGER); ProviderHelper.FIELD_TYPE_INTEGER);
return (Integer) data; return ((Long) data).intValue();
} catch(ProviderHelper.NotFoundException e) { } catch(ProviderHelper.NotFoundException e) {
throw new PgpKeyNotFoundException(e); throw new PgpKeyNotFoundException(e);
} }

View File

@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
@@ -61,9 +62,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -276,7 +277,7 @@ public class ProviderHelper {
public ArrayList<String> getConfirmedUserIds(long masterKeyId) throws NotFoundException { public ArrayList<String> getConfirmedUserIds(long masterKeyId) throws NotFoundException {
Cursor cursor = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), Cursor cursor = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId),
new String[]{ UserPackets.USER_ID }, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null
); );
if (cursor == null) { if (cursor == null) {
throw new NotFoundException("Key id for requested user ids not found"); throw new NotFoundException("Key id for requested user ids not found");
@@ -476,7 +477,7 @@ public class ProviderHelper {
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
UserPacketItem item = new UserPacketItem(); UserPacketItem item = new UserPacketItem();
uids.add(item); uids.add(item);
OpenPgpUtils.UserId splitUserId = KeyRing.splitUserId(userId); OpenPgpUtils.UserId splitUserId = KeyRing.splitUserId(userId);
item.userId = userId; item.userId = userId;
item.name = splitUserId.name; item.name = splitUserId.name;
item.email = splitUserId.email; item.email = splitUserId.email;
@@ -794,7 +795,7 @@ public class ProviderHelper {
} }
// if one is *trusted* but the other isn't, that one comes first // if one is *trusted* but the other isn't, that one comes first
// this overrides the primary attribute, even! // this overrides the primary attribute, even!
if ( (trustedCerts.size() == 0) != (o.trustedCerts.size() == 0) ) { if ((trustedCerts.size() == 0) != (o.trustedCerts.size() == 0)) {
return trustedCerts.size() > o.trustedCerts.size() ? -1 : 1; return trustedCerts.size() > o.trustedCerts.size() ? -1 : 1;
} }
// if one key is primary but the other isn't, the primary one always comes first // if one key is primary but the other isn't, the primary one always comes first
@@ -903,17 +904,16 @@ public class ProviderHelper {
} }
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
return savePublicKeyRing(keyRing, new ProgressScaler(), null);
}
/** /**
* Save a public keyring into the database. * Save a public keyring into the database.
* <p/> * <p/>
* This is a high level method, which takes care of merging all new information into the old and * This is a high level method, which takes care of merging all new information into the old and
* keep public and secret keyrings in sync. * keep public and secret keyrings in sync.
*/ */
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, String expectedFingerprint) { public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress,
String expectedFingerprint,
ArrayList<CanonicalizedKeyRing> canKeyRings,
boolean skipSave) {
try { try {
long masterKeyId = publicRing.getMasterKeyId(); long masterKeyId = publicRing.getMasterKeyId();
@@ -926,10 +926,12 @@ public class ProviderHelper {
} }
CanonicalizedPublicKeyRing canPublicRing; CanonicalizedPublicKeyRing canPublicRing;
boolean alreadyExists = false;
// If there is an old keyring, merge it // If there is an old keyring, merge it
try { try {
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing(); UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
alreadyExists = true;
// Merge data from new public ring into the old one // Merge data from new public ring into the old one
log(LogType.MSG_IP_MERGE_PUBLIC); log(LogType.MSG_IP_MERGE_PUBLIC);
@@ -945,6 +947,7 @@ public class ProviderHelper {
if (canPublicRing == null) { if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
if (canKeyRings != null) canKeyRings.add(canPublicRing);
// Early breakout if nothing changed // Early breakout if nothing changed
if (Arrays.hashCode(publicRing.getEncoded()) if (Arrays.hashCode(publicRing.getEncoded())
@@ -960,7 +963,7 @@ public class ProviderHelper {
if (canPublicRing == null) { if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
if (canKeyRings != null) canKeyRings.add(canPublicRing);
} }
// If there is a secret key, merge new data (if any) and save the key for later // If there is a secret key, merge new data (if any) and save the key for later
@@ -997,29 +1000,51 @@ public class ProviderHelper {
} }
} }
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null); int result;
if (!skipSave) {
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
} else {
result = SaveKeyringResult.SAVED_PUBLIC
| (alreadyExists ? SaveKeyringResult.UPDATED : 0);
}
// Save the saved keyring (if any) // Save the saved keyring (if any)
if (canSecretRing != null) { if (canSecretRing != null) {
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
int secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
int secretResult;
if (!skipSave) {
secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
} else {
secretResult = SaveKeyringResult.SAVED_SECRET;
}
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) { if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
result |= SaveKeyringResult.SAVED_SECRET; result |= SaveKeyringResult.SAVED_SECRET;
} }
} }
return new SaveKeyringResult(result, mLog, canSecretRing); return new SaveKeyringResult(result, mLog, canSecretRing);
} catch (IOException e) { } catch (IOException e) {
log(LogType.MSG_IP_ERROR_IO_EXC); log(LogType.MSG_IP_ERROR_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} finally { } finally {
mIndent -= 1; mIndent -= 1;
} }
} }
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) { public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress,
String expectedFingerprint) {
return savePublicKeyRing(publicRing, progress, expectedFingerprint, null, false);
}
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
return savePublicKeyRing(keyRing, new ProgressScaler(), null);
}
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress,
ArrayList<CanonicalizedKeyRing> canKeyRings,
boolean skipSave) {
try { try {
long masterKeyId = secretRing.getMasterKeyId(); long masterKeyId = secretRing.getMasterKeyId();
@@ -1032,10 +1057,12 @@ public class ProviderHelper {
} }
CanonicalizedSecretKeyRing canSecretRing; CanonicalizedSecretKeyRing canSecretRing;
boolean alreadyExists = false;
// If there is an old secret key, merge it. // If there is an old secret key, merge it.
try { try {
UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing(); UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing();
alreadyExists = true;
// Merge data from new secret ring into old one // Merge data from new secret ring into old one
log(LogType.MSG_IS_MERGE_SECRET); log(LogType.MSG_IS_MERGE_SECRET);
@@ -1052,6 +1079,7 @@ public class ProviderHelper {
if (canSecretRing == null) { if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
if (canKeyRings != null) canKeyRings.add(canSecretRing);
// Early breakout if nothing changed // Early breakout if nothing changed
if (Arrays.hashCode(secretRing.getEncoded()) if (Arrays.hashCode(secretRing.getEncoded())
@@ -1083,7 +1111,7 @@ public class ProviderHelper {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
} }
if (canKeyRings != null) canKeyRings.add(canSecretRing);
} }
// Merge new data into public keyring as well, if there is any // Merge new data into public keyring as well, if there is any
@@ -1109,25 +1137,38 @@ public class ProviderHelper {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
int result; int publicResult;
if (!skipSave) {
publicResult = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
} else {
publicResult = SaveKeyringResult.SAVED_PUBLIC;
}
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true); if ((publicResult & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} }
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
result = saveCanonicalizedSecretKeyRing(canSecretRing);
int result;
if (!skipSave) {
result = saveCanonicalizedSecretKeyRing(canSecretRing);
} else {
result = SaveKeyringResult.SAVED_SECRET
| (alreadyExists ? SaveKeyringResult.UPDATED : 0);
}
return new SaveKeyringResult(result, mLog, canSecretRing); return new SaveKeyringResult(result, mLog, canSecretRing);
} catch (IOException e) { } catch (IOException e) {
log(LogType.MSG_IS_ERROR_IO_EXC); log(LogType.MSG_IS_ERROR_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} finally { } finally {
mIndent -= 1; mIndent -= 1;
} }
}
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) {
return saveSecretKeyRing(secretRing, progress, null, false);
} }
@NonNull @NonNull
@@ -1350,7 +1391,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportOperation(mContext, this, ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
.serialKeyRingImport(itSecrets, numSecrets, null, null); .serialKeyRingImport(itSecrets, numSecrets, null, null, false);
log.add(result, indent); log.add(result, indent);
} else { } else {
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
@@ -1378,7 +1419,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportOperation(mContext, this, ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
.serialKeyRingImport(itPublics, numPublics, null, null); .serialKeyRingImport(itPublics, numPublics, null, null, false);
log.add(result, indent); log.add(result, indent);
// re-insert our backed up list of updated key times // re-insert our backed up list of updated key times
// TODO: can this cause issues in case a public key re-import failed? // TODO: can this cause issues in case a public key re-import failed?

View File

@@ -21,9 +21,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.ui.ImportKeysActivity; import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity;
public class RemoteImportKeysActivity extends ImportKeysActivity { public class RemoteImportKeysActivity extends ImportKeysActivity {
@@ -39,7 +37,7 @@ public class RemoteImportKeysActivity extends ImportKeysActivity {
} }
@Override @Override
protected void handleResult(ImportKeyResult result) { public void handleResult(ImportKeyResult result) {
setResult(RESULT_OK, mPendingIntentData); setResult(RESULT_OK, mPendingIntentData);
finish(); finish();
} }

View File

@@ -27,15 +27,23 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import java.util.ArrayList; import java.util.ArrayList;
public class ImportKeyringParcel implements Parcelable { public class ImportKeyringParcel implements Parcelable {
// if null, keys are expected to be read from a cache file in ImportExportOperations // If null, keys are expected to be read from a cache file in ImportExportOperations
public ArrayList<ParcelableKeyRing> mKeyList; public ArrayList<ParcelableKeyRing> mKeyList;
public ParcelableHkpKeyserver mKeyserver; // must be set if keys are to be imported from a keyserver public ParcelableHkpKeyserver mKeyserver; // must be set if keys are to be imported from a keyserver
// If false, don't save the key, only return it as part of result
public boolean mSkipSave = false;
public ImportKeyringParcel(ArrayList<ParcelableKeyRing> keyList, ParcelableHkpKeyserver keyserver) { public ImportKeyringParcel(ArrayList<ParcelableKeyRing> keyList, ParcelableHkpKeyserver keyserver) {
mKeyList = keyList; mKeyList = keyList;
mKeyserver = keyserver; mKeyserver = keyserver;
} }
public ImportKeyringParcel(ArrayList<ParcelableKeyRing> keyList, ParcelableHkpKeyserver keyserver, boolean skipSave) {
this(keyList, keyserver);
mSkipSave = skipSave;
}
protected ImportKeyringParcel(Parcel in) { protected ImportKeyringParcel(Parcel in) {
if (in.readByte() == 0x01) { if (in.readByte() == 0x01) {
mKeyList = new ArrayList<>(); mKeyList = new ArrayList<>();
@@ -44,6 +52,7 @@ public class ImportKeyringParcel implements Parcelable {
mKeyList = null; mKeyList = null;
} }
mKeyserver = in.readParcelable(ParcelableHkpKeyserver.class.getClassLoader()); mKeyserver = in.readParcelable(ParcelableHkpKeyserver.class.getClassLoader());
mSkipSave = in.readInt() != 0;
} }
@Override @Override
@@ -60,6 +69,7 @@ public class ImportKeyringParcel implements Parcelable {
dest.writeList(mKeyList); dest.writeList(mKeyList);
} }
dest.writeParcelable(mKeyserver, flags); dest.writeParcelable(mKeyserver, flags);
dest.writeInt(mSkipSave ? 1 : 0);
} }
public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() { public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() {

View File

@@ -465,7 +465,7 @@ public class KeyserverSyncAdapterService extends Service {
String hexKeyId = KeyFormattingUtils String hexKeyId = KeyFormattingUtils
.convertKeyIdToHex(keyId); .convertKeyIdToHex(keyId);
// we aren't updating from keybase as of now // we aren't updating from keybase as of now
keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId)); keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null, null));
} }
keyCursor.close(); keyCursor.close();

View File

@@ -18,10 +18,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@@ -36,6 +32,7 @@ import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
@@ -48,6 +45,10 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class CreateSecurityTokenImportResetFragment public class CreateSecurityTokenImportResetFragment
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
@@ -211,14 +212,14 @@ public class CreateSecurityTokenImportResetFragment
} }
public void refreshSearch() { public void refreshSearch() {
mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mTokenFingerprint, mListFragment.loadState(new CloudLoaderState("0x" + mTokenFingerprint,
Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); Preferences.getPreferences(getActivity()).getCloudSearchPrefs()));
} }
public void importKey() { public void importKey() {
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>(); ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
keyList.add(new ParcelableKeyRing(mTokenFingerprint, null)); keyList.add(new ParcelableKeyRing(mTokenFingerprint, null, null, null));
mKeyList = keyList; mKeyList = keyList;
mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();

View File

@@ -17,8 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
@@ -59,6 +57,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.util.ArrayList;
public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int LOADER_ID_UNIFIED = 0; public static final int LOADER_ID_UNIFIED = 0;
@@ -145,7 +145,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
{ {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry); selectedEntries.add(keyEntry);
@@ -320,7 +320,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
mSignatureEmail.setText(userIdSplit.email); mSignatureEmail.setText(userIdSplit.email);
} else { } else {
mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(
getActivity(), mSignatureResult.getKeyId())); mSignatureResult.getKeyId()));
} }
// NOTE: Don't use revoked and expired fields from database, they don't show // NOTE: Don't use revoked and expired fields from database, they don't show
@@ -430,7 +430,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
mSignatureEmail.setText(userIdSplit.email); mSignatureEmail.setText(userIdSplit.email);
} else { } else {
mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(
getActivity(), mSignatureResult.getKeyId())); mSignatureResult.getKeyId()));
} }
switch (mSignatureResult.getResult()) { switch (mSignatureResult.getResult()) {

View File

@@ -68,6 +68,7 @@ import android.widget.Toast;
import android.widget.ViewAnimator; import android.widget.ViewAnimator;
import com.cocosw.bottomsheet.BottomSheet; import com.cocosw.bottomsheet.BottomSheet;
import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.BuildConfig;
@@ -98,24 +99,24 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
/** Displays a list of decrypted inputs. /**
* * Displays a list of decrypted inputs.
* <p/>
* This class has a complex control flow to manage its input URIs. Each URI * This class has a complex control flow to manage its input URIs. Each URI
* which is in mInputUris is also in exactly one of mPendingInputUris, * which is in mInputUris is also in exactly one of mPendingInputUris,
* mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults. * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults.
* * <p/>
* Processing of URIs happens using a looping approach: * Processing of URIs happens using a looping approach:
* - There is always exactly one method running which works on mCurrentInputUri * - There is always exactly one method running which works on mCurrentInputUri
* - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri
* from the list of mPendingInputUris. * from the list of mPendingInputUris.
* - Once a mCurrentInputUri is finished processing, it should be set to null and * - Once a mCurrentInputUri is finished processing, it should be set to null and
* control handed back to cryptoOperation() * control handed back to cryptoOperation()
* - Control flow can move through asynchronous calls, and resume in callbacks * - Control flow can move through asynchronous calls, and resume in callbacks
* like onActivityResult() or onPermissionRequestResult(). * like onActivityResult() or onPermissionRequestResult().
*
*/ */
public class DecryptListFragment public class DecryptListFragment
extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult> extends QueueingCryptoOperationFragment<InputDataParcel, InputDataResult>
implements OnMenuItemClickListener { implements OnMenuItemClickListener {
public static final String ARG_INPUT_URIS = "input_uris"; public static final String ARG_INPUT_URIS = "input_uris";
@@ -189,7 +190,7 @@ public class DecryptListFragment
outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris); outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris);
HashMap<Uri,InputDataResult> results = new HashMap<>(mInputUris.size()); HashMap<Uri, InputDataResult> results = new HashMap<>(mInputUris.size());
for (Uri uri : mInputUris) { for (Uri uri : mInputUris) {
if (mPendingInputUris.contains(uri)) { if (mPendingInputUris.contains(uri)) {
continue; continue;
@@ -219,7 +220,7 @@ public class DecryptListFragment
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS); ParcelableHashMap<Uri, InputDataResult> results = args.getParcelable(ARG_RESULTS);
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
@@ -231,11 +232,11 @@ public class DecryptListFragment
private void displayInputUris( private void displayInputUris(
ArrayList<Uri> inputUris, ArrayList<Uri> inputUris,
ArrayList<Uri> cancelledUris, ArrayList<Uri> cancelledUris,
HashMap<Uri,InputDataResult> results) { HashMap<Uri, InputDataResult> results) {
mInputUris = inputUris; mInputUris = inputUris;
mCurrentInputUri = null; mCurrentInputUri = null;
mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size()); mInputDataResults = results != null ? results : new HashMap<Uri, InputDataResult>(inputUris.size());
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>(); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
mPendingInputUris = new ArrayList<>(); mPendingInputUris = new ArrayList<>();
@@ -295,7 +296,7 @@ public class DecryptListFragment
String filename = metadata.getFilename(); String filename = metadata.getFilename();
if (TextUtils.isEmpty(filename)) { if (TextUtils.isEmpty(filename)) {
String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType()); String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType());
filename = "decrypted" + (ext != null ? "."+ext : ""); filename = "decrypted" + (ext != null ? "." + ext : "");
} }
// requires >=kitkat // requires >=kitkat
@@ -395,7 +396,7 @@ public class DecryptListFragment
} }
HashMap<Uri,Drawable> mIconCache = new HashMap<>(); HashMap<Uri, Drawable> mIconCache = new HashMap<>();
private void processResult(final Uri uri) { private void processResult(final Uri uri) {
@@ -562,7 +563,7 @@ public class DecryptListFragment
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
new Parcelable[] { internalIntent }); new Parcelable[]{internalIntent});
startActivity(chooserIntent); startActivity(chooserIntent);
@@ -606,7 +607,7 @@ public class DecryptListFragment
.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata), .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata),
BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher); BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
new Parcelable[] { internalIntent }); new Parcelable[]{internalIntent});
} }
startActivity(chooserIntent); startActivity(chooserIntent);
@@ -633,7 +634,7 @@ public class DecryptListFragment
Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri); Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri);
if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) { if (!checkAndRequestReadPermission(activity, mCurrentInputUri)) {
return null; return null;
} }
@@ -645,15 +646,15 @@ public class DecryptListFragment
/** /**
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
* * <p/>
* This method returns true on Android < 6, or if permission is already granted. It * This method returns true on Android < 6, or if permission is already granted. It
* requests the permission and returns false otherwise, taking over responsibility * requests the permission and returns false otherwise, taking over responsibility
* for mCurrentInputUri. * for mCurrentInputUri.
* * <p/>
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
*/ */
private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) { private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) {
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return true; return true;
} }
@@ -668,7 +669,7 @@ public class DecryptListFragment
} }
requestPermissions( requestPermissions(
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
return false; return false;
@@ -677,8 +678,8 @@ public class DecryptListFragment
@Override @Override
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -694,7 +695,7 @@ public class DecryptListFragment
Iterator<Uri> it = mCancelledInputUris.iterator(); Iterator<Uri> it = mCancelledInputUris.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Uri uri = it.next(); Uri uri = it.next();
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
continue; continue;
} }
it.remove(); it.remove();
@@ -712,7 +713,7 @@ public class DecryptListFragment
Iterator<Uri> it = mPendingInputUris.iterator(); Iterator<Uri> it = mPendingInputUris.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Uri uri = it.next(); Uri uri = it.next();
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
continue; continue;
} }
it.remove(); it.remove();
@@ -767,7 +768,7 @@ public class DecryptListFragment
{ {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry); selectedEntries.add(keyEntry);
@@ -975,7 +976,7 @@ public class DecryptListFragment
String filename; String filename;
if (metadata == null) { if (metadata == null) {
filename = getString(R.string.filename_unknown); filename = getString(R.string.filename_unknown);
} else if ( ! TextUtils.isEmpty(metadata.getFilename())) { } else if (!TextUtils.isEmpty(metadata.getFilename())) {
filename = metadata.getFilename(); filename = metadata.getFilename();
} else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) { } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) {
filename = getString(R.string.filename_keys); filename = getString(R.string.filename_keys);
@@ -1227,7 +1228,7 @@ public class DecryptListFragment
vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text);
vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); vSignatureLayout = itemView.findViewById(R.id.result_signature_layout);
vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name);
vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); vSignatureMail = (TextView) itemView.findViewById(R.id.result_signature_email);
vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action); vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action);
vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); vFileList = (LinearLayout) itemView.findViewById(R.id.file_list);

View File

@@ -24,8 +24,6 @@ import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
@@ -34,6 +32,10 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
@@ -42,15 +44,14 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
public class ImportKeysActivity extends BaseActivity public class ImportKeysActivity extends BaseActivity implements ImportKeysListener {
implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
@@ -79,13 +80,8 @@ public class ImportKeysActivity extends BaseActivity
public static final String TAG_FRAG_LIST = "frag_list"; public static final String TAG_FRAG_LIST = "frag_list";
public static final String TAG_FRAG_TOP = "frag_top"; public static final String TAG_FRAG_TOP = "frag_top";
// for CryptoOperationHelper.Callback
private ParcelableHkpKeyserver mKeyserver;
private ArrayList<ParcelableKeyRing> mKeyList;
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
private boolean mFreshIntent; private boolean mFreshIntent;
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOpHelper;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -95,12 +91,6 @@ public class ImportKeysActivity extends BaseActivity
mFreshIntent = true; mFreshIntent = true;
setFullScreenDialogClose(Activity.RESULT_CANCELED, true); setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
findViewById(R.id.import_import).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
importSelectedKeys();
}
});
} }
@Override @Override
@@ -258,17 +248,6 @@ public class ImportKeysActivity extends BaseActivity
} }
} }
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// the only thing we need to take care of for restoring state is
// that the top layout is shown iff it contains a fragment
Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP);
boolean hasTopFragment = topFragment != null;
findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE);
}
/** /**
* Shows the list of keys to be imported. * Shows the list of keys to be imported.
* If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately * If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately
@@ -282,6 +261,7 @@ public class ImportKeysActivity extends BaseActivity
*/ */
private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery,
Preferences.CloudSearchPrefs cloudSearchPrefs) { Preferences.CloudSearchPrefs cloudSearchPrefs) {
Fragment listFragment = Fragment listFragment =
ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false,
cloudSearchPrefs); cloudSearchPrefs);
@@ -291,11 +271,11 @@ public class ImportKeysActivity extends BaseActivity
} }
private void startTopFileFragment() { private void startTopFileFragment() {
findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); FragmentManager fM = getSupportFragmentManager();
Fragment importFileFragment = ImportKeysFileFragment.newInstance(); if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) {
getSupportFragmentManager().beginTransaction() Fragment importFileFragment = ImportKeysFileFragment.newInstance();
.replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP) fM.beginTransaction().add(importFileFragment, TAG_FRAG_TOP).commit();
.commit(); }
} }
/** /**
@@ -309,12 +289,13 @@ public class ImportKeysActivity extends BaseActivity
*/ */
private void startTopCloudFragment(String query, boolean disableQueryEdit, private void startTopCloudFragment(String query, boolean disableQueryEdit,
Preferences.CloudSearchPrefs cloudSearchPrefs) { Preferences.CloudSearchPrefs cloudSearchPrefs) {
findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, FragmentManager fM = getSupportFragmentManager();
cloudSearchPrefs); if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) {
getSupportFragmentManager().beginTransaction() Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query,
.replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP) disableQueryEdit, cloudSearchPrefs);
.commit(); fM.beginTransaction().add(importCloudFragment, TAG_FRAG_TOP).commit();
}
} }
private boolean isFingerprintValid(String fingerprint) { private boolean isFingerprintValid(String fingerprint) {
@@ -327,96 +308,68 @@ public class ImportKeysActivity extends BaseActivity
} }
} }
public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
FragmentManager fragMan = getSupportFragmentManager();
ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
keyListFragment.loadNew(loaderState);
}
private void importSelectedKeys() {
FragmentManager fragMan = getSupportFragmentManager();
ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
if (keyListFragment.getSelectedEntries().size() == 0) {
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
return;
}
mOperationHelper = new CryptoOperationHelper<>(
1, this, this, R.string.progress_importing
);
ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState();
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
Log.d(Constants.TAG, "importKeys started");
// get DATA from selected key entries
IteratorWithSize<ParcelableKeyRing> selectedEntries = keyListFragment.getSelectedData();
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
// read FileImportCache for more info.
try {
// We parcel this iteratively into a file - anything we can
// display here, we should be able to import.
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, "key_import.pcl");
cache.writeCache(selectedEntries);
mKeyList = null;
mKeyserver = null;
mOperationHelper.cryptoOperation();
} catch (IOException e) {
Log.e(Constants.TAG, "Problem writing cache file", e);
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
}
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
ImportKeysListFragment.CloudLoaderState sls =
(ImportKeysListFragment.CloudLoaderState) ls;
// get selected key entries
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
{
// change the format into ParcelableKeyRing
ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries();
for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(entry.getFingerprintHex(),
entry.getKeyIdHex(), entry.getKeybaseName(), entry.getFbUsername()));
}
}
mKeyList = keys;
mKeyserver = sls.mCloudPrefs.keyserver;
mOperationHelper.cryptoOperation();
}
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mOperationHelper != null && if (mOpHelper != null &&
mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { mOpHelper.handleActivityResult(requestCode, resultCode, data)) {
return; return;
} }
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }
/** @Override
* Defines how the result of this activity is returned. public void onBackPressed() {
* Is overwritten in RemoteImportKeysActivity FragmentManager fM = getSupportFragmentManager();
*/ ImportKeysListFragment listFragment =
protected void handleResult(ImportKeyResult result) { (ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST);
if ((listFragment == null) || listFragment.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void loadKeys(LoaderState loaderState) {
FragmentManager fM = getSupportFragmentManager();
((ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST)).loadState(loaderState);
}
@Override
public void importKeys(List<ImportKeysListEntry> entries) {
List<ParcelableKeyRing> keyRings = new ArrayList<>();
for (ImportKeysListEntry e : entries) {
keyRings.add(e.getParcelableKeyRing());
}
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
// read FileImportCache for more info.
try {
// We parcel this iteratively into a file - anything we can
// display here, we should be able to import.
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME);
cache.writeCache(entries.size(), keyRings.iterator());
} catch (IOException e) {
Log.e(Constants.TAG, "Problem writing cache file", e);
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show();
return;
}
ImportKeyringParcel inputParcel = new ImportKeyringParcel(null, null);
ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, inputParcel);
mOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing);
mOpHelper.cryptoOperation();
}
@Override
public void handleResult(ImportKeyResult result) {
String intentAction = getIntent().getAction(); String intentAction = getIntent().getAction();
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction) if (ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction)
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) { || ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
setResult(RESULT_OK, intent); setResult(Activity.RESULT_OK, intent);
finish(); finish();
} else if (result.isOkNew() || result.isOkUpdated()) { } else if (result.isOkNew() || result.isOkUpdated()) {
// User has successfully imported a key, hide first time dialog // User has successfully imported a key, hide first time dialog
@@ -427,35 +380,8 @@ public class ImportKeysActivity extends BaseActivity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent); startActivity(intent);
} else { } else {
result.createNotify(ImportKeysActivity.this) result.createNotify(this).show();
.show((ViewGroup) findViewById(R.id.import_snackbar));
} }
} }
// methods from CryptoOperationHelper.Callback
@Override
public ImportKeyringParcel createOperationInput() {
return new ImportKeyringParcel(mKeyList, mKeyserver);
}
@Override
public void onCryptoOperationSuccess(ImportKeyResult result) {
handleResult(result);
}
@Override
public void onCryptoOperationCancelled() {
// do nothing
}
@Override
public void onCryptoOperationError(ImportKeyResult result) {
handleResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
} }

View File

@@ -20,39 +20,55 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.MatrixCursor;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.KeyEvent; import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.MenuItemCompat.OnActionExpandListener;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static android.support.v7.widget.SearchView.OnQueryTextListener;
import static android.support.v7.widget.SearchView.OnSuggestionListener;
/** /**
* Consists of the search bar, search button, and search settings button * Consists of the search bar, search button, and search settings button
*/ */
public class ImportKeysCloudFragment extends Fragment { public class ImportKeysCloudFragment extends Fragment {
public static final String ARG_QUERY = "query"; public static final String ARG_QUERY = "query";
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit"; public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
private ImportKeysActivity mImportActivity; private static final String CURSOR_SUGGESTION = "suggestion";
private AutoCompleteTextView mQueryEditText; private Activity mActivity;
private ImportKeysListener mCallback;
private List<String> mNamesAndEmails;
private SimpleCursorAdapter mSearchAdapter;
private List<String> mCurrentSuggestions = new ArrayList<>();
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
@@ -63,8 +79,8 @@ public class ImportKeysCloudFragment extends Fragment {
* preferences. * preferences.
*/ */
public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit, public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit,
Preferences.CloudSearchPrefs CloudSearchPrefs cloudSearchPrefs) {
cloudSearchPrefs) {
ImportKeysCloudFragment frag = new ImportKeysCloudFragment(); ImportKeysCloudFragment frag = new ImportKeysCloudFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
@@ -77,92 +93,120 @@ public class ImportKeysCloudFragment extends Fragment {
return frag; return frag;
} }
/**
* Inflate the layout for this fragment
*/
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false); ContactHelper contactHelper = new ContactHelper(mActivity);
mNamesAndEmails = contactHelper.getContactNames();
mNamesAndEmails.addAll(contactHelper.getContactMails());
mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query); mSearchAdapter = new SimpleCursorAdapter(mActivity,
R.layout.import_keys_cloud_suggestions_item, null, new String[]{CURSOR_SUGGESTION},
new int[]{android.R.id.text1}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
ContactHelper contactHelper = new ContactHelper(getActivity()); setHasOptionsMenu(true);
List<String> namesAndEmails = contactHelper.getContactNames(); return null;
namesAndEmails.addAll(contactHelper.getContactMails());
mQueryEditText.setThreshold(3);
mQueryEditText.setAdapter(
new ArrayAdapter<>
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
namesAndEmails
)
);
View searchButton = view.findViewById(R.id.cloud_import_server_search);
searchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
search(mQueryEditText.getText().toString());
}
});
mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
search(mQueryEditText.getText().toString());
// Don't return true to let the keyboard close itself after pressing search
return false;
}
return false;
}
});
View configButton = view.findViewById(R.id.cloud_import_server_config_button);
configButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mImportActivity, SettingsActivity.class);
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName());
startActivity(intent);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// set displayed values
if (getArguments() != null) {
String query = getArguments().getString(ARG_QUERY);
if (query != null) {
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
Log.d(Constants.TAG, "query: " + query);
} else {
// open keyboard
mQueryEditText.requestFocus();
toggleKeyboard(true);
}
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
mQueryEditText.setEnabled(false);
}
}
} }
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
mImportActivity = (ImportKeysActivity) activity; mActivity = activity;
try {
mCallback = (ImportKeysListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ImportKeysListener");
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.import_keys_cloud_fragment, menu);
MenuItem searchItem = menu.findItem(R.id.menu_import_keys_cloud_search);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSuggestionsAdapter(mSearchAdapter);
searchView.setOnSuggestionListener(new OnSuggestionListener() {
@Override
public boolean onSuggestionSelect(int position) {
searchView.setQuery(mCurrentSuggestions.get(position), true);
return true;
}
@Override
public boolean onSuggestionClick(int position) {
searchView.setQuery(mCurrentSuggestions.get(position), true);
return true;
}
});
searchView.setOnQueryTextListener(new OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
search(searchView.getQuery().toString().trim());
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
updateAdapter(newText);
return false;
}
});
MenuItemCompat.setOnActionExpandListener(searchItem, new OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
mActivity.finish();
return true;
}
});
searchItem.expandActionView();
super.onCreateOptionsMenu(menu, inflater);
}
private void updateAdapter(String query) {
mCurrentSuggestions.clear();
MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, CURSOR_SUGGESTION});
for (int i = 0; i < mNamesAndEmails.size(); i++) {
String s = mNamesAndEmails.get(i);
if (s.toLowerCase().startsWith(query.toLowerCase())) {
mCurrentSuggestions.add(s);
c.addRow(new Object[]{i, s});
}
}
mSearchAdapter.changeCursor(c);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.menu_import_keys_cloud_settings:
Intent intent = new Intent(mActivity, SettingsActivity.class);
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
SettingsActivity.CloudSearchPrefsFragment.class.getName());
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
} }
private void search(String query) { private void search(String query) {
Preferences.CloudSearchPrefs cloudSearchPrefs CloudSearchPrefs cloudSearchPrefs
= getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS); = getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS);
// no explicit search preferences passed // no explicit search preferences passed
@@ -170,8 +214,7 @@ public class ImportKeysCloudFragment extends Fragment {
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
} }
mImportActivity.loadCallback( mCallback.loadKeys(new CloudLoaderState(query, cloudSearchPrefs));
new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs));
toggleKeyboard(false); toggleKeyboard(false);
} }

View File

@@ -17,46 +17,41 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ImportKeysFileFragment extends Fragment { public class ImportKeysFileFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private View mBrowse; private Activity mActivity;
private View mClipboardButton; private ImportKeysListener mCallback;
private Uri mCurrentUri; private Uri mCurrentUri;
private static final int REQUEST_CODE_FILE = 0x00007003; private static final int REQUEST_CODE_FILE = 0x00007003;
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
@@ -70,52 +65,60 @@ public class ImportKeysFileFragment extends Fragment {
return frag; return frag;
} }
/**
* Inflate the layout for this fragment
*/
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false); setHasOptionsMenu(true);
return null;
mBrowse = view.findViewById(R.id.import_keys_file_browse);
mBrowse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// open .asc or .gpg files
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
// or gpg types!
FileHelper.openDocument(ImportKeysFileFragment.this,
Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
}
});
mClipboardButton = view.findViewById(R.id.import_clipboard_button);
mClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
sendText = PgpHelper.getPgpKeyContent(sendText);
if (sendText == null) {
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show();
return;
}
mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null));
}
}
});
return view;
} }
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
mImportActivity = (ImportKeysActivity) activity; mActivity = activity;
try {
mCallback = (ImportKeysListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ImportKeysListener");
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.import_keys_file_fragment, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.menu_import_keys_file_open:
// open .asc or .gpg files
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
// or gpg types!
FileHelper.openDocument(ImportKeysFileFragment.this,
Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
return true;
case R.id.menu_import_keys_file_paste:
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
sendText = PgpHelper.getPgpKeyContent(sendText);
if (sendText == null) {
Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show();
} else {
mCallback.loadKeys(new BytesLoaderState(sendText.getBytes(), null));
}
}
return true;
}
return super.onOptionsItemSelected(item);
} }
@Override @Override
@@ -125,90 +128,49 @@ public class ImportKeysFileFragment extends Fragment {
if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
mCurrentUri = data.getData(); mCurrentUri = data.getData();
if (checkAndRequestReadPermission(mCurrentUri)) { if (PermissionsUtil.checkAndRequestReadPermission(this, mCurrentUri)) {
startImportingKeys(); startImportingKeys();
} }
} }
break; break;
} }
default: default:
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
break;
} }
} }
private void startImportingKeys() { private void startImportingKeys() {
boolean isEncrypted; boolean isEncrypted;
try { try {
isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri); isEncrypted = FileHelper.isEncryptedFile(mActivity, mCurrentUri);
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "Error opening file", e); Log.e(Constants.TAG, "Error opening file", e);
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show();
return; return;
} }
if (isEncrypted) { if (isEncrypted) {
Intent intent = new Intent(mImportActivity, DecryptActivity.class); Intent intent = new Intent(mActivity, DecryptActivity.class);
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setData(mCurrentUri); intent.setData(mCurrentUri);
startActivity(intent); startActivity(intent);
} else { } else {
mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri)); mCallback.loadKeys(new BytesLoaderState(null, mCurrentUri));
} }
} }
/**
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
* <p/>
* This method returns true on Android < 6, or if permission is already granted. It
* requests the permission and returns false otherwise.
* <p/>
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
*/
private boolean checkAndRequestReadPermission(final Uri uri) {
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return true;
}
// Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
requestPermissions(
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
return false;
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
boolean permissionWasGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (permissionWasGranted) {
startImportingKeys(); startImportingKeys();
} else { } else {
Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); mActivity.setResult(Activity.RESULT_CANCELED);
getActivity().setResult(Activity.RESULT_CANCELED); mActivity.finish();
getActivity().finish();
} }
} }

View File

@@ -18,47 +18,45 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver; import android.databinding.DataBindingUtil;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.ListFragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.util.LongSparseArray; import android.support.v7.widget.LinearLayoutManager;
import android.view.MotionEvent; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnTouchListener; import android.view.View.OnClickListener;
import android.widget.ListView; import android.view.ViewGroup;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.databinding.ImportKeysListBasicItemBinding;
import org.sufficientlysecure.keychain.databinding.ImportKeysListFragmentBinding;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.processing.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListLoader;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
public class ImportKeysListFragment extends ListFragment implements import java.util.ArrayList;
public class ImportKeysListFragment extends Fragment implements
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
private static final String ARG_DATA_URI = "uri"; private static final String ARG_DATA_URI = "uri";
@@ -67,74 +65,26 @@ public class ImportKeysListFragment extends ListFragment implements
public static final String ARG_NON_INTERACTIVE = "non_interactive"; public static final String ARG_NON_INTERACTIVE = "non_interactive";
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private FragmentActivity mActivity;
private ImportKeysListener mListener;
private Activity mActivity; private ImportKeysListFragmentBinding mBinding;
private ImportKeysAdapter mAdapter; private ImportKeysListBasicItemBinding mBindingBasic;
private ParcelableProxy mParcelableProxy; private ParcelableProxy mParcelableProxy;
private ImportKeysAdapter mAdapter;
private LoaderState mLoaderState; private LoaderState mLoaderState;
public static final int STATUS_FIRST = 0;
public static final int STATUS_LOADING = 1;
public static final int STATUS_LOADED = 2;
public static final int STATUS_EMPTY = 3;
private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_BYTES = 0;
private static final int LOADER_ID_CLOUD = 1; private static final int LOADER_ID_CLOUD = 1;
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
private boolean mNonInteractive;
private boolean mShowingOrbotDialog; private boolean mShowingOrbotDialog;
public LoaderState getLoaderState() {
return mLoaderState;
}
public List<ImportKeysListEntry> getData() {
return mAdapter.getData();
}
/**
* Returns an Iterator (with size) of the selected data items.
* This iterator is sort of a tradeoff, it's slightly more complex than an
* ArrayList would have been, but we save some memory by just returning
* relevant elements on demand.
*/
public IteratorWithSize<ParcelableKeyRing> getSelectedData() {
final ArrayList<ImportKeysListEntry> entries = getSelectedEntries();
final Iterator<ImportKeysListEntry> it = entries.iterator();
return new IteratorWithSize<ParcelableKeyRing>() {
@Override
public int getSize() {
return entries.size();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public ParcelableKeyRing next() {
// throws NoSuchElementException if it doesn't exist, but that's not our problem
return mCachedKeyData.get(it.next().hashCode());
}
@Override
public void remove() {
it.remove();
}
};
}
public ArrayList<ImportKeysListEntry> getSelectedEntries() {
if (mAdapter != null) {
return mAdapter.getSelectedEntries();
} else {
Log.e(Constants.TAG, "Adapter not initialized, returning empty list");
return new ArrayList<>();
}
}
/** /**
* Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified
* by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order
@@ -148,7 +98,8 @@ public class ImportKeysListFragment extends ListFragment implements
* @return fragment with arguments set based on passed parameters * @return fragment with arguments set based on passed parameters
*/ */
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery,
Preferences.CloudSearchPrefs cloudSearchPrefs) { CloudSearchPrefs cloudSearchPrefs) {
return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs);
} }
@@ -168,8 +119,7 @@ public class ImportKeysListFragment extends ListFragment implements
Uri dataUri, Uri dataUri,
String serverQuery, String serverQuery,
boolean nonInteractive, boolean nonInteractive,
Preferences.CloudSearchPrefs cloudSearchPrefs) { CloudSearchPrefs cloudSearchPrefs) {
ImportKeysListFragment frag = new ImportKeysListFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putByteArray(ARG_BYTES, bytes); args.putByteArray(ARG_BYTES, bytes);
@@ -178,115 +128,70 @@ public class ImportKeysListFragment extends ListFragment implements
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs);
ImportKeysListFragment frag = new ImportKeysListFragment();
frag.setArguments(args); frag.setArguments(args);
return frag; return frag;
} }
static public class LoaderState {
}
static public class BytesLoaderState extends LoaderState {
public byte[] mKeyBytes;
public Uri mDataUri;
BytesLoaderState(byte[] keyBytes, Uri dataUri) {
mKeyBytes = keyBytes;
mDataUri = dataUri;
}
}
static public class CloudLoaderState extends LoaderState {
Preferences.CloudSearchPrefs mCloudPrefs;
String mServerQuery;
CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
mServerQuery = serverQuery;
mCloudPrefs = cloudPrefs;
}
}
/**
* Define Adapter and Loader on create of Activity
*/
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
super.onActivityCreated(savedInstanceState); mBinding = DataBindingUtil.inflate(inflater, R.layout.import_keys_list_fragment, container, false);
mBinding.setStatus(STATUS_FIRST);
mBindingBasic = mBinding.basic;
View view = mBinding.getRoot();
mActivity = getActivity(); mActivity = getActivity();
// Give some text to display if there is no data.
setEmptyText(mActivity.getString(R.string.error_nothing_import));
// Create an empty adapter we will use to display the loaded data.
mAdapter = new ImportKeysAdapter(mActivity);
setListAdapter(mAdapter);
Bundle args = getArguments(); Bundle args = getArguments();
Uri dataUri = args.getParcelable(ARG_DATA_URI); Uri dataUri = args.getParcelable(ARG_DATA_URI);
byte[] bytes = args.getByteArray(ARG_BYTES); byte[] bytes = args.getByteArray(ARG_BYTES);
String query = args.getString(ARG_SERVER_QUERY); String query = args.getString(ARG_SERVER_QUERY);
mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); boolean nonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false);
mBindingBasic.setNonInteractive(nonInteractive);
getListView().setOnTouchListener(new OnTouchListener() { // Create an empty adapter we will use to display the loaded data.
mAdapter = new ImportKeysAdapter(mActivity, mListener, nonInteractive);
mBinding.recyclerView.setAdapter(mAdapter);
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
if (dataUri != null || bytes != null) {
loadState(new BytesLoaderState(bytes, dataUri));
} else if (query != null) {
CloudSearchPrefs cloudSearchPrefs
= args.getParcelable(ARG_CLOUD_SEARCH_PREFS);
if (cloudSearchPrefs == null) {
cloudSearchPrefs = Preferences.getPreferences(mActivity).getCloudSearchPrefs();
}
loadState(new CloudLoaderState(query, cloudSearchPrefs));
}
mBinding.basic.importKeys.setOnClickListener(new OnClickListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public void onClick(View view) {
if (!mAdapter.isEmpty()) { mListener.importKeys(mAdapter.getEntries());
mActivity.onTouchEvent(event);
}
return false;
} }
}); });
getListView().setFastScrollEnabled(true); mBinding.basic.listKeys.setOnClickListener(new OnClickListener() {
@Override
if (dataUri != null || bytes != null) { public void onClick(View view) {
mLoaderState = new BytesLoaderState(bytes, dataUri); mBinding.setAdvanced(true);
} else if (query != null) {
Preferences.CloudSearchPrefs cloudSearchPrefs
= args.getParcelable(ARG_CLOUD_SEARCH_PREFS);
if (cloudSearchPrefs == null) {
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
} }
});
mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); return view;
}
if (dataUri != null && ! checkAndRequestReadPermission(dataUri)) {
return;
}
restartLoaders();
} }
/** @Override
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. public void onAttach(Activity activity) {
* super.onAttach(activity);
* This method returns true on Android < 6, or if permission is already granted. It
* requests the permission and returns false otherwise. try {
* mListener = (ImportKeysListener) activity;
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html } catch (ClassCastException e) {
*/ throw new ClassCastException(activity.toString()
private boolean checkAndRequestReadPermission(final Uri uri) { + " must implement ImportKeysListener");
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return true;
} }
// Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
requestPermissions(
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
return false;
} }
@Override @Override
@@ -294,140 +199,99 @@ public class ImportKeysListFragment extends ListFragment implements
@NonNull String[] permissions, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
boolean permissionWasGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (permissionWasGranted) {
// permission granted -> load key
restartLoaders(); restartLoaders();
} else { } else {
Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); mActivity.setResult(Activity.RESULT_CANCELED);
getActivity().setResult(Activity.RESULT_CANCELED); mActivity.finish();
getActivity().finish();
} }
} }
@Override /**
public void onListItemClick(ListView l, View v, int position, long id) { * User may want to go back to single card view if he's now in full key list
super.onListItemClick(l, v, position, id); * Check if we are in full key list and if this import operation supports basic mode
*
if (mNonInteractive) { * @return true if activity's back pressed can be performed
return; */
public boolean onBackPressed() {
boolean advanced = mBinding.getAdvanced();
if (advanced && mLoaderState.isBasicModeSupported()) {
mBinding.setAdvanced(false);
return false;
} }
return true;
// 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(LoaderState loaderState) { public void loadState(LoaderState loaderState) {
mLoaderState = loaderState; mLoaderState = loaderState;
if (mLoaderState instanceof BytesLoaderState) { if (mLoaderState instanceof BytesLoaderState) {
BytesLoaderState ls = (BytesLoaderState) mLoaderState; BytesLoaderState ls = (BytesLoaderState) mLoaderState;
if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { if (ls.mDataUri != null &&
!PermissionsUtil.checkAndRequestReadPermission(this, ls.mDataUri)) {
return; return;
} }
} }
mBinding.setAdvanced(!mLoaderState.isBasicModeSupported());
restartLoaders(); restartLoaders();
} }
public void destroyLoader() {
if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) {
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
}
if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) {
getLoaderManager().destroyLoader(LOADER_ID_CLOUD);
}
if (getView() != null) {
setListShown(true);
}
}
private void restartLoaders() { private void restartLoaders() {
LoaderManager loaderManager = getLoaderManager();
if (mLoaderState instanceof BytesLoaderState) { if (mLoaderState instanceof BytesLoaderState) {
// Start out with a progress indicator. loaderManager.restartLoader(LOADER_ID_BYTES, null, this);
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this);
} else if (mLoaderState instanceof CloudLoaderState) { } else if (mLoaderState instanceof CloudLoaderState) {
// Start out with a progress indicator. loaderManager.restartLoader(LOADER_ID_CLOUD, null, this);
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this);
} }
} }
@Override @Override
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> onCreateLoader(
onCreateLoader(int id, Bundle args) { int id, Bundle args) {
Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader = null;
switch (id) { switch (id) {
case LOADER_ID_BYTES: { case LOADER_ID_BYTES: {
return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); loader = new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
break;
} }
case LOADER_ID_CLOUD: { case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState; loader = new ImportKeysListCloudLoader(mActivity, (CloudLoaderState) mLoaderState,
return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs,
mParcelableProxy); mParcelableProxy);
break;
} }
default:
return null;
} }
if (loader != null) {
mBinding.setStatus(STATUS_LOADING);
}
return loader;
} }
@Override @Override
public void onLoadFinished(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader, public void onLoadFinished(
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) { Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
// Swap the new cursor in. (The framework will take care of closing the AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
// old cursor once we return.)
Log.d(Constants.TAG, "data: " + data.getResult());
// swap in the real data!
mAdapter.setData(data.getResult()); mAdapter.setData(data.getResult());
mAdapter.notifyDataSetChanged(); int size = mAdapter.getItemCount();
setListAdapter(mAdapter); mBinding.setNumber(size);
mBinding.setStatus(size > 0 ? STATUS_LOADED : STATUS_EMPTY);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
// free old cached key data
mCachedKeyData = null;
GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult();
switch (loader.getId()) { switch (loader.getId()) {
case LOADER_ID_BYTES: case LOADER_ID_BYTES:
if (!getKeyResult.success()) {
if (getKeyResult.success()) { getKeyResult.createNotify(mActivity).show();
// No error
mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings();
} else {
getKeyResult.createNotify(getActivity()).show();
} }
break; break;
case LOADER_ID_CLOUD: case LOADER_ID_CLOUD:
if (getKeyResult.isPending()) {
if (getKeyResult.success()) {
// No error
} else if (getKeyResult.isPending()) {
if (getKeyResult.getRequiredInputParcel().mType == if (getKeyResult.getRequiredInputParcel().mType ==
RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) { RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) {
if (mShowingOrbotDialog) { if (mShowingOrbotDialog) {
@@ -461,8 +325,7 @@ public class ImportKeysListFragment extends ListFragment implements
} }
}; };
if (OrbotHelper.putOrbotInRequiredState(dialogActions, if (OrbotHelper.putOrbotInRequiredState(dialogActions, mActivity)) {
getActivity())) {
// looks like we didn't have to show the // looks like we didn't have to show the
// dialog after all // dialog after all
mShowingOrbotDialog = false; mShowingOrbotDialog = false;
@@ -473,30 +336,18 @@ public class ImportKeysListFragment extends ListFragment implements
new Handler().post(showOrbotDialog); new Handler().post(showOrbotDialog);
mShowingOrbotDialog = true; mShowingOrbotDialog = true;
} }
} else { } else if (!getKeyResult.success()) {
getKeyResult.createNotify(getActivity()).show(); getKeyResult.createNotify(mActivity).show();
} }
break; break;
default:
break;
} }
} }
@Override @Override
public void onLoaderReset(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) { public void onLoaderReset(
switch (loader.getId()) { Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {
case LOADER_ID_BYTES:
// Clear the data in the adapter. mAdapter.clearData();
mAdapter.clear();
break;
case LOADER_ID_CLOUD:
// Clear the data in the adapter.
mAdapter.clear();
break;
default:
break;
}
} }
} }

View File

@@ -198,7 +198,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
} }
public void importKeys(String fingerprint) { public void importKeys(String fingerprint) {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry); selectedEntries.add(keyEntry);

View File

@@ -54,6 +54,7 @@ import android.widget.ViewAnimator;
import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
@@ -78,6 +79,7 @@ import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView; import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
@@ -245,7 +247,7 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) { boolean checked) {
if (checked) { if (checked) {
mAdapter.setNewSelection(position, true); mAdapter.setNewSelection(position, true);
} else { } else {
@@ -334,7 +336,7 @@ public class KeyListFragment extends LoaderFragment
headerCursor.addRow(row); headerCursor.addRow(row);
Cursor dataCursor = data; Cursor dataCursor = data;
data = new MergeCursor(new Cursor[] { data = new MergeCursor(new Cursor[]{
headerCursor, dataCursor headerCursor, dataCursor
}); });
} }
@@ -576,7 +578,7 @@ public class KeyListFragment extends LoaderFragment
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
byte[] blob = cursor.getBlob(0);//fingerprint column is 0 byte[] blob = cursor.getBlob(0);//fingerprint column is 0
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
keyList.add(keyEntry); keyList.add(keyEntry);
} }
mKeyList = keyList; mKeyList = keyList;

View File

@@ -30,6 +30,7 @@ import android.widget.NumberPicker;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
@@ -142,7 +143,7 @@ public class SafeSlingerActivity extends BaseActivity
// We parcel this iteratively into a file - anything we can // We parcel this iteratively into a file - anything we can
// display here, we should be able to import. // display here, we should be able to import.
ParcelableFileCache<ParcelableKeyRing> cache = ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, "key_import.pcl"); new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME);
cache.writeCache(it.size(), it.iterator()); cache.writeCache(it.size(), it.iterator());
mOperationHelper = mOperationHelper =

View File

@@ -19,11 +19,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import android.animation.ArgbEvaluator; import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -89,10 +84,10 @@ import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -104,6 +99,11 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
public class ViewKeyActivity extends BaseSecurityTokenActivity implements public class ViewKeyActivity extends BaseSecurityTokenActivity implements
LoaderManager.LoaderCallbacks<Cursor>, LoaderManager.LoaderCallbacks<Cursor>,
@@ -115,7 +115,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE})
private @interface RequestType {} private @interface RequestType {
}
static final int REQUEST_QR_FINGERPRINT = 1; static final int REQUEST_QR_FINGERPRINT = 1;
static final int REQUEST_BACKUP = 2; static final int REQUEST_BACKUP = 2;
static final int REQUEST_CERTIFY = 3; static final int REQUEST_CERTIFY = 3;
@@ -565,7 +567,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
private void startBackupActivity() { private void startBackupActivity() {
Intent intent = new Intent(this, BackupActivity.class); Intent intent = new Intent(this, BackupActivity.class);
intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId }); intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[]{mMasterKeyId});
intent.putExtra(BackupActivity.EXTRA_SECRET, true); intent.putExtra(BackupActivity.EXTRA_SECRET, true);
startActivity(intent); startActivity(intent);
} }
@@ -722,7 +724,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
manager.beginTransaction() manager.beginTransaction()
.addToBackStack("security_token") .addToBackStack("security_token")
.replace(R.id.view_key_fragment, frag) .replace(R.id.view_key_fragment, frag)
// if this is called while the activity wasn't resumed, just forget it happened // if this is called while the activity wasn't resumed, just forget it happened
.commitAllowingStateLoss(); .commitAllowingStateLoss();
} }
}); });
@@ -1105,7 +1107,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
entries.add(keyEntry); entries.add(keyEntry);
mKeyList = entries; mKeyList = entries;

View File

@@ -225,7 +225,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
// get key id from MASTER_KEY_ID // get key id from MASTER_KEY_ID
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); String formattedKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(masterKeyId);
getSupportActionBar().setSubtitle(formattedKeyId);
mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0;

View File

@@ -237,7 +237,8 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements
TextView wSignerName = (TextView) view.findViewById(R.id.signerName); TextView wSignerName = (TextView) view.findViewById(R.id.signerName);
TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus);
String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), cursor.getLong(mIndexSignerKeyId)); String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(
cursor.getLong(mIndexSignerKeyId));
OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId)); OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId));
if (userId.name != null) { if (userId.name != null) {
wSignerName.setText(userId.name); wSignerName.setText(userId.name);

View File

@@ -17,261 +17,281 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import android.annotation.TargetApi; import android.content.Intent;
import android.app.Activity; import android.databinding.DataBindingUtil;
import android.content.Context; import android.support.v4.app.FragmentActivity;
import android.graphics.Color; import android.support.v7.widget.RecyclerView;
import android.os.Build;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener;
import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.ViewHolder> implements ImportKeysResultListener {
protected LayoutInflater mInflater;
protected Activity mActivity;
protected List<ImportKeysListEntry> mData; private FragmentActivity mActivity;
private ImportKeysResultListener mListener;
private boolean mNonInteractive;
static class ViewHolder { private List<ImportKeysListEntry> mData;
public TextView mainUserId; private KeyState[] mKeyStates;
public TextView mainUserIdRest; private int mCurrent;
public TextView keyId;
public TextView fingerprint; private ProviderHelper mProviderHelper;
public TextView algorithm;
public ImageView status; public ImportKeysAdapter(FragmentActivity activity, ImportKeysListener listener,
public View userIdsDivider; boolean nonInteractive) {
public LinearLayout userIdsList;
public CheckBox checkBox;
}
public ImportKeysAdapter(Activity activity) {
super(activity, -1);
mActivity = activity; mActivity = activity;
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mListener = listener;
mNonInteractive = nonInteractive;
mProviderHelper = new ProviderHelper(activity);
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setData(List<ImportKeysListEntry> data) { public void setData(List<ImportKeysListEntry> data) {
mData = data;
clear(); mKeyStates = new KeyState[data.size()];
if (data != null) { for (int i = 0; i < mKeyStates.length; i++) {
this.mData = data; mKeyStates[i] = new KeyState();
// add data to extended ArrayAdapter ImportKeysListEntry entry = mData.get(i);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
addAll(data); try {
} else { KeyRing keyRing;
for (ImportKeysListEntry entry : data) { if (entry.isSecretKey()) {
add(entry); keyRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId);
} else {
keyRing = mProviderHelper.getCachedPublicKeyRing(keyId);
} }
mKeyStates[i].mAlreadyPresent = true;
mKeyStates[i].mVerified = keyRing.getVerified() > 0;
} catch (ProviderHelper.NotFoundException | PgpKeyNotFoundException ignored) {
} }
} }
// If there is only one key, get it automatically
if (mData.size() == 1) {
mCurrent = 0;
getKey(mData.get(0), true);
}
notifyDataSetChanged();
} }
public List<ImportKeysListEntry> getData() { public void clearData() {
return mData; mData = null;
mKeyStates = null;
notifyDataSetChanged();
} }
/** This method returns a list of all selected entries, with public keys sorted /**
* This method returns a list of all selected entries, with public keys sorted
* before secret keys, see ImportOperation for specifics. * before secret keys, see ImportOperation for specifics.
*
* @see ImportOperation * @see ImportOperation
*/ */
public ArrayList<ImportKeysListEntry> getSelectedEntries() { public List<ImportKeysListEntry> getEntries() {
ArrayList<ImportKeysListEntry> result = new ArrayList<>(); ArrayList<ImportKeysListEntry> result = new ArrayList<>();
ArrayList<ImportKeysListEntry> secrets = new ArrayList<>(); ArrayList<ImportKeysListEntry> secrets = new ArrayList<>();
if (mData == null) { if (mData == null) {
return result; return result;
} }
for (ImportKeysListEntry entry : mData) { for (ImportKeysListEntry entry : mData) {
if (entry.isSelected()) { // add this entry to either the secret or the public list
// add this entry to either the secret or the public list (entry.isSecretKey() ? secrets : result).add(entry);
(entry.isSecretKey() ? secrets : result).add(entry);
}
} }
// add secret keys at the end of the list // add secret keys at the end of the list
result.addAll(secrets); result.addAll(secrets);
return result; return result;
} }
@Override public class ViewHolder extends RecyclerView.ViewHolder {
public boolean hasStableIds() { public ImportKeysListItemBinding b;
return true;
public ViewHolder(View view) {
super(view);
b = DataBindingUtil.bind(view);
b.setNonInteractive(mNonInteractive);
}
} }
public View getView(int position, View convertView, ViewGroup parent) { @Override
ImportKeysListEntry entry = mData.get(position); public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Highlighter highlighter = new Highlighter(mActivity, entry.getQuery()); LayoutInflater inflater = LayoutInflater.from(mActivity);
ViewHolder holder; return new ViewHolder(inflater.inflate(R.layout.import_keys_list_item, parent, false));
if (convertView == null) { }
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.import_keys_list_item, null);
holder.mainUserId = (TextView) convertView.findViewById(R.id.import_item_user_id);
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.import_item_user_id_email);
holder.keyId = (TextView) convertView.findViewById(R.id.import_item_key_id);
holder.fingerprint = (TextView) convertView.findViewById(R.id.import_item_fingerprint);
holder.algorithm = (TextView) convertView.findViewById(R.id.import_item_algorithm);
holder.status = (ImageView) convertView.findViewById(R.id.import_item_status);
holder.userIdsDivider = convertView.findViewById(R.id.import_item_status_divider);
holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.import_item_user_ids_list);
holder.checkBox = (CheckBox) convertView.findViewById(R.id.import_item_selected);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// main user id @Override
String userId = entry.getUserIds().get(0); public void onBindViewHolder(ViewHolder holder, final int position) {
OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); final ImportKeysListItemBinding b = holder.b;
final ImportKeysListEntry entry = mData.get(position);
b.setEntry(entry);
// name final KeyState keyState = mKeyStates[position];
if (userIdSplit.name != null) { final boolean downloaded = keyState.mDownloaded;
// show red user id if it is a secret key final boolean showed = keyState.mShowed;
if (entry.isSecretKey()) {
holder.mainUserId.setText(mActivity.getString(R.string.secret_key)
+ " " + userIdSplit.name);
} else {
holder.mainUserId.setText(highlighter.highlight(userIdSplit.name));
}
} else {
holder.mainUserId.setText(R.string.user_id_no_name);
}
// email b.card.setOnClickListener(new OnClickListener() {
if (userIdSplit.email != null) { @Override
holder.mainUserIdRest.setVisibility(View.VISIBLE); public void onClick(View v) {
holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); if (!downloaded) {
} else { mCurrent = position;
holder.mainUserIdRest.setVisibility(View.GONE); getKey(entry, true);
}
holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), entry.getKeyIdHex()));
// don't show full fingerprint on key import
holder.fingerprint.setVisibility(View.GONE);
if (entry.getAlgorithm() != null) {
holder.algorithm.setText(entry.getAlgorithm());
holder.algorithm.setVisibility(View.VISIBLE);
} else {
holder.algorithm.setVisibility(View.GONE);
}
if (entry.isRevoked()) {
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray);
} else if (entry.isExpired()) {
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray);
}
if (entry.isRevoked() || entry.isExpired()) {
holder.status.setVisibility(View.VISIBLE);
// no more space for algorithm display
holder.algorithm.setVisibility(View.GONE);
holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else {
holder.status.setVisibility(View.GONE);
holder.algorithm.setVisibility(View.VISIBLE);
if (entry.isSecretKey()) {
holder.mainUserId.setTextColor(Color.RED);
} else {
holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
}
holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
}
if (entry.getUserIds().size() == 1) {
holder.userIdsList.setVisibility(View.GONE);
holder.userIdsDivider.setVisibility(View.GONE);
} else {
holder.userIdsList.setVisibility(View.VISIBLE);
holder.userIdsDivider.setVisibility(View.VISIBLE);
// destroyLoader view from holder
holder.userIdsList.removeAllViews();
// we want conventional gpg UserIDs first, then Keybase ”proofs”
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet());
java.util.Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() {
@Override
public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> entry2) {
// sort keybase UserIds after non-Keybase
boolean e1IsKeybase = entry1.getKey().contains(":");
boolean e2IsKeybase = entry2.getKey().contains(":");
if (e1IsKeybase != e2IsKeybase) {
return (e1IsKeybase) ? 1 : -1;
}
return entry1.getKey().compareTo(entry2.getKey());
}
});
for (Map.Entry<String, HashSet<String>> pair : sortedIds) {
String cUserId = pair.getKey();
HashSet<String> cEmails = pair.getValue();
TextView uidView = (TextView) mInflater.inflate(
R.layout.import_keys_list_entry_user_id, null);
uidView.setText(highlighter.highlight(cUserId));
uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0);
if (entry.isRevoked() || entry.isExpired()) {
uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else { } else {
uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); changeState(position, !showed);
}
holder.userIdsList.addView(uidView);
for (String email : cEmails) {
TextView emailView = (TextView) mInflater.inflate(
R.layout.import_keys_list_entry_user_id, null);
emailView.setPadding(
FormattingUtils.dpToPx(getContext(), 16), 0,
FormattingUtils.dpToPx(getContext(), 8), 0);
emailView.setText(highlighter.highlight(email));
if (entry.isRevoked() || entry.isExpired()) {
emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
} else {
emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText));
}
holder.userIdsList.addView(emailView);
} }
} }
});
b.extra.importKey.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getKey(entry, false);
}
});
b.extra.showKey.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
Intent intent = new Intent(mActivity, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(keyId));
mActivity.startActivity(intent);
}
});
b.extraContainer.setVisibility(showed ? View.VISIBLE : View.GONE);
}
@Override
public int getItemCount() {
return mData != null ? mData.size() : 0;
}
public void getKey(ImportKeysListEntry entry, boolean skipSave) {
ImportKeyringParcel inputParcel = prepareKeyOperation(entry, skipSave);
ImportKeysResultListener listener = skipSave ? this : mListener;
ImportKeysOperationCallback cb = new ImportKeysOperationCallback(listener, inputParcel);
int message = skipSave ? R.string.progress_downloading : R.string.progress_importing;
CryptoOperationHelper opHelper = new CryptoOperationHelper<>(1, mActivity, cb, message);
opHelper.cryptoOperation();
}
private ImportKeyringParcel prepareKeyOperation(ImportKeysListEntry entry, boolean skipSave) {
ArrayList<ParcelableKeyRing> keysList = null;
ParcelableHkpKeyserver keyserver = null;
ParcelableKeyRing keyRing = entry.getParcelableKeyRing();
if (keyRing.mBytes != null) {
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
// read FileImportCache for more info.
try {
// We parcel this iteratively into a file - anything we can
// display here, we should be able to import.
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(mActivity, ImportOperation.CACHE_FILE_NAME);
cache.writeCache(keyRing);
} catch (IOException e) {
Log.e(Constants.TAG, "Problem writing cache file", e);
Notify.create(mActivity, "Problem writing cache file!", Notify.Style.ERROR).show();
}
} else {
keysList = new ArrayList<>();
keysList.add(keyRing);
keyserver = entry.getKeyserver();
} }
holder.checkBox.setChecked(entry.isSelected()); return new ImportKeyringParcel(keysList, keyserver, skipSave);
}
return convertView; @Override
public void handleResult(ImportKeyResult result) {
boolean resultStatus = result.success();
Log.e(Constants.TAG, "getKey result: " + resultStatus);
if (resultStatus) {
ArrayList<CanonicalizedKeyRing> canKeyRings = result.mCanonicalizedKeyRings;
if (canKeyRings.size() == 1) {
CanonicalizedKeyRing keyRing = canKeyRings.get(0);
Log.e(Constants.TAG, "Key ID: " + keyRing.getMasterKeyId() +
"| isRev: " + keyRing.isRevoked() + "| isExp: " + keyRing.isExpired());
ImportKeysListEntry entry = mData.get(mCurrent);
entry.setUpdated(result.isOkUpdated());
mergeEntryWithKey(entry, keyRing);
mKeyStates[mCurrent].mDownloaded = true;
changeState(mCurrent, true);
} else {
throw new RuntimeException("getKey retrieved more than one key ("
+ canKeyRings.size() + ")");
}
} else {
result.createNotify(mActivity).show();
}
}
private void mergeEntryWithKey(ImportKeysListEntry entry, CanonicalizedKeyRing keyRing) {
entry.setRevoked(keyRing.isRevoked());
entry.setExpired(keyRing.isExpired());
Date expectedDate = entry.getDate();
Date creationDate = keyRing.getCreationDate();
if (expectedDate == null) {
entry.setDate(creationDate);
} else if (!expectedDate.equals(creationDate)) {
throw new AssertionError("Creation date doesn't match the expected one");
}
entry.setKeyId(keyRing.getMasterKeyId());
ArrayList<String> realUserIdsPlusKeybase = keyRing.getUnorderedUserIds();
realUserIdsPlusKeybase.addAll(entry.getKeybaseUserIds());
entry.setUserIds(realUserIdsPlusKeybase);
}
private class KeyState {
public boolean mAlreadyPresent = false;
public boolean mVerified = false;
public boolean mDownloaded = false;
public boolean mShowed = false;
}
private void changeState(int position, boolean showed) {
KeyState keyState = mKeyStates[position];
keyState.mShowed = showed;
notifyItemChanged(position);
} }
} }

View File

@@ -0,0 +1,68 @@
package org.sufficientlysecure.keychain.ui.bindings;
import android.content.Context;
import android.content.res.Resources;
import android.databinding.BindingAdapter;
import android.graphics.Color;
import android.text.format.DateFormat;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import java.util.Date;
public class ImportKeysBindings {
@BindingAdapter({"app:keyUserId", "app:keySecret", "app:keyRevokedOrExpired", "app:query"})
public static void setUserId(TextView textView, CharSequence userId, boolean secret,
boolean revokedOrExpired, String query) {
Context context = textView.getContext();
Resources resources = context.getResources();
if (userId == null)
userId = resources.getString(R.string.user_id_no_name);
if (secret) {
userId = resources.getString(R.string.secret_key) + " " + userId;
} else {
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
userId = highlighter.highlight(userId);
}
textView.setText(userId);
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
if (secret) {
textView.setTextColor(Color.RED);
}
}
@BindingAdapter({"app:keyUserEmail", "app:keyRevokedOrExpired", "app:query"})
public static void setUserEmail(TextView textView, CharSequence userEmail,
boolean revokedOrExpired, String query) {
Context context = textView.getContext();
if (userEmail == null)
userEmail = "";
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
textView.setText(highlighter.highlight(userEmail));
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
}
@BindingAdapter({"app:keyCreation", "app:keyRevokedOrExpired"})
public static void setCreation(TextView textView, Date creationDate, boolean revokedOrExpired) {
Context context = textView.getContext();
String text = "";
if (creationDate != null) {
text = DateFormat.getDateFormat(context).format(creationDate);
}
textView.setText(text);
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
}
}

View File

@@ -0,0 +1,32 @@
package org.sufficientlysecure.keychain.ui.bindings;
import android.content.Context;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.util.LruCache;
public class ImportKeysBindingsUtils {
private static LruCache<String, Highlighter> highlighterCache = new LruCache<>(1);
public static Highlighter getHighlighter(Context context, String query) {
Highlighter highlighter = highlighterCache.get(query);
if (highlighter == null) {
highlighter = new Highlighter(context, query);
highlighterCache.put(query, highlighter);
}
return highlighter;
}
public static int getColor(Context context, boolean revokedOrExpired) {
if (revokedOrExpired) {
return context.getResources().getColor(R.color.key_flag_gray);
} else {
return FormattingUtils.getColorFromAttr(context, R.attr.colorText);
}
}
}

View File

@@ -0,0 +1,85 @@
package org.sufficientlysecure.keychain.ui.bindings;
import android.content.Context;
import android.content.res.Resources;
import android.databinding.BindingAdapter;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
public class ImportKeysExtraBindings {
@BindingAdapter({"app:keyRevoked", "app:keyExpired"})
public static void setStatus(ImageView imageView, boolean revoked, boolean expired) {
Context context = imageView.getContext();
if (revoked) {
KeyFormattingUtils.setStatusImage(context, imageView, null,
KeyFormattingUtils.State.REVOKED, R.color.key_flag_gray);
} else if (expired) {
KeyFormattingUtils.setStatusImage(context, imageView, null,
KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray);
}
}
@BindingAdapter({"app:keyId"})
public static void setKeyId(TextView textView, String keyId) {
Context context = textView.getContext();
String text;
if (keyId != null) {
text = KeyFormattingUtils.beautifyKeyId(keyId);
} else {
Resources resources = context.getResources();
text = resources.getString(R.string.unknown);
}
textView.setText(text);
}
@BindingAdapter({"app:keyUserIds", "app:query"})
public static void setUserIds(LinearLayout linearLayout, ArrayList userIds, String query) {
linearLayout.removeAllViews();
if (userIds != null) {
Context context = linearLayout.getContext();
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
ArrayList<Map.Entry<String, HashSet<String>>> uIds = userIds;
for (Map.Entry<String, HashSet<String>> pair : uIds) {
String name = pair.getKey();
HashSet<String> emails = pair.getValue();
LayoutInflater inflater = LayoutInflater.from(context);
TextView uidView = (TextView) inflater.inflate(
R.layout.import_keys_list_entry_user_id, null);
uidView.setText(highlighter.highlight(name));
uidView.setPadding(0, 0, FormattingUtils.dpToPx(context, 8), 0);
uidView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText));
linearLayout.addView(uidView);
for (String email : emails) {
TextView emailView = (TextView) inflater.inflate(
R.layout.import_keys_list_entry_user_id, null);
emailView.setPadding(
FormattingUtils.dpToPx(context, 16), 0,
FormattingUtils.dpToPx(context, 8), 0);
emailView.setText(highlighter.highlight(email));
emailView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText));
linearLayout.addView(emailView);
}
}
}
}
}

View File

@@ -18,7 +18,7 @@ public class Highlighter {
mQuery = query; mQuery = query;
} }
public Spannable highlight(String text) { public Spannable highlight(CharSequence text) {
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
if (mQuery == null) { if (mQuery == null) {

View File

@@ -30,13 +30,13 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ViewAnimator; import android.widget.ViewAnimator;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.DigestException; import java.security.DigestException;
import java.security.MessageDigest; import java.security.MessageDigest;
@@ -99,7 +100,7 @@ public class KeyFormattingUtils {
default: { default: {
if (context != null) { if (context != null) {
algorithmStr = context.getResources().getString(R.string.unknown_algorithm); algorithmStr = context.getResources().getString(R.string.unknown);
} else { } else {
algorithmStr = "unknown"; algorithmStr = "unknown";
} }
@@ -154,7 +155,7 @@ public class KeyFormattingUtils {
default: { default: {
if (context != null) { if (context != null) {
algorithmStr = context.getResources().getString(R.string.unknown_algorithm); algorithmStr = context.getResources().getString(R.string.unknown);
} else { } else {
algorithmStr = "unknown"; algorithmStr = "unknown";
} }
@@ -189,7 +190,7 @@ public class KeyFormattingUtils {
*/ */
} }
if (context != null) { if (context != null) {
return context.getResources().getString(R.string.unknown_algorithm); return context.getResources().getString(R.string.unknown);
} else { } else {
return "unknown"; return "unknown";
} }
@@ -208,7 +209,7 @@ public class KeyFormattingUtils {
return name; return name;
} }
if (context != null) { if (context != null) {
return context.getResources().getString(R.string.unknown_algorithm); return context.getResources().getString(R.string.unknown);
} else { } else {
return "unknown"; return "unknown";
} }
@@ -272,6 +273,10 @@ public class KeyFormattingUtils {
return hexString; return hexString;
} }
public static long convertKeyIdHexToKeyId(String hex) {
return new BigInteger(hex.substring(2), 16).longValue();
}
public static long convertFingerprintToKeyId(byte[] fingerprint) { public static long convertFingerprintToKeyId(byte[] fingerprint) {
return ByteBuffer.wrap(fingerprint, 12, 8).getLong(); return ByteBuffer.wrap(fingerprint, 12, 8).getLong();
} }
@@ -312,12 +317,12 @@ public class KeyFormattingUtils {
return beautifyKeyId(convertKeyIdToHex(keyId)); return beautifyKeyId(convertKeyIdToHex(keyId));
} }
public static String beautifyKeyIdWithPrefix(Context context, String idHex) { public static String beautifyKeyIdWithPrefix(String idHex) {
return "Key ID: " + beautifyKeyId(idHex); return "Key ID: " + beautifyKeyId(idHex);
} }
public static String beautifyKeyIdWithPrefix(Context context, long keyId) { public static String beautifyKeyIdWithPrefix(long keyId) {
return beautifyKeyIdWithPrefix(context, convertKeyIdToHex(keyId)); return beautifyKeyIdWithPrefix(convertKeyIdToHex(keyId));
} }
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) { public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
@@ -434,14 +439,19 @@ public class KeyFormattingUtils {
public interface StatusHolder { public interface StatusHolder {
ImageView getEncryptionStatusIcon(); ImageView getEncryptionStatusIcon();
TextView getEncryptionStatusText(); TextView getEncryptionStatusText();
ImageView getSignatureStatusIcon(); ImageView getSignatureStatusIcon();
TextView getSignatureStatusText(); TextView getSignatureStatusText();
View getSignatureLayout(); View getSignatureLayout();
TextView getSignatureUserName(); TextView getSignatureUserName();
TextView getSignatureUserEmail(); TextView getSignatureUserEmail();
ViewAnimator getSignatureAction(); ViewAnimator getSignatureAction();
boolean hasEncrypt(); boolean hasEncrypt();
@@ -450,7 +460,7 @@ public class KeyFormattingUtils {
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result, public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result,
boolean processingkeyLookup) { boolean processingkeyLookup) {
if (holder.hasEncrypt()) { if (holder.hasEncrypt()) {
OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult(); OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();

View File

@@ -0,0 +1,89 @@
package org.sufficientlysecure.keychain.ui.util;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
import org.sufficientlysecure.keychain.R;
public class PermissionsUtil {
private static final int PERMISSION_READ_EXTERNAL_STORAGE = 1;
/**
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
* <p/>
* This method returns true on Android < 6, or if permission is already granted. It
* requests the permission and returns false otherwise.
* <p/>
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
*/
@SuppressLint("NewApi") // Api level is checked in checkReadPermission
public static boolean checkAndRequestReadPermission(Activity activity, Uri uri) {
boolean result = checkReadPermission(activity, uri);
if (!result) {
activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_READ_EXTERNAL_STORAGE);
}
return result;
}
public static boolean checkAndRequestReadPermission(Fragment fragment, Uri uri) {
boolean result = checkReadPermission(fragment.getContext(), uri);
if (!result) {
fragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_READ_EXTERNAL_STORAGE);
}
return result;
}
private static boolean checkReadPermission(Context context, Uri uri) {
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return true;
}
// Additional check due to:
// https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
return false;
}
public static boolean checkReadPermissionResult(Context context,
int requestCode,
int[] grantResults) {
if (requestCode != PERMISSION_READ_EXTERNAL_STORAGE) {
return false;
}
boolean permissionWasGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (permissionWasGranted) {
return true;
} else {
Toast.makeText(context, R.string.error_denied_storage_permission, Toast.LENGTH_LONG)
.show();
return false;
}
}
}

View File

@@ -57,7 +57,8 @@ public class EmailKeyHelper {
// Put them in a list and import // Put them in a list and import
ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size()); ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size());
for (ImportKeysListEntry entry : entries) { for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex())); keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null,
null));
} }
mKeyList = keys; mKeyList = keys;
mKeyserver = keyserver; mKeyserver = keyserver;
@@ -97,7 +98,7 @@ public class EmailKeyHelper {
Set<ImportKeysListEntry> keys = new HashSet<>(); Set<ImportKeysListEntry> keys = new HashSet<>();
try { try {
for (ImportKeysListEntry key : keyServer.search(mail, proxy)) { for (ImportKeysListEntry key : keyServer.search(mail, proxy)) {
if (key.isRevoked() || key.isExpired()) continue; if (key.isRevokedOrExpired()) continue;
for (String userId : key.getUserIds()) { for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
keys.add(key); keys.add(key);

View File

@@ -0,0 +1,17 @@
package org.sufficientlysecure.keychain.util;
import java.util.Iterator;
/**
* An extended iterator interface, which knows the total number of its entries beforehand.
*/
public interface IteratorWithSize<E> extends Iterator<E> {
/**
* Returns the total number of entries in this iterator.
*
* @return the number of entries in this iterator.
*/
int getSize();
}

View File

@@ -0,0 +1,22 @@
package org.sufficientlysecure.keychain.util;
import java.util.LinkedHashMap;
import java.util.Map;
// Source: http://stackoverflow.com/a/1953516
public class LruCache<A, B> extends LinkedHashMap<A, B> {
private final int maxEntries;
public LruCache(final int maxEntries) {
super(maxEntries + 1, 1.0f, true);
this.maxEntries = maxEntries;
}
@Override
protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
return super.size() > maxEntries;
}
}

View File

@@ -52,12 +52,40 @@ public class ParcelableFileCache<E extends Parcelable> {
mFilename = filename; mFilename = filename;
} }
public void writeCache(IteratorWithSize<E> it) throws IOException { public void writeCache(int numEntries, Iterator<E> it) throws IOException {
writeCache(it.getSize(), it); DataOutputStream oos = getOutputStream();
try {
oos.writeInt(numEntries);
while (it.hasNext()) {
writeParcelable(it.next(), oos);
}
} finally {
oos.close();
}
} }
public void writeCache(int numEntries, Iterator<E> it) throws IOException { public void writeCache(E obj) throws IOException {
DataOutputStream oos = getOutputStream();
try {
oos.writeInt(1);
writeParcelable(obj, oos);
} finally {
oos.close();
}
}
private void writeParcelable(E obj, DataOutputStream oos) throws IOException {
Parcel p = Parcel.obtain(); // creating empty parcel object
p.writeParcelable(obj, 0); // saving bundle as parcel
byte[] buf = p.marshall();
oos.writeInt(buf.length);
oos.write(buf);
p.recycle();
}
private DataOutputStream getOutputStream() throws IOException {
File cacheDir = mContext.getCacheDir(); File cacheDir = mContext.getCacheDir();
if (cacheDir == null) { if (cacheDir == null) {
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
@@ -65,29 +93,12 @@ public class ParcelableFileCache<E extends Parcelable> {
} }
File tempFile = new File(mContext.getCacheDir(), mFilename); File tempFile = new File(mContext.getCacheDir(), mFilename);
return new DataOutputStream(new FileOutputStream(tempFile));
DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));
try {
oos.writeInt(numEntries);
while (it.hasNext()) {
Parcel p = Parcel.obtain(); // creating empty parcel object
p.writeParcelable(it.next(), 0); // saving bundle as parcel
byte[] buf = p.marshall();
oos.writeInt(buf.length);
oos.write(buf);
p.recycle();
}
} finally {
oos.close();
}
} }
/** /**
* Reads from cache file and deletes it afterward. Convenience function for readCache(boolean). * Reads from cache file and deletes it afterward. Convenience function for readCache(boolean).
*
* @return an IteratorWithSize object containing entries read from the cache file * @return an IteratorWithSize object containing entries read from the cache file
* @throws IOException * @throws IOException
*/ */
@@ -97,10 +108,11 @@ public class ParcelableFileCache<E extends Parcelable> {
/** /**
* Reads entries from a cache file and returns an IteratorWithSize object containing the entries * Reads entries from a cache file and returns an IteratorWithSize object containing the entries
*
* @param deleteAfterRead if true, the cache file will be deleted after being read * @param deleteAfterRead if true, the cache file will be deleted after being read
* @return an IteratorWithSize object containing entries read from the cache file * @return an IteratorWithSize object containing entries read from the cache file
* @throws IOException if cache directory/parcel import file does not exist, or a read error * @throws IOException if cache directory/parcel import file does not exist, or a read error
* occurs * occurs
*/ */
public IteratorWithSize<E> readCache(final boolean deleteAfterRead) throws IOException { public IteratorWithSize<E> readCache(final boolean deleteAfterRead) throws IOException {
@@ -205,7 +217,6 @@ public class ParcelableFileCache<E extends Parcelable> {
} }
public boolean delete() throws IOException { public boolean delete() throws IOException {
File cacheDir = mContext.getCacheDir(); File cacheDir = mContext.getCacheDir();
if (cacheDir == null) { if (cacheDir == null) {
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
@@ -216,12 +227,4 @@ public class ParcelableFileCache<E extends Parcelable> {
return tempFile.delete(); return tempFile.delete();
} }
/** As the name implies, this is an extended iterator interface, which
* knows the total number of its entries beforehand.
*/
public static interface IteratorWithSize<E> extends Iterator<E> {
/** Returns the number of total entries in this iterator. */
int getSize();
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
xmlns:tools="http://schemas.android.com/tools">
<include <include
android:id="@+id/toolbar_include" android:id="@+id/toolbar_include"
@@ -13,79 +12,26 @@
https://medium.com/@ngdelamo/using-drawerlayout-the-material-way-i-716bba2b5705 https://medium.com/@ngdelamo/using-drawerlayout-the-material-way-i-716bba2b5705
--> -->
<LinearLayout <LinearLayout
android:layout_below="@id/toolbar_include"
android:fitsSystemWindows="true"
android:layout_marginTop="@dimen/minus_statusbar_height"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/toolbar_include"
android:layout_marginTop="@dimen/minus_statusbar_height"
android:fitsSystemWindows="true"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/notify_area" /> <include layout="@layout/notify_area" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/import_keys_top_layout"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/import_keys_top_container"
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="vertical" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
</LinearLayout>
<FrameLayout <FrameLayout
android:id="@+id/import_keys_list_container" android:id="@+id/import_keys_list_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1" /> android:layout_weight="1" />
<RelativeLayout <RelativeLayout
android:id="@+id/import_footer" android:id="@+id/import_snackbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:orientation="vertical">
<View
android:id="@+id/import_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/import_import"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_below="@id/import_divider"
android:text="@string/import_import"
android:minHeight="?android:attr/listPreferredItemHeight"
android:drawableRight="@drawable/ic_file_download_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:clickable="true"
android:background="?android:selectableItemBackground" />
<RelativeLayout
android:id="@+id/import_snackbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/import_import" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@@ -1,56 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:paddingLeft="8dp"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal">
<AutoCompleteTextView
android:id="@+id/cloud_import_server_query"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="top|left"
android:hint="@string/hint_cloud_search_hint"
android:imeOptions="actionSearch"
android:inputType="textNoSuggestions"
android:singleLine="true"
android:lines="1"
android:maxLines="1"
android:minLines="1"
android:layout_gravity="center_vertical" />
<ImageButton
android:id="@+id/cloud_import_server_search"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_search_grey_24dp"
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/cloud_import_server_config_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_settings_grey_24dp"
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center_vertical"
android:minHeight="56dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/import_keys_file_browse"
android:paddingLeft="8dp"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/filemanager_title_open"
android:layout_weight="1"
android:drawableRight="@drawable/ic_folder_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/import_clipboard_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_content_paste_grey_24dp"
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
</LinearLayout>
<TextView
android:id="@+id/import_qrcode_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/import_qrcode_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:progress="0"
android:visibility="gone" />
</LinearLayout>

View File

@@ -0,0 +1,64 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" alias="V" />
<variable name="nonInteractive" type="boolean" />
<variable name="number" type="int" />
</data>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
card_view:cardCornerRadius="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/import_found_keys(number)}"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="@{nonInteractive ? V.GONE : V.VISIBLE}">
<Button
android:id="@+id/import_keys"
style="@style/CardViewActionButton"
android:text="@string/btn_import_keys" />
<Button
android:id="@+id/list_keys"
style="@style/CardViewActionButton"
android:text="@string/btn_view_list" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</layout>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" alias="V" />
<import type="org.sufficientlysecure.keychain.ui.ImportKeysListFragment" alias="I" />
<variable name="status" type="int" />
<variable name="number" type="int" />
<variable name="advanced" type="boolean" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_grey_100">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="@{status == I.STATUS_LOADING ? V.VISIBLE : V.GONE}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/error_nothing"
android:visibility="@{status == I.STATUS_FIRST ? V.VISIBLE : V.GONE}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/error_nothing_import"
android:visibility="@{status == I.STATUS_EMPTY ? V.VISIBLE : V.GONE}" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:visibility="@{status == I.STATUS_LOADED ? V.VISIBLE : V.GONE}">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="@{advanced || (number == 1) ? V.GONE : V.VISIBLE}">
<include
android:id="@+id/basic"
layout="@layout/import_keys_list_basic_item"
app:number="@{number}" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:visibility="@{advanced || (number == 1) ? V.VISIBLE : V.GONE}" />
</LinearLayout>
</RelativeLayout>
</layout>

View File

@@ -1,114 +1,88 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:singleLine="true"
android:paddingLeft="8dp"
android:paddingRight="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<CheckBox <data>
android:id="@+id/import_item_selected" <import type="android.view.View" alias="V" />
android:layout_width="wrap_content" <import type="org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry" />
android:layout_height="match_parent"
android:paddingRight="8dp"
android:paddingTop="2dp"
android:gravity="top|center"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false" />
<!-- focusable and clickable MUST be false to handle click and longClick in ListView Activity -->
<LinearLayout <variable name="nonInteractive" type="boolean" />
<variable name="entry" type="ImportKeysListEntry" />
</data>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
card_view:cardCornerRadius="2dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="vertical"
android:paddingBottom="8dp">
<LinearLayout <RelativeLayout
android:layout_width="0dip" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:paddingBottom="16dp"
android:orientation="vertical"> android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="24dp">
<TextView <LinearLayout
android:id="@+id/import_item_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alice"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/import_item_user_id_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="alice@example.com"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/import_item_key_id"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Key ID: abcd abcd abcd abcd" android:layout_alignParentLeft="true"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:orientation="vertical">
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
app:keyRevokedOrExpired="@{entry.revokedOrExpired}"
app:keySecret="@{entry.secretKey}"
app:keyUserId="@{entry.primaryUserId.name}"
app:query="@{entry.query}" />
<LinearLayout <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAppearance="?android:attr/textAppearanceSmall"
app:keyRevokedOrExpired="@{entry.revokedOrExpired}"
app:keyUserEmail="@{entry.primaryUserId.email}"
app:query="@{entry.query}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
app:keyCreation="@{entry.date}"
app:keyRevokedOrExpired="@{entry.revokedOrExpired}" />
</LinearLayout>
</RelativeLayout>
<FrameLayout
android:id="@+id/extra_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="right" android:animateLayoutChanges="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="4dp"> android:visibility="gone">
<ImageView <include
android:id="@+id/import_item_status" android:id="@+id/extra"
android:layout_width="wrap_content" layout="@layout/import_keys_list_item_extra"
android:layout_height="wrap_content" app:entry="@{entry}" />
android:layout_gravity="center"
android:src="@drawable/status_signature_revoked_cutout_24dp"
android:padding="16dp" />
<TextView </FrameLayout>
android:id="@+id/import_item_algorithm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RSA"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<View </android.support.v7.widget.CardView>
android:id="@+id/import_item_status_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:gravity="right"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="?android:attr/listDivider" />
<LinearLayout </layout>
android:id="@+id/import_item_user_ids_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<TextView
android:id="@+id/import_item_fingerprint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0000 0000 0000 0000 0000\n0000 0000 0000 0000 0000"
android:typeface="monospace"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,126 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" alias="V" />
<import type="org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry" />
<variable name="entry" type="ImportKeysListEntry" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/status"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_key_id_colon"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/colorText" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/colorText"
app:keyId="@{entry.keyIdHex}" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_algorithm_colon"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/colorText" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@{entry.algorithm ?? @string/unknown}"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/colorText" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:visibility="@{entry.revokedOrExpired ? V.VISIBLE : V.GONE}"
app:keyExpired="@{entry.expired}"
app:keyRevoked="@{entry.revoked}" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="vertical"
app:keyUserIds="@{entry.sortedUserIds}"
app:query="@{entry.query}" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/import_key"
style="@style/CardViewActionButton"
android:text="@{entry.updated ? @string/btn_refresh : @string/btn_import}" />
<Button
android:id="@+id/show_key"
style="@style/CardViewActionButton"
android:text="@string/btn_go_to_key"
android:visibility="@{entry.updated ? V.VISIBLE : V.GONE}" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_import_keys_cloud_search"
android:icon="@drawable/ic_search_white_24dp"
android:title="@string/menu_search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="collapseActionView|always" />
<item
android:id="@+id/menu_import_keys_cloud_settings"
android:title="@string/menu_preferences" />
</menu>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_import_keys_file_open"
android:icon="@drawable/ic_folder_white_24dp"
android:title="@string/menu_open"
app:showAsAction="always" />
<item
android:id="@+id/menu_import_keys_file_paste"
android:title="@string/menu_read_clipboard" />
</menu>

View File

@@ -909,7 +909,7 @@
<string name="key_unavailable">nedostupný</string> <string name="key_unavailable">nedostupný</string>
<string name="secret_cannot_multiple">Vaše vlastní klíče lze mazat pouze jednotlivě!</string> <string name="secret_cannot_multiple">Vaše vlastní klíče lze mazat pouze jednotlivě!</string>
<string name="title_view_cert">Zobrazit detaily certifikátu</string> <string name="title_view_cert">Zobrazit detaily certifikátu</string>
<string name="unknown_algorithm">neznámý</string> <string name="unknown">neznámý</string>
<string name="can_sign_not">nelze podepsat</string> <string name="can_sign_not">nelze podepsat</string>
<string name="error_no_encrypt_subkey">Není dostupný šifrovací podklíč!</string> <string name="error_no_encrypt_subkey">Není dostupný šifrovací podklíč!</string>
<string name="contact_show_key">Zobrazit klíč (%s)</string> <string name="contact_show_key">Zobrazit klíč (%s)</string>

View File

@@ -1384,7 +1384,7 @@
<string name="key_unavailable">nicht verfügbar</string> <string name="key_unavailable">nicht verfügbar</string>
<string name="secret_cannot_multiple">Deine eigenen Schlüssel können nur einzeln gelöscht werden!</string> <string name="secret_cannot_multiple">Deine eigenen Schlüssel können nur einzeln gelöscht werden!</string>
<string name="title_view_cert">Beglaubigungsdetails anzeigen</string> <string name="title_view_cert">Beglaubigungsdetails anzeigen</string>
<string name="unknown_algorithm">unbekannt</string> <string name="unknown">unbekannt</string>
<string name="can_sign_not">kann nicht signieren</string> <string name="can_sign_not">kann nicht signieren</string>
<string name="error_no_encrypt_subkey">Kein Verschlüsselungs-Unterschlüssel verfügbar!</string> <string name="error_no_encrypt_subkey">Kein Verschlüsselungs-Unterschlüssel verfügbar!</string>
<string name="contact_show_key">Schlüssel anzeigen (%s)</string> <string name="contact_show_key">Schlüssel anzeigen (%s)</string>

View File

@@ -1266,7 +1266,7 @@
<string name="key_unavailable">no disponible</string> <string name="key_unavailable">no disponible</string>
<string name="secret_cannot_multiple">¡Sus claves propias sólo pueden ser borradas individualmente!</string> <string name="secret_cannot_multiple">¡Sus claves propias sólo pueden ser borradas individualmente!</string>
<string name="title_view_cert">Ver detalles del certificado</string> <string name="title_view_cert">Ver detalles del certificado</string>
<string name="unknown_algorithm">desconocido</string> <string name="unknown">desconocido</string>
<string name="can_sign_not">no puede firmarse</string> <string name="can_sign_not">no puede firmarse</string>
<string name="error_no_encrypt_subkey">¡No hay subclave de cifrado disponible!</string> <string name="error_no_encrypt_subkey">¡No hay subclave de cifrado disponible!</string>
<string name="contact_show_key">Mostrar clave (%s)</string> <string name="contact_show_key">Mostrar clave (%s)</string>

View File

@@ -1281,7 +1281,7 @@
<string name="key_unavailable">eskuraezina</string> <string name="key_unavailable">eskuraezina</string>
<string name="secret_cannot_multiple">Zure jabetzako giltzak banaka bakarrik ezabatu daitezke!</string> <string name="secret_cannot_multiple">Zure jabetzako giltzak banaka bakarrik ezabatu daitezke!</string>
<string name="title_view_cert">Ikusi Egiaztagiriaren Xehetasunak</string> <string name="title_view_cert">Ikusi Egiaztagiriaren Xehetasunak</string>
<string name="unknown_algorithm">ezezaguna</string> <string name="unknown">ezezaguna</string>
<string name="can_sign_not">ezin da sinatu</string> <string name="can_sign_not">ezin da sinatu</string>
<string name="error_no_encrypt_subkey">Ez dago enkriptaketa azpigiltzarik eskuragarri!</string> <string name="error_no_encrypt_subkey">Ez dago enkriptaketa azpigiltzarik eskuragarri!</string>
<string name="contact_show_key">Erakutsi (%s) giltza</string> <string name="contact_show_key">Erakutsi (%s) giltza</string>

View File

@@ -253,7 +253,7 @@
<string name="label_cert_type">Tyyppi</string> <string name="label_cert_type">Tyyppi</string>
<string name="key_no_passphrase">ei salasanaa</string> <string name="key_no_passphrase">ei salasanaa</string>
<string name="key_unavailable">ei saatavilla</string> <string name="key_unavailable">ei saatavilla</string>
<string name="unknown_algorithm">tunnistamaton</string> <string name="unknown">tunnistamaton</string>
<string name="contact_show_key">Näytä avain (%s)</string> <string name="contact_show_key">Näytä avain (%s)</string>
<string name="key_colon">Avain:</string> <string name="key_colon">Avain:</string>
<!--Android Account--> <!--Android Account-->

View File

@@ -1384,7 +1384,7 @@
<string name="key_unavailable">non disponible</string> <string name="key_unavailable">non disponible</string>
<string name="secret_cannot_multiple">Vos propres clefs ne peuvent être supprimées qu\'individuellement !</string> <string name="secret_cannot_multiple">Vos propres clefs ne peuvent être supprimées qu\'individuellement !</string>
<string name="title_view_cert">Voir les détails du certificat</string> <string name="title_view_cert">Voir les détails du certificat</string>
<string name="unknown_algorithm">inconnu</string> <string name="unknown">inconnu</string>
<string name="can_sign_not">impossible de signer</string> <string name="can_sign_not">impossible de signer</string>
<string name="error_no_encrypt_subkey">Aucune sous-clef de chiffrement n\'est proposée !</string> <string name="error_no_encrypt_subkey">Aucune sous-clef de chiffrement n\'est proposée !</string>
<string name="contact_show_key">Montrer la clef (%s)</string> <string name="contact_show_key">Montrer la clef (%s)</string>

View File

@@ -1009,7 +1009,7 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars
<string name="key_unavailable">non disponibile</string> <string name="key_unavailable">non disponibile</string>
<string name="secret_cannot_multiple">Le vostre chiavi possono essere eliminate solo singolarmente!</string> <string name="secret_cannot_multiple">Le vostre chiavi possono essere eliminate solo singolarmente!</string>
<string name="title_view_cert">Visualizza Dettagli Certificati</string> <string name="title_view_cert">Visualizza Dettagli Certificati</string>
<string name="unknown_algorithm">sconosciuto</string> <string name="unknown">sconosciuto</string>
<string name="can_sign_not">non può firmare</string> <string name="can_sign_not">non può firmare</string>
<string name="error_no_encrypt_subkey">Nessuna sottochiave di codifica disponibile!</string> <string name="error_no_encrypt_subkey">Nessuna sottochiave di codifica disponibile!</string>
<string name="contact_show_key">Mostra chiave (%s)</string> <string name="contact_show_key">Mostra chiave (%s)</string>

View File

@@ -1360,7 +1360,7 @@
<string name="key_unavailable">存在しない</string> <string name="key_unavailable">存在しない</string>
<string name="secret_cannot_multiple">あなたが所有者の鍵は個別にしか削除できません!</string> <string name="secret_cannot_multiple">あなたが所有者の鍵は個別にしか削除できません!</string>
<string name="title_view_cert">証明の詳細を見る</string> <string name="title_view_cert">証明の詳細を見る</string>
<string name="unknown_algorithm">不明</string> <string name="unknown">不明</string>
<string name="can_sign_not">署名不可</string> <string name="can_sign_not">署名不可</string>
<string name="error_no_encrypt_subkey">暗号化の副鍵がありません!</string> <string name="error_no_encrypt_subkey">暗号化の副鍵がありません!</string>
<string name="contact_show_key">鍵 (%s) を表示</string> <string name="contact_show_key">鍵 (%s) を表示</string>

View File

@@ -1161,7 +1161,7 @@
<string name="key_unavailable">niet beschikbaar</string> <string name="key_unavailable">niet beschikbaar</string>
<string name="secret_cannot_multiple">Je eigen sleutels kunnen enkel individueel verwijderd worden!</string> <string name="secret_cannot_multiple">Je eigen sleutels kunnen enkel individueel verwijderd worden!</string>
<string name="title_view_cert">Toon Certificaat Details</string> <string name="title_view_cert">Toon Certificaat Details</string>
<string name="unknown_algorithm">onbekend</string> <string name="unknown">onbekend</string>
<string name="can_sign_not">kan niet ondertekenen</string> <string name="can_sign_not">kan niet ondertekenen</string>
<string name="error_no_encrypt_subkey">Geen codeer-subsleutel beschikbaar!</string> <string name="error_no_encrypt_subkey">Geen codeer-subsleutel beschikbaar!</string>
<string name="contact_show_key">Toon sleutel (%s)</string> <string name="contact_show_key">Toon sleutel (%s)</string>

View File

@@ -527,7 +527,7 @@ OSTRZEŻENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezw
<string name="key_unavailable">niedostepne</string> <string name="key_unavailable">niedostepne</string>
<string name="secret_cannot_multiple">Twoje klucze mogą być usuwane tylko pojedynczo!</string> <string name="secret_cannot_multiple">Twoje klucze mogą być usuwane tylko pojedynczo!</string>
<string name="title_view_cert">Zweryfikuj szczegóły certyfikatu</string> <string name="title_view_cert">Zweryfikuj szczegóły certyfikatu</string>
<string name="unknown_algorithm">nieznany</string> <string name="unknown">nieznany</string>
<string name="can_sign_not">nie może podpisać</string> <string name="can_sign_not">nie może podpisać</string>
<string name="error_no_encrypt_subkey">Brak pod-klucza szyfrowania!</string> <string name="error_no_encrypt_subkey">Brak pod-klucza szyfrowania!</string>
<string name="contact_show_key">Pokaż klucz (%s)</string> <string name="contact_show_key">Pokaż klucz (%s)</string>

View File

@@ -1384,7 +1384,7 @@
<string name="key_unavailable">indisponível</string> <string name="key_unavailable">indisponível</string>
<string name="secret_cannot_multiple">Suas próprias chaves só podem ser excluídas individualmente!</string> <string name="secret_cannot_multiple">Suas próprias chaves só podem ser excluídas individualmente!</string>
<string name="title_view_cert">Exibir Detalhes do Certificado</string> <string name="title_view_cert">Exibir Detalhes do Certificado</string>
<string name="unknown_algorithm">desconhecido</string> <string name="unknown">desconhecido</string>
<string name="can_sign_not">não pode assinar</string> <string name="can_sign_not">não pode assinar</string>
<string name="error_no_encrypt_subkey">Nenhuma sub-chave de encriptação disponível!</string> <string name="error_no_encrypt_subkey">Nenhuma sub-chave de encriptação disponível!</string>
<string name="contact_show_key">Exibir chave (%s)</string> <string name="contact_show_key">Exibir chave (%s)</string>

View File

@@ -1433,7 +1433,7 @@
<string name="key_unavailable">недоступно</string> <string name="key_unavailable">недоступно</string>
<string name="secret_cannot_multiple">Ваши собственные ключи можно удалять только по одному!</string> <string name="secret_cannot_multiple">Ваши собственные ключи можно удалять только по одному!</string>
<string name="title_view_cert">Просмотреть детали сертификации</string> <string name="title_view_cert">Просмотреть детали сертификации</string>
<string name="unknown_algorithm">неизв.</string> <string name="unknown">неизв.</string>
<string name="can_sign_not">не для подписания</string> <string name="can_sign_not">не для подписания</string>
<string name="error_no_encrypt_subkey">Нет доп. ключа для шифрования!</string> <string name="error_no_encrypt_subkey">Нет доп. ключа для шифрования!</string>
<string name="contact_show_key">Показать ключ (%s)</string> <string name="contact_show_key">Показать ключ (%s)</string>

View File

@@ -751,7 +751,7 @@
<string name="key_stripped">slečen</string> <string name="key_stripped">slečen</string>
<string name="key_unavailable">ni na voljo</string> <string name="key_unavailable">ni na voljo</string>
<string name="title_view_cert">Preglej podrobosti potrdila</string> <string name="title_view_cert">Preglej podrobosti potrdila</string>
<string name="unknown_algorithm">neznan</string> <string name="unknown">neznan</string>
<string name="can_sign_not">ne more podpisati</string> <string name="can_sign_not">ne more podpisati</string>
<string name="error_no_encrypt_subkey">Ni nobenega podključa za šifriranje!</string> <string name="error_no_encrypt_subkey">Ni nobenega podključa za šifriranje!</string>
<string name="contact_show_key">Prikaži ključ (%s)</string> <string name="contact_show_key">Prikaži ključ (%s)</string>

View File

@@ -1389,7 +1389,7 @@
<string name="key_unavailable">недоступан</string> <string name="key_unavailable">недоступан</string>
<string name="secret_cannot_multiple">Сопствене кључеве можете брисати само појединачно!</string> <string name="secret_cannot_multiple">Сопствене кључеве можете брисати само појединачно!</string>
<string name="title_view_cert">Прикажи детаље сертификата</string> <string name="title_view_cert">Прикажи детаље сертификата</string>
<string name="unknown_algorithm">непознат</string> <string name="unknown">непознат</string>
<string name="can_sign_not">не може да потпише</string> <string name="can_sign_not">не може да потпише</string>
<string name="error_no_encrypt_subkey">Поткључ за шифровање није доступан!</string> <string name="error_no_encrypt_subkey">Поткључ за шифровање није доступан!</string>
<string name="contact_show_key">Прикажи кључ (%s)</string> <string name="contact_show_key">Прикажи кључ (%s)</string>

View File

@@ -958,7 +958,7 @@
<string name="key_unavailable">otillgänglig</string> <string name="key_unavailable">otillgänglig</string>
<string name="secret_cannot_multiple">Dina egna nycklar kan bara raderas var för sig!</string> <string name="secret_cannot_multiple">Dina egna nycklar kan bara raderas var för sig!</string>
<string name="title_view_cert">Visa certifikatinformation</string> <string name="title_view_cert">Visa certifikatinformation</string>
<string name="unknown_algorithm">okänd</string> <string name="unknown">okänd</string>
<string name="can_sign_not">kan inte signera</string> <string name="can_sign_not">kan inte signera</string>
<string name="error_no_encrypt_subkey">Ingen krypteringsundernyckel tillgänglig!</string> <string name="error_no_encrypt_subkey">Ingen krypteringsundernyckel tillgänglig!</string>
<string name="contact_show_key">Visa nyckel (%s)</string> <string name="contact_show_key">Visa nyckel (%s)</string>

View File

@@ -395,7 +395,7 @@
<string name="key_unavailable">mevcut değil</string> <string name="key_unavailable">mevcut değil</string>
<string name="secret_cannot_multiple">Kendi anahtarlarınız yalnızca teker teker silinebilir!</string> <string name="secret_cannot_multiple">Kendi anahtarlarınız yalnızca teker teker silinebilir!</string>
<string name="title_view_cert">Sertifika Ayrıntılarını Görüntüle</string> <string name="title_view_cert">Sertifika Ayrıntılarını Görüntüle</string>
<string name="unknown_algorithm">bilinmeyen</string> <string name="unknown">bilinmeyen</string>
<string name="can_sign_not">imzalanamadı</string> <string name="can_sign_not">imzalanamadı</string>
<string name="error_no_encrypt_subkey">Şifreleme için kullanılabilecek altanahtar mevcut değil!</string> <string name="error_no_encrypt_subkey">Şifreleme için kullanılabilecek altanahtar mevcut değil!</string>
<string name="contact_show_key">Anahtarı göster (%s)</string> <string name="contact_show_key">Anahtarı göster (%s)</string>

View File

@@ -483,7 +483,7 @@
<string name="key_stripped">голий</string> <string name="key_stripped">голий</string>
<string name="secret_cannot_multiple">Ваші власні ключі можна вилучити лише окремо!</string> <string name="secret_cannot_multiple">Ваші власні ключі можна вилучити лише окремо!</string>
<string name="title_view_cert">Переглянути дані сертифікату</string> <string name="title_view_cert">Переглянути дані сертифікату</string>
<string name="unknown_algorithm">невідомий</string> <string name="unknown">невідомий</string>
<string name="can_sign_not">не можна підписати</string> <string name="can_sign_not">не можна підписати</string>
<string name="error_no_encrypt_subkey">Жодний підключ шифрування недоступний!</string> <string name="error_no_encrypt_subkey">Жодний підключ шифрування недоступний!</string>
<string name="contact_show_key">Показати ключ (%s)</string> <string name="contact_show_key">Показати ключ (%s)</string>

View File

@@ -666,7 +666,7 @@
<string name="key_unavailable">無法使用</string> <string name="key_unavailable">無法使用</string>
<string name="secret_cannot_multiple">您的金鑰只能個別地刪除!</string> <string name="secret_cannot_multiple">您的金鑰只能個別地刪除!</string>
<string name="title_view_cert">查看認證內容</string> <string name="title_view_cert">查看認證內容</string>
<string name="unknown_algorithm">未知</string> <string name="unknown">未知</string>
<string name="error_no_encrypt_subkey">沒有可供加密的子金鑰!</string> <string name="error_no_encrypt_subkey">沒有可供加密的子金鑰!</string>
<string name="exchange_description">要發起金鑰交換,先在右邊選擇與會人數,然後點選〝開始交換〞。\n\n接下來會詢問你兩個問題以確保會議成員與交換的指紋是正確的。</string> <string name="exchange_description">要發起金鑰交換,先在右邊選擇與會人數,然後點選〝開始交換〞。\n\n接下來會詢問你兩個問題以確保會議成員與交換的指紋是正確的。</string>
<string name="btn_start_exchange">開始交換</string> <string name="btn_start_exchange">開始交換</string>

View File

@@ -1095,7 +1095,7 @@
<string name="key_unavailable">不可用</string> <string name="key_unavailable">不可用</string>
<string name="secret_cannot_multiple">只能逐个删除您的密钥!</string> <string name="secret_cannot_multiple">只能逐个删除您的密钥!</string>
<string name="title_view_cert">查看认证详情</string> <string name="title_view_cert">查看认证详情</string>
<string name="unknown_algorithm">未知</string> <string name="unknown">未知</string>
<string name="can_sign_not">无法签署</string> <string name="can_sign_not">无法签署</string>
<string name="exchange_description">在开始密钥交换之前,在右侧指定参与者的数量,然后点击“开始交换”按钮。\n\n您将被要求回答两个问题以确认由正确的参与者参与交换过程并且他们的指纹是正确的。</string> <string name="exchange_description">在开始密钥交换之前,在右侧指定参与者的数量,然后点击“开始交换”按钮。\n\n您将被要求回答两个问题以确认由正确的参与者参与交换过程并且他们的指纹是正确的。</string>
<string name="btn_start_exchange">开始交换</string> <string name="btn_start_exchange">开始交换</string>

View File

@@ -122,13 +122,14 @@
<string name="menu_delete_key">"Delete key"</string> <string name="menu_delete_key">"Delete key"</string>
<string name="menu_manage_keys">"Manage my keys"</string> <string name="menu_manage_keys">"Manage my keys"</string>
<string name="menu_search">"Search"</string> <string name="menu_search">"Search"</string>
<string name="menu_open">"Open"</string>
<string name="menu_read_clipboard">"Read from clipboard"</string>
<string name="menu_nfc_preferences">"NFC settings"</string> <string name="menu_nfc_preferences">"NFC settings"</string>
<string name="menu_beam_preferences">"Beam settings"</string> <string name="menu_beam_preferences">"Beam settings"</string>
<string name="menu_encrypt_to">"Encrypt to…"</string> <string name="menu_encrypt_to">"Encrypt to…"</string>
<string name="menu_select_all">"Select all"</string> <string name="menu_select_all">"Select all"</string>
<string name="menu_export_all_keys">"Export all keys"</string> <string name="menu_export_all_keys">"Export all keys"</string>
<string name="menu_update_all_keys">"Update all keys"</string> <string name="menu_update_all_keys">"Update all keys"</string>
<string name="menu_advanced">"Advanced"</string>
<string name="menu_certify_fingerprint">"Confirm with fingerprint"</string> <string name="menu_certify_fingerprint">"Confirm with fingerprint"</string>
<string name="menu_certify_fingerprint_phrases">"Confirm with phrases"</string> <string name="menu_certify_fingerprint_phrases">"Confirm with phrases"</string>
<string name="menu_share_log">"Share log"</string> <string name="menu_share_log">"Share log"</string>
@@ -148,6 +149,7 @@
<string name="label_passphrase_again">"Repeat Password"</string> <string name="label_passphrase_again">"Repeat Password"</string>
<string name="label_show_passphrase">"Show Password"</string> <string name="label_show_passphrase">"Show Password"</string>
<string name="label_algorithm">"Algorithm"</string> <string name="label_algorithm">"Algorithm"</string>
<string name="label_algorithm_colon">"Algorithm:"</string>
<string name="label_ascii_armor">"File ASCII Armor"</string> <string name="label_ascii_armor">"File ASCII Armor"</string>
<string name="label_file_ascii_armor">"Enable ASCII Armor"</string> <string name="label_file_ascii_armor">"Enable ASCII Armor"</string>
<string name="label_write_version_header">"Let others know that you're using OpenKeychain"</string> <string name="label_write_version_header">"Let others know that you're using OpenKeychain"</string>
@@ -165,9 +167,11 @@
<string name="label_file_compression">"File compression"</string> <string name="label_file_compression">"File compression"</string>
<string name="label_keyservers">"Manage OpenPGP keyservers"</string> <string name="label_keyservers">"Manage OpenPGP keyservers"</string>
<string name="label_key_id">"Key ID"</string> <string name="label_key_id">"Key ID"</string>
<string name="label_key_id_colon">"Key ID:"</string>
<string name="label_key_created">"Key created %s"</string> <string name="label_key_created">"Key created %s"</string>
<string name="label_key_type">"Type"</string> <string name="label_key_type">"Type"</string>
<string name="label_creation">"Creation"</string> <string name="label_creation">"Creation"</string>
<string name="label_creation_colon">"Creation:"</string>
<string name="label_expiry">"Expiry"</string> <string name="label_expiry">"Expiry"</string>
<string name="label_usage">"Usage"</string> <string name="label_usage">"Usage"</string>
<string name="label_key_size">"Key Size"</string> <string name="label_key_size">"Key Size"</string>
@@ -254,7 +258,6 @@
<string name="orbot_start_dialog_cancel">"Cancel"</string> <string name="orbot_start_dialog_cancel">"Cancel"</string>
<string name="orbot_start_dialog_ignore_tor">"Don't use Tor"</string> <string name="orbot_start_dialog_ignore_tor">"Don't use Tor"</string>
<string name="user_id_no_name"><![CDATA[<no name>]]></string> <string name="user_id_no_name"><![CDATA[<no name>]]></string>
<string name="none"><![CDATA[<none>]]></string> <string name="none"><![CDATA[<none>]]></string>
@@ -388,8 +391,8 @@
<string name="error_jelly_bean_needed">"You need Android 4.1 to use Android's NFC Beam feature!"</string> <string name="error_jelly_bean_needed">"You need Android 4.1 to use Android's NFC Beam feature!"</string>
<string name="error_nfc_needed">"NFC must be enabled!"</string> <string name="error_nfc_needed">"NFC must be enabled!"</string>
<string name="error_beam_needed">"Beam must be enabled!"</string> <string name="error_beam_needed">"Beam must be enabled!"</string>
<string name="error_nothing">"Nothing to show yet."</string>
<string name="error_nothing_import">"No keys found!"</string> <string name="error_nothing_import">"No keys found!"</string>
<string name="error_nothing_import_selected">"No keys selected for import!"</string>
<string name="error_contacts_key_id_missing">"Retrieving the key ID from contacts failed!"</string> <string name="error_contacts_key_id_missing">"Retrieving the key ID from contacts failed!"</string>
<string name="error_generic_report_bug">"A generic error occurred, please create a new bug report for OpenKeychain."</string> <string name="error_generic_report_bug">"A generic error occurred, please create a new bug report for OpenKeychain."</string>
<string name="error_denied_storage_permission">"Can not read files from storage because access has been denied!"</string> <string name="error_denied_storage_permission">"Can not read files from storage because access has been denied!"</string>
@@ -419,6 +422,7 @@
<string name="progress_done">"Done."</string> <string name="progress_done">"Done."</string>
<string name="progress_cancel">"Cancel"</string> <string name="progress_cancel">"Cancel"</string>
<string name="progress_cancelling">"cancelling…"</string> <string name="progress_cancelling">"cancelling…"</string>
<string name="progress_downloading">"downloading…"</string>
<string name="progress_saving">"saving…"</string> <string name="progress_saving">"saving…"</string>
<string name="progress_importing">"importing…"</string> <string name="progress_importing">"importing…"</string>
<string name="progress_benchmarking">"benchmarking…"</string> <string name="progress_benchmarking">"benchmarking…"</string>
@@ -523,12 +527,17 @@
<string name="import_tab_cloud">"Key Search"</string> <string name="import_tab_cloud">"Key Search"</string>
<string name="import_tab_direct">"File/Clipboard"</string> <string name="import_tab_direct">"File/Clipboard"</string>
<string name="import_tab_qr_code">"QR Code/NFC"</string> <string name="import_tab_qr_code">"QR Code/NFC"</string>
<string name="import_import">"Import selected keys"</string> <string name="import_found_keys">"Found %1$d keys"</string>
<string name="import_qr_code_wrong">"QR Code malformed! Please try again!"</string> <string name="import_qr_code_wrong">"QR Code malformed! Please try again!"</string>
<string name="import_qr_code_fp">"Fingerprint is malformed or too short!"</string> <string name="import_qr_code_fp">"Fingerprint is malformed or too short!"</string>
<string name="import_qr_code_too_short_fingerprint">"Fingerprint is too short!"</string> <string name="import_qr_code_too_short_fingerprint">"Fingerprint is too short!"</string>
<string name="import_qr_code_button">"Scan QR Code"</string> <string name="import_qr_code_button">"Scan QR Code"</string>
<string name="import_qr_code_text">"Place your camera over the QR Code!"</string> <string name="import_qr_code_text">"Place your camera over the QR Code!"</string>
<string name="btn_import">"Import"</string>
<string name="btn_import_keys">"Import all keys"</string>
<string name="btn_view_list">"View list"</string>
<string name="btn_refresh">"Refresh"</string>
<string name="btn_go_to_key">"Go to key"</string>
<!-- Import from URL --> <!-- Import from URL -->
<string name="import_url_warn_no_search_parameter">"No search query defined. You can still manually search on this keyserver."</string> <string name="import_url_warn_no_search_parameter">"No search query defined. You can still manually search on this keyserver."</string>
@@ -1439,6 +1448,7 @@
<string name="msg_get_too_many_responses">"Key search query returned too many candidates. Please refine your query!"</string> <string name="msg_get_too_many_responses">"Key search query returned too many candidates. Please refine your query!"</string>
<string name="msg_get_query_too_short">"Search query too short. Please refine your query!"</string> <string name="msg_get_query_too_short">"Search query too short. Please refine your query!"</string>
<string name="msg_get_query_too_short_or_too_many_responses">"Either no keys or too many have been found. Please improve your query!"</string> <string name="msg_get_query_too_short_or_too_many_responses">"Either no keys or too many have been found. Please improve your query!"</string>
<string name="msg_get_no_enabled_source">"Enable at least one source for downloading!"</string>
<string name="msg_download_query_failed">"An error occurred when searching for keys."</string> <string name="msg_download_query_failed">"An error occurred when searching for keys."</string>
@@ -1509,7 +1519,7 @@
<string name="key_unavailable">"unavailable"</string> <string name="key_unavailable">"unavailable"</string>
<string name="secret_cannot_multiple">"Your own keys can only be deleted individually!"</string> <string name="secret_cannot_multiple">"Your own keys can only be deleted individually!"</string>
<string name="title_view_cert">"View Certificate Details"</string> <string name="title_view_cert">"View Certificate Details"</string>
<string name="unknown_algorithm">"unknown"</string> <string name="unknown">"unknown"</string>
<string name="can_sign_not">"cannot sign"</string> <string name="can_sign_not">"cannot sign"</string>
<string name="error_no_encrypt_subkey">"No encryption subkey available!"</string> <string name="error_no_encrypt_subkey">"No encryption subkey available!"</string>
<string name="contact_show_key">"Show key (%s)"</string> <string name="contact_show_key">"Show key (%s)"</string>
@@ -1569,7 +1579,6 @@
<string name="security_token_status_partly">"Security Token matches, partly bound to key"</string> <string name="security_token_status_partly">"Security Token matches, partly bound to key"</string>
<string name="security_token_create">"Hold Security Token against the back of your device."</string> <string name="security_token_create">"Hold Security Token against the back of your device."</string>
<string name="security_token_reset_or_import">"This Security Token already contains a key. You can import the key using the cloud or reset the Security Token."</string> <string name="security_token_reset_or_import">"This Security Token already contains a key. You can import the key using the cloud or reset the Security Token."</string>
<string name="btn_import">"Import"</string>
<string name="btn_reset">"Reset"</string> <string name="btn_reset">"Reset"</string>
<string name="security_token_import_radio">"Import key"</string> <string name="security_token_import_radio">"Import key"</string>
<string name="security_token_reset_radio">"Reset Security Token"</string> <string name="security_token_reset_radio">"Reset Security Token"</string>

View File

@@ -11,6 +11,17 @@
<item name="android:textSize">17sp</item> <item name="android:textSize">17sp</item>
</style> </style>
<style name="CardViewActionButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_centerVertical">true</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
<item name="android:padding">8dp</item>
<item name="android:textColor">@color/card_view_button</item>
</style>
<style name="SectionHeader"> <style name="SectionHeader">
<item name="android:drawableBottom">?attr/sectionHeaderDrawable</item> <item name="android:drawableBottom">?attr/sectionHeaderDrawable</item>
<item name="android:drawablePadding">4dp</item> <item name="android:drawablePadding">4dp</item>

View File

@@ -24,19 +24,13 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLog;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.WorkaroundBuildConfig; import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = WorkaroundBuildConfig.class, sdk = 23, manifest = "src/main/AndroidManifest.xml") @Config(constants = WorkaroundBuildConfig.class, sdk = 23, manifest = "src/main/AndroidManifest.xml")