diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
155 files changed, 6544 insertions, 2072 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index d1b37aed2..3d58602ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -26,6 +26,8 @@ import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.BuildConfig; import java.io.File; +import java.net.InetSocketAddress; +import java.net.Proxy; public final class Constants { @@ -44,6 +46,8 @@ public final class Constants { public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider"; public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage"; + public static final String CLIPBOARD_LABEL = "Keychain"; + // as defined in http://tools.ietf.org/html/rfc3156, section 7 public static final String NFC_MIME = "application/pgp-keys"; @@ -93,11 +97,33 @@ public final class Constants { public static final String FILE_USE_COMPRESSION = "useFileCompression"; public static final String TEXT_USE_COMPRESSION = "useTextCompression"; public static final String USE_ARMOR = "useArmor"; + // proxy settings + public static final String USE_NORMAL_PROXY = "useNormalProxy"; + public static final String USE_TOR_PROXY = "useTorProxy"; + public static final String PROXY_HOST = "proxyHost"; + public static final String PROXY_PORT = "proxyPort"; + public static final String PROXY_TYPE = "proxyType"; + public static final String THEME = "theme"; + + public static final class Theme { + public static final String LIGHT = "light"; + public static final String DARK = "dark"; + public static final String DEFAULT = Constants.Pref.Theme.LIGHT; + } + } + + /** + * information to connect to Orbot's localhost HTTP proxy + */ + public static final class Orbot { + public static final String PROXY_HOST = "127.0.0.1"; + public static final int PROXY_PORT = 8118; + public static final Proxy.Type PROXY_TYPE = Proxy.Type.HTTP; } public static final class Defaults { public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu"; - public static final int PREF_VERSION = 4; + public static final int PREF_VERSION = 5; } public static final class key { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 627623f47..cd24394d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; import org.sufficientlysecure.keychain.util.Preferences; @@ -91,12 +92,12 @@ public class KeychainApplication extends Application { } brandGlowEffect(getApplicationContext(), - getApplicationContext().getResources().getColor(R.color.primary)); + FormattingUtils.getColorFromAttr(getApplicationContext(), R.attr.colorPrimary)); setupAccountAsNeeded(this); // Update keyserver list as needed - Preferences.getPreferences(this).updatePreferences(); + Preferences.getPreferences(this).upgradePreferences(); TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index 403e654e4..abf16851d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -17,28 +17,22 @@ package org.sufficientlysecure.keychain.compatibility; + import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.support.annotation.Nullable; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; -import java.lang.reflect.Method; - public class ClipboardReflection { - private static final String clipboardLabel = "Keychain"; - - public static void copyToClipboard(Context context, String text) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - - ClipData clip = ClipData.newPlainText(clipboardLabel, text); - clipboard.setPrimaryClip(clip); - - } - - public static String getClipboardText(Context context) { + @Nullable + public static String getClipboardText(@Nullable Context context) { + if (context == null) { + return null; + } ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = clipboard.getPrimaryClip(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java index 07799f466..e1d8c0da7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java @@ -21,7 +21,7 @@ import android.os.Build; import android.os.Handler; /** - * Bug on Android >= 4.2 + * Bug on Android >= 4.2. Fixed in 4.2.2 ? * * http://code.google.com/p/android/issues/detail?id=41901 * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java index c0221fad3..d91dd28bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -20,6 +20,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import java.net.Proxy; import java.util.ArrayList; import java.util.Vector; @@ -30,7 +31,8 @@ public class CloudSearch { private final static long SECONDS = 1000; - public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs) + public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs, + final Proxy proxy) throws Keyserver.CloudSearchFailureException { final ArrayList<Keyserver> servers = new ArrayList<>(); @@ -45,31 +47,42 @@ public class CloudSearch { } final ImportKeysList results = new ImportKeysList(servers.size()); + ArrayList<Thread> searchThreads = new ArrayList<>(); for (final Keyserver keyserver : servers) { Runnable r = new Runnable() { @Override public void run() { try { - results.addAll(keyserver.search(query)); + results.addAll(keyserver.search(query, proxy)); } catch (Keyserver.CloudSearchFailureException e) { problems.add(e); } results.finishedAdding(); // notifies if all searchers done } }; - new Thread(r).start(); + Thread searchThread = new Thread(r); + searchThreads.add(searchThread); + searchThread.start(); } - // wait for either all the searches to come back, or 10 seconds - synchronized(results) { + // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds. + synchronized (results) { try { - results.wait(10 * SECONDS); + if (proxy != null) { + results.wait(30 * SECONDS); + } else { + results.wait(10 * SECONDS); + } + for (Thread thread : searchThreads) { + // kill threads that haven't returned yet + thread.interrupt(); + } } catch (InterruptedException e) { } } if (results.outstandingSuppliers() > 0) { - String message = "Launched " + servers.size() + " cloud searchers, but" + + String message = "Launched " + servers.size() + " cloud searchers, but " + results.outstandingSuppliers() + "failed to complete."; problems.add(new Keyserver.QueryFailedException(message)); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index cb8a53e25..2cf6d8b34 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,18 +18,20 @@ package org.sufficientlysecure.keychain.keyimport; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.TlsHelper; -import java.io.BufferedWriter; import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; @@ -39,6 +41,7 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -190,36 +193,52 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - private HttpURLConnection openConnection(URL url) throws IOException { - HttpURLConnection conn = null; + /** + * returns a client with pinned certificate if necessary + * + * @param url + * @param proxy + * @return + */ + public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException { + OkHttpClient client = new OkHttpClient(); + try { - conn = (HttpURLConnection) TlsHelper.openConnection(url); + TlsHelper.pinCertificateIfNecessary(client, url); } catch (TlsHelper.TlsHelperException e) { Log.w(Constants.TAG, e); } - if (conn == null) { - conn = (HttpURLConnection) url.openConnection(); + + if (proxy != null) { + client.setProxy(proxy); + client.setConnectTimeout(30000, TimeUnit.MILLISECONDS); + } else { + client.setProxy(Proxy.NO_PROXY); + client.setConnectTimeout(5000, TimeUnit.MILLISECONDS); } - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - return conn; + client.setReadTimeout(45000, TimeUnit.MILLISECONDS); + + return client; } - private String query(String request) throws QueryFailedException, HttpError { + private String query(String request, Proxy proxy) throws QueryFailedException, HttpError { try { URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); - Log.d(Constants.TAG, "hkp keyserver query: " + url); - HttpURLConnection conn = openConnection(url); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - return readAll(conn.getInputStream(), conn.getContentEncoding()); + Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); + OkHttpClient client = getClient(url, proxy); + Response response = client.newCall(new Request.Builder().url(url).build()).execute(); + + String responseBody = response.body().string();// contains body both in case of success or failure + + if (response.isSuccessful()) { + return responseBody; } else { - String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new HttpError(response, data); + throw new HttpError(response.code(), responseBody); } } catch (IOException e) { - throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!"); + Log.e(Constants.TAG, "IOException at HkpKeyserver", e); + throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" + + proxy == null?"":" Using proxy " + proxy); } } @@ -232,7 +251,7 @@ public class HkpKeyserver extends Keyserver { * @throws QueryNeedsRepairException */ @Override - public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, + public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { ArrayList<ImportKeysListEntry> results = new ArrayList<>(); @@ -250,7 +269,7 @@ public class HkpKeyserver extends Keyserver { String data; try { - data = query(request); + data = query(request, proxy); } catch (HttpError e) { if (e.getData() != null) { Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH)); @@ -334,13 +353,14 @@ public class HkpKeyserver extends Keyserver { } @Override - public String get(String keyIdHex) throws QueryFailedException { + public String get(String keyIdHex, Proxy proxy) throws QueryFailedException { String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex; - Log.d(Constants.TAG, "hkp keyserver get: " + request); + Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + proxy); String data; try { - data = query(request); + data = query(request, proxy); } catch (HttpError httpError) { + Log.e(Constants.TAG, "Failed to get key at HkpKeyserver", httpError); throw new QueryFailedException("not found"); } Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); @@ -351,38 +371,38 @@ public class HkpKeyserver extends Keyserver { } @Override - public void add(String armoredKey) throws AddKeyException { + public void add(String armoredKey, Proxy proxy) throws AddKeyException { try { - String request = "/pks/add"; + String path = "/pks/add"; String params; try { params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AddKeyException(); } - URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); + URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path); Log.d(Constants.TAG, "hkp keyserver add: " + url.toString()); Log.d(Constants.TAG, "params: " + params); - HttpURLConnection conn = openConnection(url); - conn.setRequestMethod("POST"); - conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); - conn.setDoInput(true); - conn.setDoOutput(true); + RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params); + + Request request = new Request.Builder() + .url(url) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("Content-Length", Integer.toString(params.getBytes().length)) + .post(body) + .build(); - OutputStream os = conn.getOutputStream(); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); - writer.write(params); - writer.flush(); - writer.close(); - os.close(); + Response response = getClient(url, proxy).newCall(request).execute(); - conn.connect(); + Log.d(Constants.TAG, "response code: " + response.code()); + Log.d(Constants.TAG, "answer: " + response.body().string()); + + if (response.code() != 200) { + throw new AddKeyException(); + } - Log.d(Constants.TAG, "response code: " + conn.getResponseCode()); - Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding())); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); @@ -398,6 +418,7 @@ public class HkpKeyserver extends Keyserver { * Tries to find a server responsible for a given domain * * @return A responsible Keyserver or null if not found. + * TODO: PHILIP Add proxy functionality */ public static HkpKeyserver resolve(String domain) { try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index e310e9a3f..c2865410e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import java.net.Proxy; import java.util.ArrayList; import java.util.List; @@ -34,7 +35,7 @@ public class KeybaseKeyserver extends Keyserver { private String mQuery; @Override - public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, + public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { ArrayList<ImportKeysListEntry> results = new ArrayList<>(); @@ -48,7 +49,7 @@ public class KeybaseKeyserver extends Keyserver { mQuery = query; try { - Iterable<Match> matches = Search.search(query); + Iterable<Match> matches = Search.search(query, proxy); for (Match match : matches) { results.add(makeEntry(match)); } @@ -98,16 +99,16 @@ public class KeybaseKeyserver extends Keyserver { } @Override - public String get(String id) throws QueryFailedException { + public String get(String id, Proxy proxy) throws QueryFailedException { try { - return User.keyForUsername(id); + return User.keyForUsername(id, proxy); } catch (KeybaseException e) { throw new QueryFailedException(e.getMessage()); } } @Override - public void add(String armoredKey) throws AddKeyException { + public void add(String armoredKey, Proxy proxy) throws AddKeyException { throw new AddKeyException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java index 5e4bd0b70..640b39f44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.keyimport; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; import java.util.List; public abstract class Keyserver { @@ -31,6 +32,7 @@ public abstract class Keyserver { public CloudSearchFailureException(String message) { super(message); } + public CloudSearchFailureException() { super(); } @@ -67,12 +69,12 @@ public abstract class Keyserver { private static final long serialVersionUID = -507574859137295530L; } - public abstract List<ImportKeysListEntry> search(String query) throws QueryFailedException, + public abstract List<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException; - public abstract String get(String keyIdHex) throws QueryFailedException; + public abstract String get(String keyIdHex, Proxy proxy) throws QueryFailedException; - public abstract void add(String armoredKey) throws AddKeyException; + public abstract void add(String armoredKey, Proxy proxy) throws AddKeyException; public static String readAll(InputStream in, String encoding) throws IOException { ByteArrayOutputStream raw = new ByteArrayOutputStream(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java index fae59b7a4..e4026eaaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -19,7 +19,9 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; import android.os.Parcelable; +import android.support.annotation.NonNull; +import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -76,9 +78,8 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase mCancelled = cancelled; } - public OperationResult execute(T input, CryptoInputParcel cryptoInput) { - return null; - } + @NonNull + public abstract OperationResult execute(T input, CryptoInputParcel cryptoInput); public void updateProgress(int message, int current, int total) { if (mProgressable != null) { @@ -111,8 +112,11 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase @Override public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException { try { - long masterKeyId = mProviderHelper.getMasterKeyId(subKeyId); - return getCachedPassphrase(masterKeyId, subKeyId); + if (subKeyId != key.symmetric) { + long masterKeyId = mProviderHelper.getMasterKeyId(subKeyId); + return getCachedPassphrase(masterKeyId, subKeyId); + } + return getCachedPassphrase(key.symmetric, key.symmetric); } catch (NotFoundException e) { throw new PassphraseCacheInterface.NoSecretKeyException(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 0806e6a16..eeed24db0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -18,17 +18,18 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; +import android.support.annotation.NonNull; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation; import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -43,27 +44,31 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.net.Proxy; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; -/** An operation which implements a high level user id certification operation. - * +/** + * An operation which implements a high level user id certification operation. + * <p/> * This operation takes a specific CertifyActionsParcel as its input. These * contain a masterKeyId to be used for certification, and a list of * masterKeyIds and related user ids to certify. * * @see CertifyActionsParcel - * */ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { - public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { + public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean + cancelled) { super(context, providerHelper, progressable, cancelled); } + @NonNull @Override public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) { @@ -86,14 +91,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { case PIN: case PATTERN: case PASSPHRASE: - if (!cryptoInput.hasPassphrase()) { + passphrase = cryptoInput.getPassphrase(); + if (passphrase == null) { + try { + passphrase = getCachedPassphrase(certificationKey.getKeyId(), certificationKey.getKeyId()); + } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { + // treat as a cache miss for error handling purposes + } + } + + if (passphrase == null) { return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase( - certificationKey.getKeyId(), certificationKey.getKeyId(), null) + certificationKey.getKeyId(), + certificationKey.getKeyId(), + null), + cryptoInput ); } - // certification is always with the master key id, so use that one - passphrase = cryptoInput.getPassphrase(); break; case PASSPHRASE_EMPTY: @@ -101,6 +116,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { break; case DIVERT_TO_CARD: + // the unlock operation will succeed for passphrase == null in a divertToCard key passphrase = null; break; @@ -174,9 +190,9 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { } - if ( ! allRequiredInput.isEmpty()) { + if (!allRequiredInput.isEmpty()) { log.add(LogType.MSG_CRT_NFC_RETURN, 1); - return new CertifyResult(log, allRequiredInput.build()); + return new CertifyResult(log, allRequiredInput.build(), cryptoInput); } log.add(LogType.MSG_CRT_SAVING, 1); @@ -187,11 +203,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { return new CertifyResult(CertifyResult.RESULT_CANCELLED, log); } + // these variables are used inside the following loop, but they need to be created only once HkpKeyserver keyServer = null; ExportOperation exportOperation = null; + Proxy proxy = null; if (parcel.keyServerUri != null) { keyServer = new HkpKeyserver(parcel.keyServerUri); exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable); + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new CertifyResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } } // Write all certified keys into the database @@ -200,7 +229,8 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { // Check if we were cancelled if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, 0); - return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk, uploadError); + return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk, + uploadError); } log.add(LogType.MSG_CRT_SAVE, 2, @@ -210,12 +240,15 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey); if (exportOperation != null) { - // TODO use subresult, get rid of try/catch! - try { - exportOperation.uploadKeyRingToServer(keyServer, certifiedKey); + ExportResult uploadResult = exportOperation.uploadKeyRingToServer( + keyServer, + certifiedKey, + proxy); + log.add(uploadResult, 2); + + if (uploadResult.success()) { uploadOk += 1; - } catch (AddKeyException e) { - Log.e(Constants.TAG, "error uploading key", e); + } else { uploadError += 1; } } @@ -227,19 +260,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { } log.add(result, 2); - } if (certifyOk == 0) { log.add(LogType.MSG_CRT_ERROR_NOTHING, 0); - return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError, uploadOk, uploadError); + return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError, + uploadOk, uploadError); } - log.add(LogType.MSG_CRT_SUCCESS, 0); - //since only verified keys are synced to contacts, we need to initiate a sync now + // since only verified keys are synced to contacts, we need to initiate a sync now ContactSyncAdapterService.requestSync(); - - return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError); + + log.add(LogType.MSG_CRT_SUCCESS, 0); + if (uploadError != 0) { + return new CertifyResult(CertifyResult.RESULT_WARNINGS, log, certifyOk, certifyError, uploadOk, + uploadError); + } else { + return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java index bda574e0a..782cd6800 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -34,6 +35,7 @@ public class ConsolidateOperation extends BaseOperation<ConsolidateInputParcel> super(context, providerHelper, progressable); } + @NonNull @Override public ConsolidateResult execute(ConsolidateInputParcel consolidateInputParcel, CryptoInputParcel cryptoInputParcel) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 50b2ef69b..56bd3b786 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -18,9 +18,11 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.DeleteResult; +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.Progressable; @@ -45,13 +47,19 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> { super(context, providerHelper, progressable); } + @NonNull @Override - public DeleteResult execute(DeleteKeyringParcel deleteKeyringParcel, + public OperationResult execute(DeleteKeyringParcel deleteKeyringParcel, CryptoInputParcel cryptoInputParcel) { long[] masterKeyIds = deleteKeyringParcel.mMasterKeyIds; boolean isSecret = deleteKeyringParcel.mIsSecret; + return onlyDeleteKey(masterKeyIds, isSecret); + } + + private DeleteResult onlyDeleteKey(long[] masterKeyIds, boolean isSecret) { + OperationLog log = new OperationLog(); if (masterKeyIds.length == 0) { @@ -111,7 +119,6 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> { } return new DeleteResult(result, log, success, fail); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index da0aef018..f5ba88502 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -18,10 +18,12 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; @@ -33,16 +35,20 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; -/** An operation which implements a high level key edit operation. - * +/** + * An operation which implements a high level key edit operation. + * <p/> * This operation provides a higher level interface to the edit and * create key operations in PgpKeyOperation. It takes care of fetching * and saving the key before and after the operation. @@ -57,7 +63,16 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { super(context, providerHelper, progressable, cancelled); } - public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) { + /** + * Saves an edited key, and uploads it to a server atomically or otherwise as + * specified in saveParcel + * + * @param saveParcel primary input to the operation + * @param cryptoInput input that changes if user interaction is required + * @return the result of the operation + */ + @NonNull + public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); log.add(LogType.MSG_ED, 0); @@ -118,6 +133,36 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { // It's a success, so this must be non-null now UncachedKeyRing ring = modifyResult.getRing(); + if (saveParcel.isUpload()) { + UncachedKeyRing publicKeyRing; + try { + publicKeyRing = ring.extractPublicKeyRing(); + } catch (IOException e) { + log.add(LogType.MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD, 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + ExportKeyringParcel exportKeyringParcel = + new ExportKeyringParcel(saveParcel.getUploadKeyserver(), + publicKeyRing); + + ExportResult uploadResult = + new ExportOperation(mContext, mProviderHelper, mProgressable) + .execute(exportKeyringParcel, cryptoInput); + + if (uploadResult.isPending()) { + return uploadResult; + } else if (!uploadResult.success() && saveParcel.isUploadAtomic()) { + // if atomic, update fail implies edit operation should also fail and not save + log.add(uploadResult, 2); + return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(), + cryptoInput); + } else { + // upload succeeded or not atomic so we continue + log.add(uploadResult, 2); + } + } + // Save the new keyring. SaveKeyringResult saveResult = mProviderHelper .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 60, 95, 100)); @@ -129,15 +174,24 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { } // There is a new passphrase - cache it - if (saveParcel.mNewUnlock != null) { + if (saveParcel.mNewUnlock != null && cryptoInput.mCachePassphrase) { log.add(LogType.MSG_ED_CACHING_NEW, 1); - PassphraseCacheService.addCachedPassphrase(mContext, - ring.getMasterKeyId(), - ring.getMasterKeyId(), - saveParcel.mNewUnlock.mNewPassphrase != null - ? saveParcel.mNewUnlock.mNewPassphrase - : saveParcel.mNewUnlock.mNewPin, - ring.getPublicKey().getPrimaryUserIdWithFallback()); + + // NOTE: Don't cache empty passphrases! Important for MOVE_KEY_TO_CARD + if (saveParcel.mNewUnlock.mNewPassphrase != null + && ( ! saveParcel.mNewUnlock.mNewPassphrase.isEmpty())) { + PassphraseCacheService.addCachedPassphrase(mContext, + ring.getMasterKeyId(), + ring.getMasterKeyId(), + saveParcel.mNewUnlock.mNewPassphrase, + ring.getPublicKey().getPrimaryUserIdWithFallback()); + } else if (saveParcel.mNewUnlock.mNewPin != null) { + PassphraseCacheService.addCachedPassphrase(mContext, + ring.getMasterKeyId(), + ring.getMasterKeyId(), + saveParcel.mNewUnlock.mNewPin, + ring.getPublicKey().getPrimaryUserIdWithFallback()); + } } updateProgress(R.string.progress_done, 100, 100); @@ -149,5 +203,4 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java index 01a45bc79..a5b70a41f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java @@ -18,9 +18,22 @@ package org.sufficientlysecure.keychain.operations; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Proxy; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextUtils; import org.spongycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.Constants; @@ -40,18 +53,12 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.atomic.AtomicBoolean; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; /** * An operation class which implements high level export @@ -62,7 +69,6 @@ import java.util.concurrent.atomic.AtomicBoolean; * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries() * For the export operation, the input consists of a set of key ids and * either the name of a file or an output uri to write to. - * TODO rework uploadKeyRingToServer */ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { @@ -76,26 +82,43 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { super(context, providerHelper, progressable, cancelled); } - public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) - throws AddKeyException { - uploadKeyRingToServer(server, keyring.getUncachedKeyRing()); + public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, + Proxy proxy) { + return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy); } - public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws - AddKeyException { + public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) { + mProgressable.setProgress(R.string.progress_uploading, 0, 1); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; + OperationLog log = new OperationLog(); + log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex( + keyring.getPublicKey().getKeyId() + )); + try { aos = new ArmoredOutputStream(bos); keyring.encode(aos); aos.close(); String armoredKey = bos.toString("UTF-8"); - server.add(armoredKey); + server.add(armoredKey, proxy); + + log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); - throw new AddKeyException(); + + log.add(LogType.MSG_EXPORT_ERROR_KEY, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } catch (AddKeyException e) { + Log.e(Constants.TAG, "AddKeyException", e); + + log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); } finally { + mProgressable.setProgress(R.string.progress_uploading, 1, 1); try { if (aos != null) { aos.close(); @@ -192,21 +215,21 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { Cursor cursor = null; try { - String selection = null, ids[] = null; + String selection = null, selectionArgs[] = null; if (masterKeyIds != null) { - // generate placeholders and string selection args - ids = new String[masterKeyIds.length]; - StringBuilder placeholders = new StringBuilder("?"); + // convert long[] to String[] + selectionArgs = new String[masterKeyIds.length]; for (int i = 0; i < masterKeyIds.length; i++) { - ids[i] = Long.toString(masterKeyIds[i]); - if (i != 0) { - placeholders.append(",?"); - } + selectionArgs[i] = Long.toString(masterKeyIds[i]); } + // generates ?,?,? as placeholders for selectionArgs + String placeholders = TextUtils.join(",", + Collections.nCopies(masterKeyIds.length, "?")); + // put together selection string - selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID + selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN (" + placeholders + ")"; } @@ -214,7 +237,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { KeyRings.buildUnifiedKeyRingsUri(), new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA, KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET - }, selection, ids, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID ); if (cursor == null || !cursor.moveToFirst()) { @@ -311,20 +334,37 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { } + @NonNull public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) { switch (exportInput.mExportType) { case UPLOAD_KEYSERVER: { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new ExportResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } + HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver); try { - CanonicalizedPublicKeyRing keyring - = mProviderHelper.getCanonicalizedPublicKeyRing( - exportInput.mCanonicalizedPublicKeyringUri); - uploadKeyRingToServer(hkpKeyserver, keyring); - // TODO: replace with proper log - return new ExportResult(ExportResult.RESULT_OK, new OperationLog()); - } catch (Exception e) { + if (exportInput.mCanonicalizedPublicKeyringUri != null) { + CanonicalizedPublicKeyRing keyring + = mProviderHelper.getCanonicalizedPublicKeyRing( + exportInput.mCanonicalizedPublicKeyringUri); + return uploadKeyRingToServer(hkpKeyserver, keyring, proxy); + } else { + return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing, + proxy); + } + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "error uploading key", e); return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog()); - // TODO: Implement better exception handling, replace with log } } case EXPORT_FILE: { @@ -335,8 +375,8 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret, exportInput.mOutputUri); } - default: { // can't happen - return null; + default: { // can never happen, all enum types must be handled above + throw new AssertionError("must not happen, this is a bug!"); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index ace059dac..4acfd6e30 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -18,7 +18,24 @@ package org.sufficientlysecure.keychain.operations; + +import java.io.IOException; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + import android.content.Context; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -39,24 +56,13 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressScaler; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; /** * An operation class which implements high level import @@ -89,39 +95,40 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { // Overloaded functions for using progressable supplied in constructor during import public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, - String keyServerUri) { - return serialKeyRingImport(entries, num, keyServerUri, mProgressable); + String keyServerUri, Proxy proxy) { + return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy); } public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, - String keyServerUri) { + String keyServerUri, Proxy proxy) { Iterator<ParcelableKeyRing> it = entries.iterator(); int numEntries = entries.size(); - return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy); } public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, String keyServerUri, - Progressable progressable) { + Progressable progressable, Proxy proxy) { Iterator<ParcelableKeyRing> it = entries.iterator(); int numEntries = entries.size(); - return serialKeyRingImport(it, numEntries, keyServerUri, progressable); + return serialKeyRingImport(it, numEntries, keyServerUri, progressable, proxy); } + @NonNull public ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache, - String keyServerUri) { + String keyServerUri, Proxy proxy) { // get entries from cached file try { IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); int numEntries = it.getSize(); - return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy); } catch (IOException e) { // Special treatment here, we need a lot @@ -143,11 +150,14 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { * @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud * @param progressable Allows multi-threaded import to supply a progressable that ignores the * progress of a single key being imported - * @return */ + @NonNull public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, - String keyServerUri, Progressable progressable) { - updateProgress(R.string.progress_importing, 0, 100); + String keyServerUri, Progressable progressable, + Proxy proxy) { + if (progressable != null) { + progressable.setProgress(R.string.progress_importing, 0, 100); + } OperationLog log = new OperationLog(); log.add(LogType.MSG_IMPORT, 0, num); @@ -208,10 +218,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { if (entry.mExpectedFingerprint != null) { log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + entry.mExpectedFingerprint.substring(24)); - data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes(); + data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy) + .getBytes(); } else { log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex); - data = keyServer.get(entry.mKeyIdHex).getBytes(); + data = keyServer.get(entry.mKeyIdHex, proxy).getBytes(); } key = UncachedKeyRing.decodeFromData(data); if (key != null) { @@ -234,7 +245,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { try { log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName); - byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes(); + byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes(); UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data); // If there already is a key, merge the two @@ -373,12 +384,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { importedMasterKeyIdsArray); } + @NonNull @Override - public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { - return importKeys(importInput.mKeyList, importInput.mKeyserver); - } - - public ImportKeyResult importKeys(ArrayList<ParcelableKeyRing> keyList, String keyServer) { + public OperationResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { + ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList; + String keyServer = importInput.mKeyserver; ImportKeyResult result; @@ -386,8 +396,21 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext, "key_import.pcl"); - result = serialKeyRingImport(cache, keyServer); + result = serialKeyRingImport(cache, null, null); } else { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if(!OrbotHelper.isOrbotInRequiredState(mContext)) { + // show dialog to enable/install dialog + return new ImportKeyResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy + .getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } // if there is more than one key with the same fingerprint, we do a serial import to // prevent // https://github.com/open-keychain/open-keychain/issues/1221 @@ -397,9 +420,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } if (keyFingerprintSet.size() == keyList.size()) { // all keys have unique fingerprints - result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer); + result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, + proxy); } else { - result = serialKeyRingImport(keyList, keyServer); + result = serialKeyRingImport(keyList, keyServer, proxy); } } @@ -407,8 +431,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { return result; } + @NonNull private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, - int totKeys, final String keyServer) { + int totKeys, final String keyServer, + final Proxy proxy) { Log.d(Constants.TAG, "Multi-threaded key import starting"); if (keyListIterator != null) { KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable); @@ -421,7 +447,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { new SynchronousQueue<Runnable>()); ExecutorCompletionService<ImportKeyResult> importCompletionService = - new ExecutorCompletionService(importExecutor); + new ExecutorCompletionService<>(importExecutor); while (keyListIterator.hasNext()) { // submit all key rings to be imported @@ -436,7 +462,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { ArrayList<ParcelableKeyRing> list = new ArrayList<>(); list.add(pkRing); - return serialKeyRingImport(list, keyServer, ignoreProgressable); + return serialKeyRingImport(list, keyServer, ignoreProgressable, proxy); } }; @@ -562,4 +588,4 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } } -}
\ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index 57b99951d..30f37dd4f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -19,7 +19,15 @@ package org.sufficientlysecure.keychain.operations; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.List; + import android.content.Context; +import android.support.annotation.NonNull; import com.textuality.keybase.lib.Proof; import com.textuality.keybase.lib.prover.Prover; @@ -41,11 +49,9 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> { @@ -54,9 +60,22 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat super(context, providerHelper, progressable); } + @NonNull @Override public KeybaseVerificationResult execute(KeybaseVerificationParcel keybaseInput, CryptoInputParcel cryptoInput) { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new KeybaseVerificationResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } String requiredFingerprint = keybaseInput.mRequiredFingerprint; @@ -76,7 +95,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); } - if (!prover.fetchProofData()) { + if (!prover.fetchProofData(proxy)) { log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1); return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); } @@ -96,7 +115,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); } Record[] records = dnsQuery.getAnswers(); - List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); + List<List<byte[]>> extents = new ArrayList<>(); for (Record r : records) { Data d = r.getPayload(); if (d instanceof TXT) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index efe0c466a..2f25b6926 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -17,7 +17,11 @@ package org.sufficientlysecure.keychain.operations; + +import java.util.concurrent.atomic.AtomicBoolean; + import android.content.Context; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -29,7 +33,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; @@ -37,9 +40,6 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; - /** An operation which promotes a public key ring to a secret one. * * This operation can only be applied to public key rings where no secret key @@ -54,18 +54,10 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> { super(context, providerHelper, progressable, cancelled); } + @NonNull @Override public PromoteKeyResult execute(PromoteKeyringParcel promoteKeyringParcel, CryptoInputParcel cryptoInputParcel) { - // Input - long masterKeyId = promoteKeyringParcel.mKeyRingId; - byte[] cardAid = promoteKeyringParcel.mCardAid; - long[] subKeyIds = promoteKeyringParcel.mSubKeyIds; - - return execute(masterKeyId, cardAid, subKeyIds); - } - - public PromoteKeyResult execute(long masterKeyId, byte[] cardAid, long[] subKeyIds) { OperationLog log = new OperationLog(); log.add(LogType.MSG_PR, 0); @@ -76,17 +68,17 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> { try { log.add(LogType.MSG_PR_FETCHING, 1, - KeyFormattingUtils.convertKeyIdToHex(masterKeyId)); + KeyFormattingUtils.convertKeyIdToHex(promoteKeyringParcel.mKeyRingId)); CanonicalizedPublicKeyRing pubRing = - mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); + mProviderHelper.getCanonicalizedPublicKeyRing(promoteKeyringParcel.mKeyRingId); - if (subKeyIds == null) { + if (promoteKeyringParcel.mSubKeyIds == null) { log.add(LogType.MSG_PR_ALL, 1); } else { // sort for binary search for (CanonicalizedPublicKey key : pubRing.publicKeyIterator()) { long subKeyId = key.getKeyId(); - if (naiveIndexOf(subKeyIds, subKeyId) != null) { + if (naiveIndexOf(promoteKeyringParcel.mSubKeyIds, subKeyId) != null) { log.add(LogType.MSG_PR_SUBKEY_MATCH, 1, KeyFormattingUtils.convertKeyIdToHex(subKeyId)); } else { @@ -97,7 +89,8 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> { } // create divert-to-card secret key from public key - promotedRing = pubRing.createDivertSecretRing(cardAid, subKeyIds); + promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.mCardAid, + promoteKeyringParcel.mSubKeyIds); } catch (NotFoundException e) { log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java new file mode 100644 index 000000000..ecf64e1af --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.results.InputPendingResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.RevokeResult; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> { + + public RevokeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); + } + + @NonNull + @Override + public OperationResult execute(RevokeKeyringParcel revokeKeyringParcel, + CryptoInputParcel cryptoInputParcel) { + + // we don't cache passphrases during revocation + cryptoInputParcel.mCachePassphrase = false; + + long masterKeyId = revokeKeyringParcel.mMasterKeyId; + + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_REVOKE, 0, + KeyFormattingUtils.beautifyKeyId(masterKeyId)); + + try { + + Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId); + CachedPublicKeyRing keyRing = mProviderHelper.getCachedPublicKeyRing(secretUri); + + // check if this is a master secret key we can work with + switch (keyRing.getSecretKeyType(masterKeyId)) { + case GNU_DUMMY: + log.add(OperationResult.LogType.MSG_EK_ERROR_DUMMY, 1); + return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); + } + + SaveKeyringParcel saveKeyringParcel = getRevokedSaveKeyringParcel(masterKeyId, + keyRing.getFingerprint()); + + // all revoke operations are made atomic as of now + saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true, + revokeKeyringParcel.mKeyserver); + + InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext, + mProviderHelper, mProgressable, mCancelled) + .execute(saveKeyringParcel, cryptoInputParcel); + + if (revokeAndUploadResult.isPending()) { + return revokeAndUploadResult; + } + + log.add(revokeAndUploadResult, 1); + + if (revokeAndUploadResult.success()) { + log.add(OperationResult.LogType.MSG_REVOKE_OK, 1); + return new RevokeResult(RevokeResult.RESULT_OK, log, masterKeyId); + } else { + log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1); + return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); + } + + } catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "could not find key to revoke", e); + log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1); + return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); + } + } + + private SaveKeyringParcel getRevokedSaveKeyringParcel(long masterKeyId, byte[] fingerprint) { + final String[] SUBKEYS_PROJECTION = new String[]{ + KeychainContract.Keys.KEY_ID + }; + final int INDEX_KEY_ID = 0; + + Uri keysUri = KeychainContract.Keys.buildKeysUri(masterKeyId); + Cursor subKeyCursor = + mContext.getContentResolver().query(keysUri, SUBKEYS_PROJECTION, null, null, null); + + SaveKeyringParcel saveKeyringParcel = + new SaveKeyringParcel(masterKeyId, fingerprint); + + // add all subkeys, for revocation + while (subKeyCursor != null && subKeyCursor.moveToNext()) { + saveKeyringParcel.mRevokeSubKeys.add(subKeyCursor.getLong(INDEX_KEY_ID)); + } + if (subKeyCursor != null) { + subKeyCursor.close(); + } + + final String[] USER_IDS_PROJECTION = new String[]{ + KeychainContract.UserPackets.USER_ID + }; + final int INDEX_USER_ID = 0; + + Uri userIdsUri = KeychainContract.UserPackets.buildUserIdsUri(masterKeyId); + Cursor userIdCursor = mContext.getContentResolver().query( + userIdsUri, USER_IDS_PROJECTION, null, null, null); + + while (userIdCursor != null && userIdCursor.moveToNext()) { + saveKeyringParcel.mRevokeUserIds.add(userIdCursor.getString(INDEX_USER_ID)); + } + if (userIdCursor != null) { + userIdCursor.close(); + } + + return saveKeyringParcel; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index 7c58d62f8..843a55389 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -19,13 +19,13 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; import android.net.Uri; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSign import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; @@ -62,6 +63,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> { super(context, providerHelper, progressable, cancelled); } + @NonNull public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); @@ -85,7 +87,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> { input.getSignatureMasterKeyId()).getSecretSignId(); input.setSignatureSubKeyId(signKeyId); } catch (PgpKeyNotFoundException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Key not found", e); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } } @@ -153,7 +155,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> { RequiredInputParcel requiredInput = result.getRequiredInputParcel(); // Passphrase returns immediately, nfc are aggregated if (requiredInput.mType == RequiredInputType.PASSPHRASE) { - return new SignEncryptResult(log, requiredInput, results); + return new SignEncryptResult(log, requiredInput, results, cryptoInput); } if (pendingInputBuilder == null) { pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime, @@ -171,7 +173,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> { } while (!inputUris.isEmpty()); if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) { - return new SignEncryptResult(log, pendingInputBuilder.build(), results); + return new SignEncryptResult(log, pendingInputBuilder.build(), results, cryptoInput); } if (!outputUris.isEmpty()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index a9f8170d9..cf73f019c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; @@ -38,8 +39,9 @@ public class CertifyResult extends InputPendingResult { super(result, log); } - public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) { - super(log, requiredInput); + public CertifyResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); } public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index 25a86f137..f9a738d56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -24,7 +24,6 @@ import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.util.Passphrase; public class DecryptVerifyResult extends InputPendingResult { @@ -45,8 +44,9 @@ public class DecryptVerifyResult extends InputPendingResult { super(result, log); } - public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) { - super(log, requiredInput); + public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); } public DecryptVerifyResult(Parcel source) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java index 52ff8bf44..1a8f10d4f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java @@ -21,8 +21,11 @@ package org.sufficientlysecure.keychain.operations.results; import android.app.Activity; import android.content.Intent; import android.os.Parcel; +import android.support.annotation.Nullable; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -30,7 +33,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -public class DeleteResult extends OperationResult { +public class DeleteResult extends InputPendingResult { final public int mOk, mFail; @@ -40,6 +43,19 @@ public class DeleteResult extends OperationResult { mFail = fail; } + /** + * used when more input is required + * @param log operation log upto point of required input, if any + * @param requiredInput represents input required + */ + public DeleteResult(@Nullable OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); + // values are not to be used + mOk = -1; + mFail = -1; + } + /** Construct from a parcel - trivial because we have no extra data. */ public DeleteResult(Parcel source) { super(source); @@ -109,7 +125,10 @@ public class DeleteResult extends OperationResult { } else { duration = 0; style = Style.ERROR; - if (mFail == 0) { + if (mLog.getLast().mType == LogType.MSG_DEL_ERROR_MULTI_SECRET) { + str = activity.getString(R.string.secret_cannot_multiple); + } + else if (mFail == 0) { str = activity.getString(R.string.delete_nothing); } else { str = activity.getResources().getQuantityString(R.plurals.delete_fail, mFail); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java index 842b75c3b..6098d59d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java @@ -20,7 +20,10 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -public class EditKeyResult extends OperationResult { +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class EditKeyResult extends InputPendingResult { public final Long mMasterKeyId; @@ -29,6 +32,12 @@ public class EditKeyResult extends OperationResult { mMasterKeyId = masterKeyId; } + public EditKeyResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); + mMasterKeyId = null; + } + public EditKeyResult(Parcel source) { super(source); mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java index c8edce259..e21ef949f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java @@ -19,7 +19,10 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -public class ExportResult extends OperationResult { +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class ExportResult extends InputPendingResult { final int mOkPublic, mOkSecret; @@ -33,6 +36,15 @@ public class ExportResult extends OperationResult { mOkSecret = okSecret; } + + public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInputParcel, cryptoInputParcel); + // we won't use these values + mOkPublic = -1; + mOkSecret = -1; + } + /** Construct from a parcel - trivial because we have no extra data. */ public ExportResult(Parcel source) { super(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java index 53bc545c5..bdc4d9a47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java @@ -20,7 +20,10 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -public class GetKeyResult extends OperationResult { +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class GetKeyResult extends InputPendingResult { public int mNonPgpPartsCount; @@ -36,6 +39,11 @@ public class GetKeyResult extends OperationResult { super(result, log); } + public GetKeyResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); + } + public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + 8; public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + 16; public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + 32; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 2a032cef2..5f5090bee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -23,6 +23,8 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -30,7 +32,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -public class ImportKeyResult extends OperationResult { +public class ImportKeyResult extends InputPendingResult { public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; public final long[] mImportedMasterKeyIds; @@ -80,7 +82,7 @@ public class ImportKeyResult extends OperationResult { } public ImportKeyResult(int result, OperationLog log) { - this(result, log, 0, 0, 0, 0, new long[] { }); + this(result, log, 0, 0, 0, 0, new long[]{}); } public ImportKeyResult(int result, OperationLog log, @@ -94,6 +96,17 @@ public class ImportKeyResult extends OperationResult { mImportedMasterKeyIds = importedMasterKeyIds; } + public ImportKeyResult(OperationLog log, RequiredInputParcel requiredInputParcel, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInputParcel, cryptoInputParcel); + // just assign default values, we won't use them anyway + mNewKeys = 0; + mUpdatedKeys = 0; + mBadKeys = 0; + mSecret = 0; + mImportedMasterKeyIds = new long[]{}; + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java index 0b7aa6d03..d767382ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -18,10 +18,9 @@ package org.sufficientlysecure.keychain.operations.results; -import java.util.ArrayList; - import android.os.Parcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; public class InputPendingResult extends OperationResult { @@ -30,26 +29,33 @@ public class InputPendingResult extends OperationResult { public static final int RESULT_PENDING = RESULT_ERROR + 8; final RequiredInputParcel mRequiredInput; + // in case operation needs to add to/changes the cryptoInputParcel sent to it + public final CryptoInputParcel mCryptoInputParcel; public InputPendingResult(int result, OperationLog log) { super(result, log); mRequiredInput = null; + mCryptoInputParcel = null; } - public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) { + public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { super(RESULT_PENDING, log); mRequiredInput = requiredInput; + mCryptoInputParcel = cryptoInputParcel; } public InputPendingResult(Parcel source) { super(source); mRequiredInput = source.readParcelable(getClass().getClassLoader()); + mCryptoInputParcel = source.readParcelable(getClass().getClassLoader()); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(mRequiredInput, 0); + dest.writeParcelable(mCryptoInputParcel, 0); } public boolean isPending() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java index 420cbbf01..84648d32c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java @@ -24,7 +24,10 @@ import android.os.Parcelable; import com.textuality.keybase.lib.KeybaseException; import com.textuality.keybase.lib.prover.Prover; -public class KeybaseVerificationResult extends OperationResult implements Parcelable { +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class KeybaseVerificationResult extends InputPendingResult { public final String mProofUrl; public final String mPresenceUrl; public final String mPresenceLabel; @@ -44,6 +47,14 @@ public class KeybaseVerificationResult extends OperationResult implements Parcel mPresenceLabel = prover.getPresenceLabel(); } + public KeybaseVerificationResult(OperationLog log, RequiredInputParcel requiredInputParcel, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInputParcel, cryptoInputParcel); + mProofUrl = null; + mPresenceUrl = null; + mPresenceLabel = null; + } + protected KeybaseVerificationResult(Parcel in) { super(in); mProofUrl = in.readString(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f0561bef2..04013e9ed 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -482,6 +482,7 @@ public abstract class OperationResult implements Parcelable { // secret key modify MSG_MF (LogLevel.START, R.string.msg_mr), MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert), + MSG_MF_ERROR_DIVERT_NEWSUB (LogLevel.ERROR, R.string.msg_mf_error_divert_newsub), MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial), MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode), MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint), @@ -499,6 +500,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted), MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary), MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig), + MSG_MF_ERROR_SUB_STRIPPED(LogLevel.ERROR, R.string.msg_mf_error_sub_stripped), MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands), MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot), @@ -510,6 +512,8 @@ public abstract class OperationResult implements Parcelable { MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), + MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin), + MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin), MSG_MF_PASSPHRASE_KEY (LogLevel.DEBUG, R.string.msg_mf_passphrase_key), MSG_MF_PASSPHRASE_EMPTY_RETRY (LogLevel.DEBUG, R.string.msg_mf_passphrase_empty_retry), MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail), @@ -567,6 +571,8 @@ public abstract class OperationResult implements Parcelable { MSG_ED_CACHING_NEW (LogLevel.DEBUG, R.string.msg_ed_caching_new), MSG_ED_ERROR_NO_PARCEL (LogLevel.ERROR, R.string.msg_ed_error_no_parcel), MSG_ED_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_ed_error_key_not_found), + MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD (LogLevel.ERROR, + R.string.msg_ed_error_extract_public_upload), MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching), MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success), @@ -601,12 +607,14 @@ public abstract class OperationResult implements Parcelable { MSG_DC_CLEAR_SIGNATURE_OK (LogLevel.OK, R.string.msg_dc_clear_signature_ok), MSG_DC_CLEAR_SIGNATURE (LogLevel.DEBUG, R.string.msg_dc_clear_signature), MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase), + MSG_DC_ERROR_SYM_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_sym_passphrase), MSG_DC_ERROR_CORRUPT_DATA (LogLevel.ERROR, R.string.msg_dc_error_corrupt_data), MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key), MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check), MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing), MSG_DC_ERROR_INVALID_DATA (LogLevel.ERROR, R.string.msg_dc_error_invalid_data), MSG_DC_ERROR_IO (LogLevel.ERROR, R.string.msg_dc_error_io), + MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input), MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data), MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key), MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception), @@ -690,6 +698,7 @@ public abstract class OperationResult implements Parcelable { MSG_CRT_WARN_NOT_FOUND (LogLevel.WARN, R.string.msg_crt_warn_not_found), MSG_CRT_WARN_CERT_FAILED (LogLevel.WARN, R.string.msg_crt_warn_cert_failed), MSG_CRT_WARN_SAVE_FAILED (LogLevel.WARN, R.string.msg_crt_warn_save_failed), + MSG_CRT_WARN_UPLOAD_FAILED (LogLevel.WARN, R.string.msg_crt_warn_upload_failed), MSG_IMPORT (LogLevel.START, R.plurals.msg_import), @@ -710,6 +719,7 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), MSG_EXPORT (LogLevel.START, R.plurals.msg_export), + MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public), MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public), MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret), MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all), @@ -721,7 +731,9 @@ public abstract class OperationResult implements Parcelable { MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db), MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io), MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key), + MSG_EXPORT_ERROR_UPLOAD (LogLevel.ERROR, R.string.msg_export_error_upload), MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success), + MSG_EXPORT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_export_upload_success), MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), @@ -742,7 +754,7 @@ public abstract class OperationResult implements Parcelable { MSG_GET_QUERY_FAILED(LogLevel.ERROR, R.string.msg_download_query_failed), MSG_DEL_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_del_error_empty), - MSG_DEL_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_del_error_multi_secret), + MSG_DEL_ERROR_MULTI_SECRET (LogLevel.ERROR, R.string.msg_del_error_multi_secret), MSG_DEL (LogLevel.START, R.plurals.msg_del), MSG_DEL_KEY (LogLevel.DEBUG, R.string.msg_del_key), MSG_DEL_KEY_FAIL (LogLevel.WARN, R.string.msg_del_key_fail), @@ -750,6 +762,13 @@ public abstract class OperationResult implements Parcelable { MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok), MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail), + MSG_REVOKE_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_revoke_error_empty), + MSG_REVOKE_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret), + MSG_REVOKE_ERROR_NOT_FOUND (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret), + MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key), + MSG_REVOKE_KEY_FAIL (LogLevel.ERROR, R.string.msg_revoke_key_fail), + MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok), + // keybase verification MSG_KEYBASE_VERIFICATION(LogLevel.START, R.string.msg_keybase_verification), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java index 38edbf6ee..30307ba46 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java @@ -22,6 +22,7 @@ import android.os.Parcel; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -37,8 +38,9 @@ public class PgpEditKeyResult extends InputPendingResult { mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; } - public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) { - super(log, requiredInput); + public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); mRingMasterKeyId = Constants.key.none; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java index acb265462..2b33b8ace 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -38,8 +39,9 @@ public class PgpSignEncryptResult extends InputPendingResult { super(result, log); } - public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) { - super(log, requiredInput); + public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); } public PgpSignEncryptResult(Parcel source) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java new file mode 100644 index 000000000..b737f6e50 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/RevokeResult.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.app.Activity; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.LogDisplayActivity; +import org.sufficientlysecure.keychain.ui.LogDisplayFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; + +public class RevokeResult extends InputPendingResult { + + public final long mMasterKeyId; + + public RevokeResult(int result, OperationLog log, long masterKeyId) { + super(result, log); + mMasterKeyId = masterKeyId; + } + + /** + * used when more input is required + * + * @param log operation log upto point of required input, if any + * @param requiredInput represents input required + */ + @SuppressWarnings("unused") // standard pattern across all results, we might need it later + public RevokeResult(@Nullable OperationLog log, RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); + // we won't use these values + mMasterKeyId = -1; + } + + /** Construct from a parcel - trivial because we have no extra data. */ + public RevokeResult(Parcel source) { + super(source); + mMasterKeyId = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mMasterKeyId); + } + + public static final Parcelable.Creator<RevokeResult> CREATOR = new Parcelable.Creator<RevokeResult>() { + @Override + public RevokeResult createFromParcel(Parcel in) { + return new RevokeResult(in); + } + + @Override + public RevokeResult[] newArray(int size) { + return new RevokeResult[size]; + } + }; + + @Override + public Notify.Showable createNotify(final Activity activity) { + + int resultType = getResult(); + + String str; + int duration; + Notify.Style style; + + // Not an overall failure + if ((resultType & OperationResult.RESULT_ERROR) == 0) { + + duration = Notify.LENGTH_LONG; + + // New and updated keys + if (resultType == OperationResult.RESULT_OK) { + style = Notify.Style.OK; + str = activity.getString(R.string.revoke_ok); + } else { + duration = 0; + style = Notify.Style.ERROR; + str = "internal error"; + } + + } else { + duration = 0; + style = Notify.Style.ERROR; + str = activity.getString(R.string.revoke_fail); + } + + return Notify.create(activity, str, duration, style, new Notify.ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, RevokeResult.this); + activity.startActivity(intent); + } + }, R.string.snackbar_details); + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index b05921b0d..0e0c5d598 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -19,18 +19,20 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -import java.util.ArrayList; - +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import java.util.ArrayList; public class SignEncryptResult extends InputPendingResult { ArrayList<PgpSignEncryptResult> mResults; byte[] mResultBytes; - public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) { - super(log, requiredInput); + public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, + ArrayList<PgpSignEncryptResult> results, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInput, cryptoInputParcel); mResults = results; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 432ba23e9..770e8de91 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -27,6 +27,9 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; +import java.util.Set; + /** A generic wrapped PGPKeyRing object. * @@ -91,6 +94,16 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getRing().getPublicKey().isEncryptionKey(); } + public Set<Long> getEncryptIds() { + HashSet<Long> result = new HashSet<>(); + for(CanonicalizedPublicKey key : publicKeyIterator()) { + if (key.canEncrypt() && key.isValid()) { + result.add(key.getKeyId()); + } + } + return result; + } + public long getEncryptId() throws PgpKeyNotFoundException { for(CanonicalizedPublicKey key : publicKeyIterator()) { if (key.canEncrypt() && key.isValid()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 676491164..be5f21f23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -62,19 +62,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { return mRing; } - /** Getter that returns the subkey that should be used for signing. */ - CanonicalizedPublicKey getEncryptionSubKey() throws PgpKeyNotFoundException { - PGPPublicKey key = getRing().getPublicKey(getEncryptId()); - if(key != null) { - CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key); - if(!cKey.canEncrypt()) { - throw new PgpKeyNotFoundException("key error"); - } - return cKey; - } - throw new PgpKeyNotFoundException("no encryption key available"); - } - public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() { @SuppressWarnings("unchecked") final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index a6d260d22..e264b4678 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -19,12 +19,14 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; +import android.support.annotation.NonNull; import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPDataValidationException; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; import org.spongycastle.openpgp.PGPException; @@ -44,7 +46,9 @@ import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.util.encoders.DecoderException; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; @@ -80,9 +84,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> super(context, providerHelper, progressable); } - /** - * Decrypts and/or verifies data based on parameters of class - */ + /** Decrypts and/or verifies data based on parameters of PgpDecryptVerifyInputParcel. */ + @NonNull public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) { InputData inputData; OutputStream outputStream; @@ -96,8 +99,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0); inputData = new InputData(inputStream, inputSize); } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; + Log.e(Constants.TAG, "Input URI could not be opened: " + input.getInputUri(), e); + OperationLog log = new OperationLog(); + log.add(LogType.MSG_DC_ERROR_INPUT, 1); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } @@ -107,8 +112,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> try { outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri()); } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; + Log.e(Constants.TAG, "Output URI could not be opened: " + input.getOutputUri(), e); + OperationLog log = new OperationLog(); + log.add(LogType.MSG_DC_ERROR_IO, 1); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } @@ -122,11 +129,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } + @NonNull public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput, InputData inputData, OutputStream outputStream) { return executeInternal(input, cryptoInput, inputData, outputStream); } + @NonNull private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput, InputData inputData, OutputStream outputStream) { try { @@ -161,6 +170,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> OperationLog log = new OperationLog(); log.add(LogType.MSG_DC_ERROR_PGP_EXCEPTION, 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } catch (DecoderException | ArrayIndexOutOfBoundsException e) { + // these can happen if assumptions in JcaPGPObjectFactory.nextObject() aren't + // fulfilled, so we need to catch them here to handle this gracefully + Log.d(Constants.TAG, "data error", e); + OperationLog log = new OperationLog(); + log.add(LogType.MSG_DC_ERROR_IO, 1); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } catch (IOException e) { Log.d(Constants.TAG, "IOException", e); OperationLog log = new OperationLog(); @@ -169,9 +185,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } - /** - * Verify Keybase.io style signed literal data - */ + /**Verify signed plaintext data (PGP/INLINE). */ + @NonNull private DecryptVerifyResult verifySignedLiteralData( PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent) throws IOException, PGPException { @@ -301,9 +316,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } - /** - * Decrypt and/or verifies binary or ascii armored pgp - */ + /** Decrypt and/or verify binary or ascii armored pgp data. */ + @NonNull private DecryptVerifyResult decryptVerify( PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput, InputStream in, OutputStream out, int indent) throws IOException, PGPException { @@ -445,7 +459,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); return new DecryptVerifyResult(log, RequiredInputParcel.createRequiredDecryptPassphrase( - secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId())); + secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()), + cryptoInput); } } @@ -473,12 +488,24 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> // if no passphrase is given, return here // indicating that a passphrase is missing! if (!cryptoInput.hasPassphrase()) { - log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); - return new DecryptVerifyResult(log, - RequiredInputParcel.createRequiredSymmetricPassphrase()); - } - passphrase = cryptoInput.getPassphrase(); + try { + passphrase = getCachedPassphrase(key.symmetric); + log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); + } catch (PassphraseCacheInterface.NoSecretKeyException e) { + // nvm + } + + if (passphrase == null) { + log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); + return new DecryptVerifyResult(log, + RequiredInputParcel.createRequiredSymmetricPassphrase(), + cryptoInput); + } + + } else { + passphrase = cryptoInput.getPassphrase(); + } // break out of while, only decrypt the first packet break; @@ -514,7 +541,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( passphrase.getCharArray()); - clear = encryptedDataSymmetric.getDataStream(decryptorFactory); + try { + clear = encryptedDataSymmetric.getDataStream(decryptorFactory); + } catch (PGPDataValidationException e) { + log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent +1); + return new DecryptVerifyResult(log, + RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput); + } + encryptedData = encryptedDataSymmetric; symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory); @@ -548,7 +582,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation( secretEncryptionKey.getRing().getMasterKeyId(), secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0] - )); + ), + cryptoInput); } @@ -843,6 +878,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> * The method is heavily based on * pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java */ + @NonNull private DecryptVerifyResult verifyCleartextSignature( ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException { @@ -950,6 +986,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return result; } + @NonNull private DecryptVerifyResult verifyDetachedSignature( PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent) throws IOException, PGPException { @@ -1042,7 +1079,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return result; } - private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException { + private PGPSignature processPGPSignatureList( + PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) + throws PGPException { CanonicalizedPublicKeyRing signingRing = null; CanonicalizedPublicKey signingKey = null; int signatureIndex = -1; @@ -1178,12 +1217,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> private static byte[] getLineSeparator() { String nl = System.getProperty("line.separator"); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; + return nl.getBytes(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index a018815f3..c82cbce8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -22,6 +22,7 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.S2K; import org.spongycastle.bcpg.sig.Features; import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.bcpg.sig.RevocationReasonTags; import org.spongycastle.jce.spec.ElGamalParameterSpec; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyFlags; @@ -469,7 +470,7 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase( masterSecretKey.getKeyID(), masterSecretKey.getKeyID(), - cryptoInput.getSignatureTime())); + cryptoInput.getSignatureTime()), cryptoInput); } // read masterKeyFlags, and use the same as before. @@ -897,18 +898,35 @@ public class PgpKeyOperation { pKey = PGPPublicKey.removeCertification(pKey, sig); } - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - cryptoInput.getPassphrase().getCharArray()); - PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); - PGPSignature sig = generateSubkeyBindingSignature( - getSignatureGenerator(masterSecretKey, cryptoInput), - cryptoInput.getSignatureTime(), - masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry); + PGPPrivateKey subPrivateKey; + if (!isDivertToCard(sKey)) { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + cryptoInput.getPassphrase().getCharArray()); + subPrivateKey = sKey.extractPrivateKey(keyDecryptor); + // super special case: subkey is allowed to sign, but isn't available + if (subPrivateKey == null) { + log.add(LogType.MSG_MF_ERROR_SUB_STRIPPED, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } else { + subPrivateKey = null; + } + try { + PGPSignature sig = generateSubkeyBindingSignature( + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey, + getSignatureGenerator(sKey, cryptoInput), subPrivateKey, + pKey, flags, expiry); + + // generate and add new signature + pKey = PGPPublicKey.addCertification(pKey, sig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } - // generate and add new signature - pKey = PGPPublicKey.addCertification(pKey, sig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); } subProgressPop(); @@ -959,6 +977,11 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_SUBKEY_NEW, indent, KeyFormattingUtils.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) ); + if (isDivertToCard(masterSecretKey)) { + log.add(LogType.MSG_MF_ERROR_DIVERT_NEWSUB, indent +1); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + if (add.mExpiry == null) { log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -987,7 +1010,8 @@ public class PgpKeyOperation { PGPSignature cert = generateSubkeyBindingSignature( getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(), - masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, + masterPublicKey, masterPrivateKey, + getSignatureGenerator(pKey, cryptoInput, false), keyPair.getPrivateKey(), pKey, add.mFlags, add.mExpiry); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); } catch (NfcInteractionNeeded e) { @@ -1039,6 +1063,26 @@ public class PgpKeyOperation { indent -= 1; } + // 7. if requested, change PIN and/or Admin PIN on card + if (saveParcel.mCardPin != null) { + progress(R.string.progress_modify_pin, 90); + log.add(LogType.MSG_MF_PIN, indent); + indent += 1; + + nfcKeyToCardOps.setPin(saveParcel.mCardPin); + + indent -= 1; + } + if (saveParcel.mCardAdminPin != null) { + progress(R.string.progress_modify_admin_pin, 90); + log.add(LogType.MSG_MF_ADMIN_PIN, indent); + indent += 1; + + nfcKeyToCardOps.setAdminPin(saveParcel.mCardAdminPin); + + indent -= 1; + } + } catch (IOException e) { Log.e(Constants.TAG, "encountered IOException while modifying key", e); log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1); @@ -1062,12 +1106,12 @@ public class PgpKeyOperation { if (!nfcSignOps.isEmpty()) { log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent); - return new PgpEditKeyResult(log, nfcSignOps.build()); + return new PgpEditKeyResult(log, nfcSignOps.build(), cryptoInput); } if (!nfcKeyToCardOps.isEmpty()) { log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent); - return new PgpEditKeyResult(log, nfcKeyToCardOps.build()); + return new PgpEditKeyResult(log, nfcKeyToCardOps.build(), cryptoInput); } log.add(LogType.MSG_MF_SUCCESS, indent); @@ -1382,22 +1426,27 @@ public class PgpKeyOperation { static PGPSignatureGenerator getSignatureGenerator( PGPSecretKey secretKey, CryptoInputParcel cryptoInput) { - PGPContentSignerBuilder builder; - S2K s2k = secretKey.getS2K(); - if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K - && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) { + boolean isDivertToCard = s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K + && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD; + + return getSignatureGenerator(secretKey.getPublicKey(), cryptoInput, isDivertToCard); + } + + static PGPSignatureGenerator getSignatureGenerator( + PGPPublicKey pKey, CryptoInputParcel cryptoInput, boolean divertToCard) { + + PGPContentSignerBuilder builder; + if (divertToCard) { // use synchronous "NFC based" SignerBuilder builder = new NfcSyncPGPContentSignerBuilder( - secretKey.getPublicKey().getAlgorithm(), - PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO, - secretKey.getKeyID(), cryptoInput.getCryptoData()) + pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO, + pKey.getKeyID(), cryptoInput.getCryptoData()) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); } else { // content signer based on signing key algorithm and chosen hash algorithm builder = new JcaPGPContentSignerBuilder( - secretKey.getPublicKey().getAlgorithm(), - PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) + pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); } @@ -1477,6 +1526,9 @@ public class PgpKeyOperation { throws IOException, PGPException, SignatureException { PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + // we use the tag NO_REASON since gnupg does not care about the tag while verifying + // signatures with a revoked key, the warning is the same + subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, ""); subHashedPacketsGen.setSignatureCreationTime(true, creationTime); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); @@ -1489,6 +1541,9 @@ public class PgpKeyOperation { throws IOException, PGPException, SignatureException { PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + // we use the tag NO_REASON since gnupg does not care about the tag while verifying + // signatures with a revoked key, the warning is the same + subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, ""); subHashedPacketsGen.setSignatureCreationTime(true, creationTime); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); // Generate key revocation or subkey revocation, depending on master/subkey-ness @@ -1504,7 +1559,8 @@ public class PgpKeyOperation { static PGPSignature generateSubkeyBindingSignature( PGPSignatureGenerator sGen, Date creationTime, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, - PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry) + PGPSignatureGenerator subSigGen, PGPPrivateKey subPrivateKey, PGPPublicKey pKey, + int flags, long expiry) throws IOException, PGPException, SignatureException { PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); @@ -1514,10 +1570,6 @@ public class PgpKeyOperation { // cross-certify signing keys PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); subHashedPacketsGen.setSignatureCreationTime(false, creationTime); - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder); subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); subSigGen.setHashedSubpackets(subHashedPacketsGen.generate()); PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 89bdf1c89..8fb41a909 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -20,6 +20,8 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; +import android.os.Parcelable; +import android.support.annotation.NonNull; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; @@ -36,11 +38,11 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; +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.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -63,6 +65,7 @@ import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -99,6 +102,13 @@ public class PgpSignEncryptOperation extends BaseOperation { super(context, providerHelper, progressable); } + @NonNull + @Override + // TODO this is horrible, refactor ASAP!! + public OperationResult execute(Parcelable input, CryptoInputParcel cryptoInput) { + return null; + } + /** * Signs and/or encrypts data based on parameters of class */ @@ -189,7 +199,7 @@ public class PgpSignEncryptOperation extends BaseOperation { log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase( signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), - cryptoInput.getSignatureTime())); + cryptoInput.getSignatureTime()), cryptoInput); } if (!signingKey.unlock(localPassphrase)) { log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); @@ -263,15 +273,19 @@ public class PgpSignEncryptOperation extends BaseOperation { try { CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing( KeyRings.buildUnifiedKeyRingUri(id)); - CanonicalizedPublicKey key = keyRing.getEncryptionSubKey(); - cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients())); - log.add(LogType.MSG_PSE_KEY_OK, indent + 1, - KeyFormattingUtils.convertKeyIdToHex(id)); - } catch (PgpKeyNotFoundException e) { - log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, - KeyFormattingUtils.convertKeyIdToHex(id)); - if (input.isFailOnMissingEncryptionKeyIds()) { - return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + Set<Long> encryptSubKeyIds = keyRing.getEncryptIds(); + for (Long subKeyId : encryptSubKeyIds) { + CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId); + cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients())); + log.add(LogType.MSG_PSE_KEY_OK, indent + 1, + KeyFormattingUtils.convertKeyIdToHex(subKeyId)); + } + if (encryptSubKeyIds.isEmpty()) { + log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, + KeyFormattingUtils.convertKeyIdToHex(id)); + if (input.isFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } } } catch (ProviderHelper.NotFoundException e) { log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, @@ -498,7 +512,7 @@ public class PgpSignEncryptOperation extends BaseOperation { log.add(LogType.MSG_PSE_PENDING_NFC, indent); return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation( signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), - e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime())); + e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 7be61d9c8..a7baddf8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -48,6 +48,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -78,7 +79,7 @@ import java.util.TreeSet; * */ @SuppressWarnings("unchecked") -public class UncachedKeyRing { +public class UncachedKeyRing implements Serializable { final PGPKeyRing mRing; final boolean mIsSecret; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 3346926ec..4d16d44c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -53,7 +53,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 11; static Boolean apgHack = false; private Context mContext; @@ -274,6 +274,14 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_CERTS); case 10: // do nothing here, just consolidate + case 11: + // fix problems in database, see #1402 for details + // https://github.com/open-keychain/open-keychain/issues/1402 + db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3"); + if (oldVersion == 10) { + // no consolidate if we are updating from 10, we're just here for the api_accounts fix + return; + } } @@ -297,10 +305,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { // It's the Java way =( String[] dbs = context.databaseList(); for (String db : dbs) { - if (db.equals("apg.db")) { + if ("apg.db".equals(db)) { hasApgDb = true; - } else if (db.equals("apg_old.db")) { + } else if ("apg_old.db".equals(db)) { Log.d(Constants.TAG, "Found apg_old.db, delete it!"); + // noinspection ResultOfMethodCallIgnored - if it doesn't happen, it doesn't happen. context.getDatabasePath("apg_old.db").delete(); } } @@ -384,7 +393,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { } } - // delete old database + // noinspection ResultOfMethodCallIgnored - not much we can do if this doesn't work context.getDatabasePath("apg.db").delete(); } @@ -416,6 +425,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { } else { in = context.getDatabasePath(DATABASE_NAME); out = context.getDatabasePath("debug_backup.db"); + // noinspection ResultOfMethodCallIgnored - this is a pure debug feature, anyways out.createNewFile(); } if (!in.canRead()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 590c58f97..ed17de4ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -26,6 +26,7 @@ import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; +import android.support.annotation.NonNull; import android.support.v4.util.LongSparseArray; import org.spongycastle.bcpg.CompressionAlgorithmTags; @@ -725,7 +726,7 @@ public class ProviderHelper { LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>(); @Override - public int compareTo(UserPacketItem o) { + public int compareTo(@NonNull UserPacketItem o) { // revoked keys always come last! //noinspection DoubleNegation if ((selfRevocation != null) != (o.selfRevocation != null)) { @@ -906,7 +907,8 @@ public class ProviderHelper { // If there is a secret key, merge new data (if any) and save the key for later CanonicalizedSecretKeyRing canSecretRing; try { - UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing(); + UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()) + .getUncachedKeyRing(); // Merge data from new public ring into secret one log(LogType.MSG_IP_MERGE_SECRET); @@ -1031,7 +1033,8 @@ public class ProviderHelper { publicRing = secretRing.extractPublicKeyRing(); } - CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); + CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, + mIndent); if (canPublicRing == null) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } @@ -1057,6 +1060,7 @@ public class ProviderHelper { } + @NonNull public ConsolidateResult consolidateDatabaseStep1(Progressable progress) { OperationLog log = new OperationLog(); @@ -1082,7 +1086,7 @@ public class ProviderHelper { indent += 1; final Cursor cursor = mContentResolver.query(KeyRingData.buildSecretKeyRingUri(), - new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null); + new String[]{KeyRingData.KEY_RING_DATA}, null, null, null); if (cursor == null) { log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1124,6 +1128,7 @@ public class ProviderHelper { } }); + cursor.close(); } catch (IOException e) { Log.e(Constants.TAG, "error saving secret", e); @@ -1143,7 +1148,7 @@ public class ProviderHelper { final Cursor cursor = mContentResolver.query( KeyRingData.buildPublicKeyRingUri(), - new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null); + new String[]{KeyRingData.KEY_RING_DATA}, null, null, null); if (cursor == null) { log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1185,6 +1190,7 @@ public class ProviderHelper { } }); + cursor.close(); } catch (IOException e) { Log.e(Constants.TAG, "error saving public", e); @@ -1200,12 +1206,14 @@ public class ProviderHelper { return consolidateDatabaseStep2(log, indent, progress, false); } + @NonNull public ConsolidateResult consolidateDatabaseStep2(Progressable progress) { return consolidateDatabaseStep2(new OperationLog(), 0, progress, true); } private static boolean mConsolidateCritical = false; + @NonNull private ConsolidateResult consolidateDatabaseStep2( OperationLog log, int indent, Progressable progress, boolean recovery) { @@ -1250,7 +1258,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itSecrets, numSecrets, null); + .serialKeyRingImport(itSecrets, numSecrets, null, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); @@ -1278,7 +1286,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itPublics, numPublics, null); + .serialKeyRingImport(itPublics, numPublics, null, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent); @@ -1414,7 +1422,7 @@ public class ProviderHelper { private ContentValues contentValueForApiApps(AppSettings appSettings) { ContentValues values = new ContentValues(); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); - values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature()); + values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); return values; } @@ -1460,7 +1468,7 @@ public class ProviderHelper { settings = new AppSettings(); settings.setPackageName(cursor.getString( cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); - settings.setPackageSignature(cursor.getBlob( + settings.setPackageCertificate(cursor.getBlob( cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE))); } } finally { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java index a3f9f84c9..498601769 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java @@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.remote; public class AppSettings { private String mPackageName; - private byte[] mPackageSignature; + private byte[] mPackageCertificate; public AppSettings() { @@ -28,7 +28,7 @@ public class AppSettings { public AppSettings(String packageName, byte[] packageSignature) { super(); this.mPackageName = packageName; - this.mPackageSignature = packageSignature; + this.mPackageCertificate = packageSignature; } public String getPackageName() { @@ -39,12 +39,12 @@ public class AppSettings { this.mPackageName = packageName; } - public byte[] getPackageSignature() { - return mPackageSignature; + public byte[] getPackageCertificate() { + return mPackageCertificate; } - public void setPackageSignature(byte[] packageSignature) { - this.mPackageSignature = packageSignature; + public void setPackageCertificate(byte[] packageCertificate) { + this.mPackageCertificate = packageCertificate; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index ac66bd097..2568d68b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -64,6 +64,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; public class OpenPgpService extends RemoteService { @@ -88,8 +89,8 @@ public class OpenPgpService extends RemoteService { boolean duplicateUserIdsCheck = false; ArrayList<Long> keyIds = new ArrayList<>(); - ArrayList<String> missingUserIds = new ArrayList<>(); - ArrayList<String> duplicateUserIds = new ArrayList<>(); + ArrayList<String> missingEmails = new ArrayList<>(); + ArrayList<String> duplicateEmails = new ArrayList<>(); if (!noUserIdsCheck) { for (String email : encryptionUserIds) { // try to find the key for this specific email @@ -102,13 +103,13 @@ public class OpenPgpService extends RemoteService { keyIds.add(id); } else { missingUserIdsCheck = true; - missingUserIds.add(email); + missingEmails.add(email); Log.d(Constants.TAG, "user id missing"); } - // another entry for this email -> too keys with the same email inside user id + // another entry for this email -> two keys with the same email inside user id if (cursor != null && cursor.moveToNext()) { duplicateUserIdsCheck = true; - duplicateUserIds.add(email); + duplicateEmails.add(email); // also pre-select long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); @@ -136,8 +137,8 @@ public class OpenPgpService extends RemoteService { intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS); intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); intent.putExtra(RemoteServiceActivity.EXTRA_NO_USER_IDS_CHECK, noUserIdsCheck); - intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); - intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_USER_IDS, duplicateUserIds); + intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_EMAILS, missingEmails); + intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_EMAILS, duplicateEmails); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, @@ -164,7 +165,9 @@ public class OpenPgpService extends RemoteService { } private static PendingIntent getRequiredInputPendingIntent(Context context, - Intent data, RequiredInputParcel requiredInput) { + Intent data, + RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInput) { switch (requiredInput.mType) { case NFC_MOVE_KEY_TO_CARD: @@ -175,6 +178,7 @@ public class OpenPgpService extends RemoteService { // pass params through to activity that it can be returned again later to repeat pgp operation intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data); intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); + intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInput); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); } @@ -185,6 +189,7 @@ public class OpenPgpService extends RemoteService { // pass params through to activity that it can be returned again later to repeat pgp operation intent.putExtra(PassphraseDialogActivity.EXTRA_SERVICE_INTENT, data); intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); + intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInput); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); } @@ -279,12 +284,12 @@ public class OpenPgpService extends RemoteService { CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data); if (inputParcel == null) { - inputParcel = new CryptoInputParcel(); + inputParcel = new CryptoInputParcel(new Date()); } // override passphrase in input parcel if given by API call if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(), - new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE))); + inputParcel.mPassphrase = + new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); } // execute PGP operation! @@ -294,7 +299,8 @@ public class OpenPgpService extends RemoteService { if (pgpResult.isPending()) { RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel(); - PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput); + PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, + requiredInput, pgpResult.mCryptoInputParcel); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -434,12 +440,12 @@ public class OpenPgpService extends RemoteService { CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data); if (inputParcel == null) { - inputParcel = new CryptoInputParcel(); + inputParcel = new CryptoInputParcel(new Date()); } // override passphrase in input parcel if given by API call if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(), - new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE))); + inputParcel.mPassphrase = + new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); } PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); @@ -449,7 +455,8 @@ public class OpenPgpService extends RemoteService { if (pgpResult.isPending()) { RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel(); - PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput); + PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, + requiredInput, pgpResult.mCryptoInputParcel); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -519,8 +526,8 @@ public class OpenPgpService extends RemoteService { } // override passphrase in input parcel if given by API call if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { - cryptoInput = new CryptoInputParcel(cryptoInput.getSignatureTime(), - new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE))); + cryptoInput.mPassphrase = + new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); } byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); @@ -543,7 +550,8 @@ public class OpenPgpService extends RemoteService { if (pgpResult.isPending()) { // prepare and return PendingIntent to be executed by client RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel(); - PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput); + PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, + requiredInput, pgpResult.mCryptoInputParcel); Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index d25249b14..4eee73e01 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -75,7 +75,7 @@ public class AppSettingsActivity extends BaseActivity { mAppNameView = (TextView) findViewById(R.id.api_app_settings_app_name); mAppIconView = (ImageView) findViewById(R.id.api_app_settings_app_icon); mPackageName = (TextView) findViewById(R.id.api_app_settings_package_name); - mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_signature); + mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_certificate); mStartFab = (FloatingActionButton) findViewById(R.id.fab); mStartFab.setOnClickListener(new View.OnClickListener() { @@ -148,19 +148,19 @@ public class AppSettingsActivity extends BaseActivity { } private void showAdvancedInfo() { - String signature = null; - // advanced info: package signature SHA-256 + String certificate = null; + // advanced info: package certificate SHA-256 try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(mAppSettings.getPackageSignature()); + md.update(mAppSettings.getPackageCertificate()); byte[] digest = md.digest(); - signature = new String(Hex.encode(digest)); + certificate = new String(Hex.encode(digest)); } catch (NoSuchAlgorithmException e) { Log.e(Constants.TAG, "Should not happen!", e); } AdvancedAppSettingsDialogFragment dialogFragment = - AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), signature); + AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate); dialogFragment.show(getSupportFragmentManager(), "advancedDialog"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java index 7beac8973..9160987ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java @@ -47,7 +47,7 @@ public class AppSettingsHeaderFragment extends Fragment { private TextView mAppNameView; private ImageView mAppIconView; private TextView mPackageName; - private TextView mPackageSignature; + private TextView mPackageCertificate; public AppSettings getAppSettings() { return mAppSettings; @@ -67,7 +67,7 @@ public class AppSettingsHeaderFragment extends Fragment { mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name); mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon); mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name); - mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature); + mPackageCertificate = (TextView) view.findViewById(R.id.api_app_settings_package_certificate); return view; } @@ -94,11 +94,11 @@ public class AppSettingsHeaderFragment extends Fragment { // advanced info: package signature SHA-256 try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(appSettings.getPackageSignature()); + md.update(appSettings.getPackageCertificate()); byte[] digest = md.digest(); String signature = new String(Hex.encode(digest)); - mPackageSignature.setText(signature); + mPackageCertificate.setText(signature); } catch (NoSuchAlgorithmException e) { Log.e(Constants.TAG, "Should not happen!", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index 5facde64f..a2e781e8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -69,8 +69,8 @@ public class RemoteServiceActivity extends BaseActivity { public static final String EXTRA_ACC_NAME = "acc_name"; // select pub keys action public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; - public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; - public static final String EXTRA_DUPLICATE_USER_IDS = "dublicate_user_ids"; + public static final String EXTRA_MISSING_EMAILS = "missing_emails"; + public static final String EXTRA_DUPLICATE_EMAILS = "dublicate_emails"; public static final String EXTRA_NO_USER_IDS_CHECK = "no_user_ids"; // error message public static final String EXTRA_ERROR_MESSAGE = "error_message"; @@ -222,10 +222,10 @@ public class RemoteServiceActivity extends BaseActivity { case ACTION_SELECT_PUB_KEYS: { long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true); - ArrayList<String> missingUserIds = intent - .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); - ArrayList<String> dublicateUserIds = intent - .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS); + ArrayList<String> missingEmails = intent + .getStringArrayListExtra(EXTRA_MISSING_EMAILS); + ArrayList<String> duplicateEmails = intent + .getStringArrayListExtra(EXTRA_DUPLICATE_EMAILS); SpannableStringBuilder ssb = new SpannableStringBuilder(); final SpannableString textIntro = new SpannableString( @@ -235,23 +235,23 @@ public class RemoteServiceActivity extends BaseActivity { textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append(textIntro); - if (missingUserIds != null && missingUserIds.size() > 0) { + if (missingEmails != null && missingEmails.size() > 0) { ssb.append("\n\n"); ssb.append(getString(R.string.api_select_pub_keys_missing_text)); ssb.append("\n"); - for (String userId : missingUserIds) { - SpannableString ss = new SpannableString(userId + "\n"); + for (String emails : missingEmails) { + SpannableString ss = new SpannableString(emails + "\n"); ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append(ss); } } - if (dublicateUserIds != null && dublicateUserIds.size() > 0) { + if (duplicateEmails != null && duplicateEmails.size() > 0) { ssb.append("\n\n"); ssb.append(getString(R.string.api_select_pub_keys_dublicates_text)); ssb.append("\n"); - for (String userId : dublicateUserIds) { - SpannableString ss = new SpannableString(userId + "\n"); + for (String email : duplicateEmails) { + SpannableString ss = new SpannableString(email + "\n"); ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append(ss); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index a11f81658..e76dcfb49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -22,13 +22,10 @@ import android.os.Parcel; import android.os.Parcelable; import java.io.Serializable; -import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Date; -import java.util.Map; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; /** @@ -54,6 +51,7 @@ public class CertifyActionsParcel implements Parcelable { mMasterKeyId = source.readLong(); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; + keyServerUri = source.readString(); mCertifyActions = (ArrayList<CertifyAction>) source.readSerializable(); } @@ -66,6 +64,7 @@ public class CertifyActionsParcel implements Parcelable { public void writeToParcel(Parcel destination, int flags) { destination.writeLong(mMasterKeyId); destination.writeInt(mLevel.ordinal()); + destination.writeString(keyServerUri); destination.writeSerializable(mCertifyActions); } @@ -94,7 +93,7 @@ public class CertifyActionsParcel implements Parcelable { } public CertifyAction(long masterKeyId, ArrayList<String> userIds, - ArrayList<WrappedUserAttribute> attributes) { + ArrayList<WrappedUserAttribute> attributes) { mMasterKeyId = masterKeyId; mUserIds = userIds; mUserAttributes = attributes; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java index 41ff6d02b..18bc3cc9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -83,7 +83,7 @@ public class DummyAccountService extends Service { public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { response.onResult(new Bundle()); - toaster.toast(R.string.info_no_manual_account_creation); + toaster.toast(R.string.account_no_manual_account_creation); Log.d(Constants.TAG, "DummyAccountService.addAccount"); return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java index ef5b48df3..24c002bbd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java @@ -23,9 +23,12 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; + public class ExportKeyringParcel implements Parcelable { public String mKeyserver; public Uri mCanonicalizedPublicKeyringUri; + public UncachedKeyRing mUncachedKeyRing; public boolean mExportSecret; public long mMasterKeyIds[]; @@ -45,6 +48,12 @@ public class ExportKeyringParcel implements Parcelable { mCanonicalizedPublicKeyringUri = keyringUri; } + public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) { + mExportType = ExportType.UPLOAD_KEYSERVER; + mKeyserver = keyserver; + mUncachedKeyRing = uncachedKeyRing; + } + public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) { mExportType = ExportType.EXPORT_FILE; mMasterKeyIds = masterKeyIds; @@ -52,6 +61,7 @@ public class ExportKeyringParcel implements Parcelable { mOutputFile = outputFile; } + @SuppressWarnings("unused") // TODO: is it used? public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { mExportType = ExportType.EXPORT_URI; mMasterKeyIds = masterKeyIds; @@ -62,6 +72,7 @@ public class ExportKeyringParcel implements Parcelable { protected ExportKeyringParcel(Parcel in) { mKeyserver = in.readString(); mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader()); + mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader()); mExportSecret = in.readByte() != 0x00; mOutputFile = in.readString(); mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader()); @@ -78,6 +89,7 @@ public class ExportKeyringParcel implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mKeyserver); dest.writeValue(mCanonicalizedPublicKeyringUri); + dest.writeValue(mUncachedKeyRing); dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00)); dest.writeString(mOutputFile); dest.writeValue(mOutputUri); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index 1cd76b462..dca2a08c2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.ExportOperation; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation; import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; +import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; @@ -114,6 +115,8 @@ public class KeychainService extends Service implements Progressable { } else if (inputParcel instanceof SaveKeyringParcel) { op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); + } else if (inputParcel instanceof RevokeKeyringParcel) { + op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof CertifyActionsParcel) { op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); @@ -135,7 +138,7 @@ public class KeychainService extends Service implements Progressable { op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else { - return; + throw new AssertionError("Unrecognized input parcel in KeychainService!"); } @SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 7c0b7eaef..2a258b7e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -232,21 +232,21 @@ public class PassphraseCacheService extends Service { * Internal implementation to get cached passphrase. */ private Passphrase getCachedPassphraseImpl(long masterKeyId, long subKeyId) throws ProviderHelper.NotFoundException { + // on "none" key, just do nothing + if (masterKeyId == Constants.key.none) { + return null; + } + // passphrase for symmetric encryption? if (masterKeyId == Constants.key.symmetric) { Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption"); - Passphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase(); + CachedPassphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric); if (cachedPassphrase == null) { return null; } addCachedPassphrase(this, Constants.key.symmetric, Constants.key.symmetric, - cachedPassphrase, getString(R.string.passp_cache_notif_pwd)); - return cachedPassphrase; - } - - // on "none" key, just do nothing - if (masterKeyId == Constants.key.none) { - return null; + cachedPassphrase.getPassphrase(), getString(R.string.passp_cache_notif_pwd)); + return cachedPassphrase.getPassphrase(); } // try to get master key id which is used as an identifier for cached passphrases diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java new file mode 100644 index 000000000..02e8fda46 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class RevokeKeyringParcel implements Parcelable { + + final public long mMasterKeyId; + final public boolean mUpload; + final public String mKeyserver; + + public RevokeKeyringParcel(long masterKeyId, boolean upload, String keyserver) { + mMasterKeyId = masterKeyId; + mUpload = upload; + mKeyserver = keyserver; + } + + protected RevokeKeyringParcel(Parcel in) { + mMasterKeyId = in.readLong(); + mUpload = in.readByte() != 0x00; + mKeyserver = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mMasterKeyId); + dest.writeByte((byte) (mUpload ? 0x01 : 0x00)); + dest.writeString(mKeyserver); + } + + public static final Parcelable.Creator<RevokeKeyringParcel> CREATOR = new Parcelable.Creator<RevokeKeyringParcel>() { + @Override + public RevokeKeyringParcel createFromParcel(Parcel in) { + return new RevokeKeyringParcel(in); + } + + @Override + public RevokeKeyringParcel[] newArray(int size) { + return new RevokeKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index e2c4dc542..6959fca56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -61,6 +61,15 @@ public class SaveKeyringParcel implements Parcelable { public ArrayList<String> mRevokeUserIds; public ArrayList<Long> mRevokeSubKeys; + // if these are non-null, PINs will be changed on the card + public Passphrase mCardPin; + public Passphrase mCardAdminPin; + + // private because they have to be set together with setUpdateOptions + private boolean mUpload; + private boolean mUploadAtomic; + private String mKeyserver; + public SaveKeyringParcel() { reset(); } @@ -80,6 +89,29 @@ public class SaveKeyringParcel implements Parcelable { mChangeSubKeys = new ArrayList<>(); mRevokeUserIds = new ArrayList<>(); mRevokeSubKeys = new ArrayList<>(); + mCardPin = null; + mCardAdminPin = null; + mUpload = false; + mUploadAtomic = false; + mKeyserver = null; + } + + public void setUpdateOptions(boolean upload, boolean uploadAtomic, String keysever) { + mUpload = upload; + mUploadAtomic = uploadAtomic; + mKeyserver = keysever; + } + + public boolean isUpload() { + return mUpload; + } + + public boolean isUploadAtomic() { + return mUploadAtomic; + } + + public String getUploadKeyserver() { + return mKeyserver; } public boolean isEmpty() { @@ -210,6 +242,7 @@ public class SaveKeyringParcel implements Parcelable { } } + @SuppressWarnings("unchecked") // we verify the reads against writes in writeToParcel public SaveKeyringParcel(Parcel source) { mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; mFingerprint = source.createByteArray(); @@ -225,6 +258,13 @@ public class SaveKeyringParcel implements Parcelable { mRevokeUserIds = source.createStringArrayList(); mRevokeSubKeys = (ArrayList<Long>) source.readSerializable(); + + mCardPin = source.readParcelable(Passphrase.class.getClassLoader()); + mCardAdminPin = source.readParcelable(Passphrase.class.getClassLoader()); + + mUpload = source.readByte() != 0; + mUploadAtomic = source.readByte() != 0; + mKeyserver = source.readString(); } @Override @@ -236,7 +276,7 @@ public class SaveKeyringParcel implements Parcelable { destination.writeByteArray(mFingerprint); // yes, null values are ok for parcelables - destination.writeParcelable(mNewUnlock, 0); + destination.writeParcelable(mNewUnlock, flags); destination.writeStringList(mAddUserIds); destination.writeSerializable(mAddUserAttribute); @@ -247,6 +287,13 @@ public class SaveKeyringParcel implements Parcelable { destination.writeStringList(mRevokeUserIds); destination.writeSerializable(mRevokeSubKeys); + + destination.writeParcelable(mCardPin, flags); + destination.writeParcelable(mCardAdminPin, flags); + + destination.writeByte((byte) (mUpload ? 1 : 0)); + destination.writeByte((byte) (mUploadAtomic ? 1 : 0)); + destination.writeString(mKeyserver); } public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() { @@ -274,7 +321,9 @@ public class SaveKeyringParcel implements Parcelable { out += "mChangeSubKeys: " + mChangeSubKeys + "\n"; out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; out += "mRevokeUserIds: " + mRevokeUserIds + "\n"; - out += "mRevokeSubKeys: " + mRevokeSubKeys; + out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n"; + out += "mCardPin: " + mCardPin + "\n"; + out += "mCardAdminPin: " + mCardAdminPin; return out; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index ee7caf2d8..0d8569fe6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -17,52 +17,87 @@ package org.sufficientlysecure.keychain.service.input; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Passphrase; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + /** * This is a base class for the input of crypto operations. */ public class CryptoInputParcel implements Parcelable { - final Date mSignatureTime; - final Passphrase mPassphrase; + private Date mSignatureTime; + private boolean mHasSignature; + + public Passphrase mPassphrase; + // used to supply an explicit proxy to operations that require it + // this is not final so it can be added to an existing CryptoInputParcel + // (e.g) CertifyOperation with upload might require both passphrase and orbot to be enabled + private ParcelableProxy mParcelableProxy; + + // specifies whether passphrases should be cached + public boolean mCachePassphrase = true; // this map contains both decrypted session keys and signed hashes to be // used in the crypto operation described by this parcel. private HashMap<ByteBuffer, byte[]> mCryptoData = new HashMap<>(); public CryptoInputParcel() { - mSignatureTime = new Date(); + mSignatureTime = null; mPassphrase = null; + mCachePassphrase = true; } public CryptoInputParcel(Date signatureTime, Passphrase passphrase) { + mHasSignature = true; mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = passphrase; + mCachePassphrase = true; } public CryptoInputParcel(Passphrase passphrase) { - mSignatureTime = new Date(); mPassphrase = passphrase; + mCachePassphrase = true; } public CryptoInputParcel(Date signatureTime) { + mHasSignature = true; + mSignatureTime = signatureTime == null ? new Date() : signatureTime; + mPassphrase = null; + mCachePassphrase = true; + } + + public CryptoInputParcel(ParcelableProxy parcelableProxy) { + this(); + mParcelableProxy = parcelableProxy; + } + + public CryptoInputParcel(Date signatureTime, boolean cachePassphrase) { + mHasSignature = true; mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = null; + mCachePassphrase = cachePassphrase; + } + + public CryptoInputParcel(boolean cachePassphrase) { + mCachePassphrase = cachePassphrase; } protected CryptoInputParcel(Parcel source) { - mSignatureTime = new Date(source.readLong()); + mHasSignature = source.readByte() != 0; + if (mHasSignature) { + mSignatureTime = new Date(source.readLong()); + } mPassphrase = source.readParcelable(getClass().getClassLoader()); + mParcelableProxy = source.readParcelable(getClass().getClassLoader()); + mCachePassphrase = source.readByte() != 0; { int count = source.readInt(); @@ -83,8 +118,13 @@ public class CryptoInputParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mSignatureTime.getTime()); + dest.writeByte((byte) (mHasSignature ? 1 : 0)); + if (mHasSignature) { + dest.writeLong(mSignatureTime.getTime()); + } dest.writeParcelable(mPassphrase, 0); + dest.writeParcelable(mParcelableProxy, 0); + dest.writeByte((byte) (mCachePassphrase ? 1 : 0)); dest.writeInt(mCryptoData.size()); for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) { @@ -93,6 +133,14 @@ public class CryptoInputParcel implements Parcelable { } } + public void addParcelableProxy(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } + + public void addSignatureTime(Date signatureTime) { + mSignatureTime = signatureTime; + } + public void addCryptoData(byte[] hash, byte[] signedHash) { mCryptoData.put(ByteBuffer.wrap(hash), signedHash); } @@ -101,6 +149,10 @@ public class CryptoInputParcel implements Parcelable { mCryptoData.putAll(cachedSessionKeys); } + public ParcelableProxy getParcelableProxy() { + return mParcelableProxy; + } + public Map<ByteBuffer, byte[]> getCryptoData() { return mCryptoData; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index efe844145..e4dac3227 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -1,34 +1,37 @@ package org.sufficientlysecure.keychain.service.input; +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.util.Passphrase; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import android.os.Parcel; -import android.os.Parcelable; - public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD + PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT, + UPLOAD_FAIL_RETRY } public Date mSignatureTime; public final RequiredInputType mType; - public final byte[][] mInputHashes; + public final byte[][] mInputData; public final int[] mSignAlgos; private Long mMasterKeyId; private Long mSubKeyId; - private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, + private RequiredInputParcel(RequiredInputType type, byte[][] inputData, int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { mType = type; - mInputHashes = inputHashes; + mInputData = inputData; mSignAlgos = signAlgos; mSignatureTime = signatureTime; mMasterKeyId = masterKeyId; @@ -38,25 +41,25 @@ public class RequiredInputParcel implements Parcelable { public RequiredInputParcel(Parcel source) { mType = RequiredInputType.values()[source.readInt()]; - // 0 = none, 1 = both, 2 = only hashes (decrypt) - int hashTypes = source.readInt(); - if (hashTypes != 0) { + // 0 = none, 1 = signAlgos + inputData, 2 = only inputData (decrypt) + int inputDataType = source.readInt(); + if (inputDataType != 0) { int count = source.readInt(); - mInputHashes = new byte[count][]; - if (hashTypes == 1) { + mInputData = new byte[count][]; + if (inputDataType == 1) { mSignAlgos = new int[count]; for (int i = 0; i < count; i++) { - mInputHashes[i] = source.createByteArray(); + mInputData[i] = source.createByteArray(); mSignAlgos[i] = source.readInt(); } } else { mSignAlgos = null; for (int i = 0; i < count; i++) { - mInputHashes[i] = source.createByteArray(); + mInputData[i] = source.createByteArray(); } } } else { - mInputHashes = null; + mInputData = null; mSignAlgos = null; } @@ -74,6 +77,15 @@ public class RequiredInputParcel implements Parcelable { return mSubKeyId; } + public static RequiredInputParcel createRetryUploadOperation() { + return new RequiredInputParcel(RequiredInputType.UPLOAD_FAIL_RETRY, + null, null, null, 0L, 0L); + } + + public static RequiredInputParcel createOrbotRequiredOperation() { + return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L); + } + public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { @@ -83,9 +95,9 @@ public class RequiredInputParcel implements Parcelable { } public static RequiredInputParcel createNfcDecryptOperation( - long masterKeyId, long subKeyId, byte[] inputHash) { + long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, - new byte[][] { inputHash }, null, null, masterKeyId, subKeyId); + new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createRequiredSignPassphrase( @@ -119,11 +131,11 @@ public class RequiredInputParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType.ordinal()); - if (mInputHashes != null) { + if (mInputData != null) { dest.writeInt(mSignAlgos != null ? 1 : 2); - dest.writeInt(mInputHashes.length); - for (int i = 0; i < mInputHashes.length; i++) { - dest.writeByteArray(mInputHashes[i]); + dest.writeInt(mInputData.length); + for (int i = 0; i < mInputData.length; i++) { + dest.writeByteArray(mInputData[i]); if (mSignAlgos != null) { dest.writeInt(mSignAlgos[i]); } @@ -200,7 +212,7 @@ public class RequiredInputParcel implements Parcelable { throw new AssertionError("operation types must match, this is a progrmming error!"); } - Collections.addAll(mInputHashes, input.mInputHashes); + Collections.addAll(mInputHashes, input.mInputData); for (int signAlgo : input.mSignAlgos) { mSignAlgos.add(signAlgo); } @@ -215,19 +227,31 @@ public class RequiredInputParcel implements Parcelable { public static class NfcKeyToCardOperationsBuilder { ArrayList<byte[]> mSubkeysToExport = new ArrayList<>(); Long mMasterKeyId; + byte[] mPin; + byte[] mAdminPin; public NfcKeyToCardOperationsBuilder(Long masterKeyId) { mMasterKeyId = masterKeyId; } public RequiredInputParcel build() { - byte[][] inputHashes = new byte[mSubkeysToExport.size()][]; - mSubkeysToExport.toArray(inputHashes); + byte[][] inputData = new byte[mSubkeysToExport.size() + 2][]; + + // encode all subkeys into inputData + byte[][] subkeyData = new byte[mSubkeysToExport.size()][]; + mSubkeysToExport.toArray(subkeyData); + + // first two are PINs + inputData[0] = mPin; + inputData[1] = mAdminPin; + // then subkeys + System.arraycopy(subkeyData, 0, inputData, 2, subkeyData.length); + ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD, - inputHashes, null, null, mMasterKeyId, buf.getLong()); + inputData, null, null, mMasterKeyId, buf.getLong()); } public void addSubkey(long subkeyId) { @@ -237,6 +261,14 @@ public class RequiredInputParcel implements Parcelable { mSubkeysToExport.add(subKeyId); } + public void setPin(Passphrase pin) { + mPin = pin.toStringUnsafe().getBytes(); + } + + public void setAdminPin(Passphrase adminPin) { + mAdminPin = adminPin.toStringUnsafe().getBytes(); + } + public void addAll(RequiredInputParcel input) { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); @@ -245,7 +277,7 @@ public class RequiredInputParcel implements Parcelable { throw new AssertionError("Operation types must match, this is a programming error!"); } - Collections.addAll(mSubkeysToExport, input.mInputHashes); + Collections.addAll(mSubkeysToExport, input.mInputData); } public boolean isEmpty() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java new file mode 100644 index 000000000..2d9ac6ee3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.util.ArrayList; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.util.ExportHelper; + +public class BackupFragment extends Fragment { + + // This ids for multiple key export. + private ArrayList<Long> mIdsForRepeatAskPassphrase; + // This index for remembering the number of master key. + private int mIndex; + + static final int REQUEST_REPEAT_PASSPHRASE = 1; + + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.backup_fragment, container, false); + + View backupAll = view.findViewById(R.id.backup_all); + View backupPublicKeys = view.findViewById(R.id.backup_public_keys); + + backupAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(true); + } + }); + + backupPublicKeys.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(false); + } + }); + + return view; + } + + private void exportToFile(boolean includeSecretKeys) { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + if (!includeSecretKeys) { + ExportHelper exportHelper = new ExportHelper(activity); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false); + return; + } + + new AsyncTask<ContentResolver,Void,ArrayList<Long>>() { + @Override + protected ArrayList<Long> doInBackground(ContentResolver... resolver) { + ArrayList<Long> askPassphraseIds = new ArrayList<>(); + Cursor cursor = resolver[0].query( + KeyRings.buildUnifiedKeyRingsUri(), new String[] { + KeyRings.MASTER_KEY_ID, + KeyRings.HAS_SECRET, + }, KeyRings.HAS_SECRET + " != 0", null, null); + try { + if (cursor != null) { + while (cursor.moveToNext()) { + SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); + switch (secretKeyType) { + // all of these make no sense to ask + case PASSPHRASE_EMPTY: + case GNU_DUMMY: + case DIVERT_TO_CARD: + case UNAVAILABLE: + continue; + default: { + long keyId = cursor.getLong(0); + askPassphraseIds.add(keyId); + } + } + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return askPassphraseIds; + } + + @Override + protected void onPostExecute(ArrayList<Long> askPassphraseIds) { + super.onPostExecute(askPassphraseIds); + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + mIdsForRepeatAskPassphrase = askPassphraseIds; + mIndex = 0; + + if (mIdsForRepeatAskPassphrase.size() != 0) { + startPassphraseActivity(); + return; + } + + ExportHelper exportHelper = new ExportHelper(activity); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + } + + }.execute(activity.getContentResolver()); + + } + + private void startPassphraseActivity() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + Intent intent = new Intent(activity, PassphraseDialogActivity.class); + long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++); + intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId); + startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_REPEAT_PASSPHRASE) { + if (resultCode != Activity.RESULT_OK) { + return; + } + if (mIndex < mIdsForRepeatAskPassphrase.size()) { + startPassphraseActivity(); + return; + } + + ExportHelper exportHelper = new ExportHelper(getActivity()); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 891c2268c..5ca7c6bc7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -49,13 +49,17 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; +import java.util.Date; public class CertifyKeyFragment extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> @@ -151,7 +155,7 @@ public class CertifyKeyFragment // make certify image gray, like action icons ImageView vActionCertifyImage = (ImageView) view.findViewById(R.id.certify_key_action_certify_image); - vActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View vCertifyButton = view.findViewById(R.id.certify_key_certify_button); @@ -164,7 +168,7 @@ public class CertifyKeyFragment Notify.create(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR).show(); } else { - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); } } }); @@ -311,6 +315,11 @@ public class CertifyKeyFragment CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId); actionsParcel.mCertifyActions.addAll(certifyActions); + if (mUploadKeyCheckbox.isChecked()) { + actionsParcel.keyServerUri = Preferences.getPreferences(getActivity()) + .getPreferredKeyserver(); + } + // cached for next cryptoOperation loop cacheActionsParcel(actionsParcel); @@ -318,16 +327,15 @@ public class CertifyKeyFragment } @Override - public void onCryptoOperationSuccess(CertifyResult result) { + public void onQueuedOperationSuccess(CertifyResult result) { + // protected by Queueing*Fragment + Activity activity = getActivity(); + Intent intent = new Intent(); intent.putExtra(CertifyResult.EXTRA_RESULT, result); - getActivity().setResult(Activity.RESULT_OK, intent); - getActivity().finish(); - } + activity.setResult(Activity.RESULT_OK, intent); + activity.finish(); - @Override - public void onCryptoOperationCancelled() { - super.onCryptoOperationCancelled(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index 7a473e49f..ff5fb7cca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -53,7 +53,7 @@ public class ConsolidateDialogActivity extends FragmentActivity mRecovery = recovery; - mConsolidateOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + mConsolidateOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 505638c7c..83b176680 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Passphrase; import java.io.IOException; @@ -42,7 +43,9 @@ public class CreateKeyActivity extends BaseNfcActivity { public static final String EXTRA_FIRST_TIME = "first_time"; public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; - public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings"; + public static final String EXTRA_CREATE_YUBI_KEY = "create_yubi_key"; + public static final String EXTRA_YUBI_KEY_PIN = "yubi_key_pin"; + public static final String EXTRA_YUBI_KEY_ADMIN_PIN = "yubi_key_admin_pin"; public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; public static final String EXTRA_NFC_AID = "nfc_aid"; @@ -55,10 +58,17 @@ public class CreateKeyActivity extends BaseNfcActivity { ArrayList<String> mAdditionalEmails; Passphrase mPassphrase; boolean mFirstTime; - boolean mUseSmartCardSettings; + boolean mCreateYubiKey; + Passphrase mYubiKeyPin; + Passphrase mYubiKeyAdminPin; Fragment mCurrentFragment; + + byte[] mScannedFingerprints; + byte[] mNfcAid; + String mNfcUserId; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -66,13 +76,8 @@ public class CreateKeyActivity extends BaseNfcActivity { // React on NDEF_DISCOVERED from Manifest // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - try { - handleTagDiscoveredIntent(getIntent()); - } catch (CardException e) { - handleNfcError(e); - } catch (IOException e) { - handleNfcError(e); - } + + handleIntentInBackground(getIntent()); setTitle(R.string.title_manage_my_keys); @@ -88,7 +93,9 @@ public class CreateKeyActivity extends BaseNfcActivity { mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS); mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); - mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS); + mCreateYubiKey = savedInstanceState.getBoolean(EXTRA_CREATE_YUBI_KEY); + mYubiKeyPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_PIN); + mYubiKeyAdminPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_ADMIN_PIN); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { @@ -98,7 +105,7 @@ public class CreateKeyActivity extends BaseNfcActivity { mName = intent.getStringExtra(EXTRA_NAME); mEmail = intent.getStringExtra(EXTRA_EMAIL); mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); - mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false); + mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false); if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); @@ -106,15 +113,19 @@ public class CreateKeyActivity extends BaseNfcActivity { byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); if (containsKeys(nfcFingerprints)) { - Fragment frag = CreateKeyYubiKeyImportFragment.newInstance( + Fragment frag = CreateYubiKeyImportFragment.newInstance( nfcFingerprints, nfcAid, nfcUserId); loadFragment(frag, FragAction.START); setTitle(R.string.title_import_keys); } else { - Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance(); - loadFragment(frag, FragAction.START); - setTitle(R.string.title_manage_my_keys); +// Fragment frag = CreateYubiKeyBlankFragment.newInstance(); +// loadFragment(frag, FragAction.START); +// setTitle(R.string.title_manage_my_keys); + Notify.create(this, + "YubiKey key creation is currently not supported. Please follow our FAQ.", + Notify.Style.ERROR + ).show(); } // done @@ -136,40 +147,51 @@ public class CreateKeyActivity extends BaseNfcActivity { } @Override - protected void onNfcPerform() throws IOException { + protected void doNfcInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).onNfcPerform(); + ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; } - byte[] scannedFingerprints = nfcGetFingerprints(); - byte[] nfcAid = nfcGetAid(); - String userId = nfcGetUserId(); + mScannedFingerprints = nfcGetFingerprints(); + mNfcAid = nfcGetAid(); + mNfcUserId = nfcGetUserId(); + } - if (containsKeys(scannedFingerprints)) { + @Override + protected void onNfcPostExecute() throws IOException { + if (mCurrentFragment instanceof NfcListenerFragment) { + ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); + return; + } + + if (containsKeys(mScannedFingerprints)) { try { - long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); + long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); ring.getMasterKeyId(); Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { - Fragment frag = CreateKeyYubiKeyImportFragment.newInstance( - scannedFingerprints, nfcAid, userId); + Fragment frag = CreateYubiKeyImportFragment.newInstance( + mScannedFingerprints, mNfcAid, mNfcUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { - Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance(); - loadFragment(frag, FragAction.TO_RIGHT); +// Fragment frag = CreateYubiKeyBlankFragment.newInstance(); +// loadFragment(frag, FragAction.TO_RIGHT); + Notify.create(this, + "YubiKey key creation is currently not supported. Please follow our FAQ.", + Notify.Style.ERROR + ).show(); } - } private boolean containsKeys(byte[] scannedFingerprints) { @@ -193,7 +215,9 @@ public class CreateKeyActivity extends BaseNfcActivity { outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails); outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); - outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings); + outState.putBoolean(EXTRA_CREATE_YUBI_KEY, mCreateYubiKey); + outState.putParcelable(EXTRA_YUBI_KEY_PIN, mYubiKeyPin); + outState.putParcelable(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin); } @Override @@ -238,7 +262,8 @@ public class CreateKeyActivity extends BaseNfcActivity { } interface NfcListenerFragment { - public void onNfcPerform() throws IOException; + public void doNfcInBackground() throws IOException; + public void onNfcPostExecute() throws IOException; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 597f04d6b..acb768f55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -29,6 +30,7 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; @@ -37,7 +39,6 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.EmailEditText; @@ -201,7 +202,7 @@ public class CreateKeyEmailFragment extends Fragment { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + if (message.what == AddEmailDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); @@ -232,11 +233,35 @@ public class CreateKeyEmailFragment extends Fragment { mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); - CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity()); + + if (createKeyActivity.mCreateYubiKey) { + hideKeyboard(); + + CreateYubiKeyPinFragment frag = CreateYubiKeyPinFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } else { + CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } } } + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + private ArrayList<String> getAdditionalEmails() { ArrayList<String> emails = new ArrayList<>(); for (EmailAdapter.ViewModel holder : mAdditionalEmailModels) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index e67715988..739eb3e35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.Date; +import java.util.Iterator; + import android.app.Activity; import android.content.Intent; import android.database.Cursor; @@ -44,13 +48,13 @@ import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -import java.util.Iterator; - public class CreateKeyFinalFragment extends Fragment { public static final int REQUEST_EDIT_KEY = 0x00008007; @@ -69,8 +73,14 @@ public class CreateKeyFinalFragment extends Fragment { private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper; private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper; + // queued results which may trigger delayed actions + private EditKeyResult mQueuedSaveKeyResult; + private OperationResult mQueuedFinishResult; + private EditKeyResult mQueuedDisplayResult; + public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); + frag.setRetainInstance(true); Bundle args = new Bundle(); frag.setArguments(args); @@ -178,7 +188,7 @@ public class CreateKeyFinalFragment extends Fragment { if (mSaveKeyringParcel == null) { mSaveKeyringParcel = new SaveKeyringParcel(); - if (createKeyActivity.mUseSmartCardSettings) { + if (createKeyActivity.mCreateYubiKey) { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, @@ -187,6 +197,9 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); mEditText.setText(R.string.create_key_custom); mEditButton.setEnabled(false); + + // use empty passphrase + mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); } else { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -194,6 +207,10 @@ public class CreateKeyFinalFragment extends Fragment { 4096, null, KeyFlags.SIGN_DATA, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + + mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null + ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) + : null; } String userId = KeyRing.createUserId( new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) @@ -209,14 +226,44 @@ public class CreateKeyFinalFragment extends Fragment { mSaveKeyringParcel.mAddUserIds.add(thisUserId); } } - mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) - : null; } + + // handle queued actions + + if (mQueuedFinishResult != null) { + finishWithResult(mQueuedFinishResult); + return; + } + + if (mQueuedDisplayResult != null) { + try { + displayResult(mQueuedDisplayResult); + } finally { + // clear after operation, note that this may drop the operation if it didn't + // work when called from here! + mQueuedDisplayResult = null; + } + } + + if (mQueuedSaveKeyResult != null) { + try { + uploadKey(mQueuedSaveKeyResult); + } finally { + // see above + mQueuedSaveKeyResult = null; + } + } + } private void createKey() { - final CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); + CreateKeyActivity activity = (CreateKeyActivity) getActivity(); + if (activity == null) { + // this is a ui-triggered action, nvm if it fails while detached! + return; + } + + final boolean createYubiKey = activity.mCreateYubiKey; CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> createKeyCallback = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { @@ -228,7 +275,7 @@ public class CreateKeyFinalFragment extends Fragment { @Override public void onCryptoOperationSuccess(EditKeyResult result) { - if (createKeyActivity.mUseSmartCardSettings) { + if (createYubiKey) { moveToCard(result); return; } @@ -239,10 +286,7 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, result); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + finishWithResult(result); } @Override @@ -252,7 +296,7 @@ public class CreateKeyFinalFragment extends Fragment { @Override public void onCryptoOperationError(EditKeyResult result) { - result.createNotify(getActivity()).show(); + displayResult(result); } @Override @@ -261,16 +305,25 @@ public class CreateKeyFinalFragment extends Fragment { } }; - mCreateOpHelper = new CryptoOperationHelper<>(this, createKeyCallback, - R.string.progress_building_key); + mCreateOpHelper = new CryptoOperationHelper<>(1, this, createKeyCallback, R.string.progress_building_key); mCreateOpHelper.cryptoOperation(); } + private void displayResult(EditKeyResult result) { + Activity activity = getActivity(); + if (activity == null) { + mQueuedDisplayResult = result; + return; + } + result.createNotify(activity).show(); + } + private void moveToCard(final EditKeyResult saveKeyResult) { - CachedPublicKeyRing key = (new ProviderHelper(getActivity())) - .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); + CreateKeyActivity activity = (CreateKeyActivity) getActivity(); final SaveKeyringParcel changeKeyringParcel; + CachedPublicKeyRing key = (new ProviderHelper(activity)) + .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); try { changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); } catch (PgpKeyNotFoundException e) { @@ -278,9 +331,10 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Cursor cursor = getActivity().getContentResolver().query( + // define subkeys that should be moved to the card + Cursor cursor = activity.getContentResolver().query( KeychainContract.Keys.buildKeysUri(changeKeyringParcel.mMasterKeyId), - new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null + new String[] { KeychainContract.Keys.KEY_ID, }, null, null, null ); try { while (cursor != null && cursor.moveToNext()) { @@ -293,6 +347,10 @@ public class CreateKeyFinalFragment extends Fragment { } } + // define new PIN and Admin PIN for the card + changeKeyringParcel.mCardPin = activity.mYubiKeyPin; + changeKeyringParcel.mCardAdminPin = activity.mYubiKeyAdminPin; + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> callback = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { @@ -326,10 +384,7 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + finishWithResult(saveKeyResult); } @Override @@ -339,16 +394,22 @@ public class CreateKeyFinalFragment extends Fragment { }; - mMoveToCardOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_modify); - mMoveToCardOpHelper.cryptoOperation(); + mMoveToCardOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_modify); + mMoveToCardOpHelper.cryptoOperation(new CryptoInputParcel(new Date())); } private void uploadKey(final EditKeyResult saveKeyResult) { + Activity activity = getActivity(); + // if the activity is gone at this point, there is nothing we can do! + if (activity == null) { + mQueuedSaveKeyResult = saveKeyResult; + return; + } + // set data uri as path to keyring - final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - saveKeyResult.mMasterKeyId); + final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId); // upload to favorite keyserver - final String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); + final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() { @@ -374,14 +435,8 @@ public class CreateKeyFinalFragment extends Fragment { } public void handleResult(ExportResult result) { - // TODO: ExportOperation UPLOAD_KEYSERVER needs logs! - // TODO: then merge logs here! - //saveKeyResult.getLog().add(result, 0); - - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + saveKeyResult.getLog().add(result, 0); + finishWithResult(saveKeyResult); } @Override @@ -390,9 +445,21 @@ public class CreateKeyFinalFragment extends Fragment { } }; - - mUploadOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_uploading); + mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } + public void finishWithResult(OperationResult result) { + Activity activity = getActivity(); + if (activity == null) { + mQueuedFinishResult = result; + return; + } + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, result); + activity.setResult(Activity.RESULT_OK, data); + activity.finish(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java index 3379e0a6d..d858fd6ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java @@ -21,6 +21,8 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; @@ -79,19 +81,10 @@ public class CreateKeyPassphraseFragment extends Fragment { return output; } - private static boolean areEditTextsEqual(Context context, EditText editText1, EditText editText2) { + private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { Passphrase p1 = new Passphrase(editText1); Passphrase p2 = new Passphrase(editText2); - boolean output = (p1.equals(p2)); - - if (!output) { - editText2.setError(context.getString(R.string.create_key_passphrases_not_equal)); - editText2.requestFocus(); - } else { - editText2.setError(null); - } - - return output; + return (p1.equals(p2)); } @Override @@ -107,8 +100,8 @@ public class CreateKeyPassphraseFragment extends Fragment { // initial values // TODO: using String here is unsafe... if (mCreateKeyActivity.mPassphrase != null) { - mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); - mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEdit.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); + mPassphraseEditAgain.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); } mPassphraseEdit.requestFocus(); @@ -137,6 +130,35 @@ public class CreateKeyPassphraseFragment extends Fragment { } }); + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + return; + } + + if (areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_ok, 0); + } else { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_bad, 0); + } + + } + + @Override + public void afterTextChanged(Editable s) { + + } + }; + + mPassphraseEdit.addTextChangedListener(textWatcher); + mPassphraseEditAgain.addTextChangedListener(textWatcher); return view; } @@ -153,9 +175,15 @@ public class CreateKeyPassphraseFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mPassphraseEdit) - && areEditTextsEqual(getActivity(), mPassphraseEdit, mPassphraseEditAgain)) { + if (isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { + + if (!areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { + mPassphraseEditAgain.setError(getActivity().getApplicationContext().getString(R.string.create_key_passphrases_not_equal)); + mPassphraseEditAgain.requestFocus(); + return; + } + mPassphraseEditAgain.setError(null); // save state mCreateKeyActivity.mPassphrase = new Passphrase(mPassphraseEdit); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 1a844e6e4..cd97ef108 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -81,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment(); + CreateYubiKeyWaitFragment frag = new CreateYubiKeyWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java index 6df340636..5b13dc88e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiKeyBlankFragment extends Fragment { +public class CreateYubiKeyBlankFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; @@ -36,8 +36,8 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment { /** * Creates new instance of this fragment */ - public static CreateKeyYubiKeyBlankFragment newInstance() { - CreateKeyYubiKeyBlankFragment frag = new CreateKeyYubiKeyBlankFragment(); + public static CreateYubiKeyBlankFragment newInstance() { + CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment(); Bundle args = new Bundle(); @@ -81,7 +81,7 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment { } private void nextClicked() { - mCreateKeyActivity.mUseSmartCardSettings = true; + mCreateKeyActivity.mCreateYubiKey = true; CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java index 8de874ec5..d88e6b9f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -39,13 +40,13 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiKeyImportFragment - extends CryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> +public class CreateYubiKeyImportFragment + extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> implements NfcListenerFragment { private static final String ARG_FINGERPRINT = "fingerprint"; @@ -68,7 +69,7 @@ public class CreateKeyYubiKeyImportFragment public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { - CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment(); + CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); @@ -195,7 +196,7 @@ public class CreateKeyYubiKeyImportFragment } @Override - public void onNfcPerform() throws IOException { + public void doNfcInBackground() throws IOException { mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints(); mNfcAid = mCreateKeyActivity.nfcGetAid(); @@ -204,10 +205,14 @@ public class CreateKeyYubiKeyImportFragment byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20); mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + } + + @Override + public void onNfcPostExecute() throws IOException { setData(); - refreshSearch(); + refreshSearch(); } @Override @@ -216,14 +221,17 @@ public class CreateKeyYubiKeyImportFragment } @Override - public void onCryptoOperationSuccess(ImportKeyResult result) { + public void onQueuedOperationSuccess(ImportKeyResult result) { long[] masterKeyIds = result.getImportedMasterKeyIds(); if (masterKeyIds.length == 0) { super.onCryptoOperationError(result); return; } - Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + // null-protected from Queueing*Fragment + Activity activity = getActivity(); + + Intent intent = new Intent(activity, ViewKeyActivity.class); // use the imported masterKeyId, not the one from the yubikey, because // that one might* just have been a subkey of the imported key intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); @@ -232,6 +240,6 @@ public class CreateKeyYubiKeyImportFragment intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); - getActivity().finish(); + activity.finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java new file mode 100644 index 000000000..a793b31f2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.security.SecureRandom; + +public class CreateYubiKeyPinFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + TextView mPin; + TextView mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinFragment newInstance() { + CreateYubiKeyPinFragment frag = new CreateYubiKeyPinFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); + + mPin = (TextView) view.findViewById(R.id.create_yubi_key_pin); + mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + if (mCreateKeyActivity.mYubiKeyPin == null) { + new AsyncTask<Void, Void, Pair<Passphrase, Passphrase>>() { + @Override + protected Pair<Passphrase, Passphrase> doInBackground(Void... unused) { + SecureRandom secureRandom = new SecureRandom(); + // min = 6, we choose 6 + String pin = "" + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9); + // min = 8, we choose 10, but 6 are equals the PIN + String adminPin = pin + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9); + + return new Pair<>(new Passphrase(pin), new Passphrase(adminPin)); + } + + @Override + protected void onPostExecute(Pair<Passphrase, Passphrase> pair) { + mCreateKeyActivity.mYubiKeyPin = pair.first; + mCreateKeyActivity.mYubiKeyAdminPin = pair.second; + + mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); + } + }.execute(); + } else { + mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); + } + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + + private void nextClicked() { + CreateYubiKeyPinRepeatFragment frag = CreateYubiKeyPinRepeatFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + + private void back() { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java new file mode 100644 index 000000000..2e752e609 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +public class CreateYubiKeyPinRepeatFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + EditText mPin; + EditText mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinRepeatFragment newInstance() { + CreateYubiKeyPinRepeatFragment frag = new CreateYubiKeyPinRepeatFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + /** + * Checks if text of given EditText is not empty. If it is empty an error is + * set and the EditText gets the focus. + * + * @param context + * @param editText + * @return true if EditText is not empty + */ + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().length() == 0) { + editText.setError(context.getString(R.string.create_key_empty)); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + private static boolean checkPin(Context context, EditText editText1, String pin) { + boolean output = editText1.getText().toString().equals(pin); + + if (!output) { + editText1.setError(context.getString(R.string.create_key_yubi_key_pin_not_correct)); + editText1.requestFocus(); + } else { + editText1.setError(null); + } + + return output; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_repeat_fragment, container, false); + + mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); + mAdminPin = (EditText) view.findViewById(R.id.create_yubi_key_admin_pin_repeat); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mPin.requestFocus(); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void back() { + hideKeyboard(); + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + + private void nextClicked() { + if (isEditTextNotEmpty(getActivity(), mPin) + && checkPin(getActivity(), mPin, mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()) + && isEditTextNotEmpty(getActivity(), mAdminPin) + && checkPin(getActivity(), mAdminPin, mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe())) { + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + hideKeyboard(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + } + + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java index 0e2dd2531..d45195512 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiKeyWaitFragment extends Fragment { +public class CreateYubiKeyWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 04f54f151..e581e069b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -49,13 +49,7 @@ public class DecryptActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); @@ -158,7 +152,6 @@ public class DecryptActivity extends BaseActivity { } - } catch (IOException e) { Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show(); finish(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 4eb8cd5e8..aaf337f42 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -19,15 +19,11 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; -import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; -import android.os.Parcelable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -52,7 +48,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -189,7 +184,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. } }; - mImportOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_importing); + mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 96767463e..567589821 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -65,12 +65,11 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -81,18 +80,21 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap; public class DecryptListFragment - extends CryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult> + extends QueueingCryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult> implements OnMenuItemClickListener { public static final String ARG_INPUT_URIS = "input_uris"; public static final String ARG_OUTPUT_URIS = "output_uris"; + public static final String ARG_CANCELLED_URIS = "cancelled_uris"; public static final String ARG_RESULTS = "results"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; + public static final String ARG_CURRENT_URI = "current_uri"; private ArrayList<Uri> mInputUris; private HashMap<Uri, Uri> mOutputUris; private ArrayList<Uri> mPendingInputUris; + private ArrayList<Uri> mCancelledInputUris; private Uri mCurrentInputUri; @@ -111,6 +113,10 @@ public class DecryptListFragment return frag; } + public DecryptListFragment() { + super(null); + } + /** * Inflate the layout for this fragment */ @@ -152,6 +158,8 @@ public class DecryptListFragment outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mOutputUris)); + outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); + outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); } @@ -162,25 +170,45 @@ public class DecryptListFragment Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); + ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ParcelableHashMap<Uri,Uri> outputUris = args.getParcelable(ARG_OUTPUT_URIS); ParcelableHashMap<Uri,DecryptVerifyResult> results = args.getParcelable(ARG_RESULTS); + Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); - displayInputUris(inputUris, + displayInputUris(inputUris, currentInputUri, cancelledUris, outputUris != null ? outputUris.getMap() : null, results != null ? results.getMap() : null ); } - private void displayInputUris(ArrayList<Uri> inputUris, HashMap<Uri,Uri> outputUris, + private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri, + ArrayList<Uri> cancelledUris, HashMap<Uri,Uri> outputUris, HashMap<Uri,DecryptVerifyResult> results) { mInputUris = inputUris; + mCurrentInputUri = currentInputUri; mOutputUris = outputUris != null ? outputUris : new HashMap<Uri,Uri>(inputUris.size()); + mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>(); mPendingInputUris = new ArrayList<>(); - for (Uri uri : inputUris) { + for (final Uri uri : inputUris) { mAdapter.add(uri); + + if (uri.equals(mCurrentInputUri)) { + continue; + } + + if (mCancelledInputUris.contains(uri)) { + mAdapter.setCancelled(uri, new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + continue; + } + if (results != null && results.containsKey(uri)) { processResult(uri, results.get(uri)); } else { @@ -189,16 +217,9 @@ public class DecryptListFragment } } - cryptoOperation(); - } - - private String removeEncryptedAppend(String name) { - if (name.endsWith(Constants.FILE_EXTENSION_ASC) - || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) - || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { - return name.substring(0, name.length() - 4); + if (mCurrentInputUri == null) { + cryptoOperation(); } - return name; } private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { @@ -249,17 +270,13 @@ public class DecryptListFragment } @Override - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - super.cryptoOperation(cryptoInput, false); - } - - @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); return true; } + @Override - public void onCryptoOperationError(DecryptVerifyResult result) { + public void onQueuedOperationError(DecryptVerifyResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -269,7 +286,7 @@ public class DecryptListFragment } @Override - public void onCryptoOperationSuccess(DecryptVerifyResult result) { + public void onQueuedOperationSuccess(DecryptVerifyResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -278,6 +295,25 @@ public class DecryptListFragment cryptoOperation(); } + @Override + public void onCryptoOperationCancelled() { + super.onCryptoOperationCancelled(); + + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; + + mCancelledInputUris.add(uri); + mAdapter.setCancelled(uri, new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + + cryptoOperation(); + + } + private void processResult(final Uri uri, final DecryptVerifyResult result) { new AsyncTask<Void, Void, Drawable>() { @@ -304,7 +340,7 @@ public class DecryptListFragment } final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(type); + intent.setDataAndType(outputUri, type); final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent, 0); @@ -361,6 +397,22 @@ public class DecryptListFragment } + public void retryUri(Uri uri) { + + // never interrupt running operations! + if (mCurrentInputUri != null) { + return; + } + + // un-cancel this one + mCancelledInputUris.remove(uri); + mPendingInputUris.add(uri); + mAdapter.setCancelled(uri, null); + + cryptoOperation(); + + } + public void displayWithViewIntent(final Uri uri) { Activity activity = getActivity(); if (activity == null || mCurrentInputUri != null) { @@ -539,12 +591,14 @@ public class DecryptListFragment int mProgress, mMax; String mProgressMsg; + OnClickListener mCancelled; ViewModel(Context context, Uri uri) { mContext = context; mInputUri = uri; mProgress = 0; mMax = 100; + mCancelled = null; } void addResult(DecryptVerifyResult result) { @@ -564,6 +618,10 @@ public class DecryptListFragment return mResult != null; } + void setCancelled(OnClickListener retryListener) { + mCancelled = retryListener; + } + void setProgress(int progress, int max, String msg) { if (msg != null) { mProgressMsg = msg; @@ -621,6 +679,11 @@ public class DecryptListFragment // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); + if (model.mCancelled != null) { + bindItemCancelled(holder, model); + return; + } + if (!model.hasResult()) { bindItemProgress(holder, model); return; @@ -634,6 +697,14 @@ public class DecryptListFragment } + private void bindItemCancelled(ViewHolder holder, ViewModel model) { + if (holder.vAnimator.getDisplayedChild() != 3) { + holder.vAnimator.setDisplayedChild(3); + } + + holder.vCancelledRetry.setOnClickListener(model.mCancelled); + } + private void bindItemProgress(ViewHolder holder, ViewModel model) { if (holder.vAnimator.getDisplayedChild() != 0) { holder.vAnimator.setDisplayedChild(0); @@ -749,6 +820,13 @@ public class DecryptListFragment notifyItemChanged(pos); } + public void setCancelled(Uri uri, OnClickListener retryListener) { + ViewModel newModel = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).setCancelled(retryListener); + notifyItemChanged(pos); + } + public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, OnClickListener onFileClick, OnClickListener onKeyClick) { @@ -796,6 +874,8 @@ public class DecryptListFragment public TextView vErrorMsg; public ImageView vErrorViewLog; + public ImageView vCancelledRetry; + public ViewHolder(View itemView) { super(itemView); @@ -824,6 +904,8 @@ public class DecryptListFragment vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg); vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log); + vCancelledRetry = (ImageView) itemView.findViewById(R.id.cancel_retry); + } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java new file mode 100644 index 000000000..b22053df1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.support.v7.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DeleteResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.RevokeResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; +import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; +import java.util.HashMap; + +public class DeleteKeyDialogActivity extends FragmentActivity { + public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids"; + public static final String EXTRA_HAS_SECRET = "extra_has_secret"; + public static final String EXTRA_KEYSERVER = "extra_keyserver"; + + private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper; + private CryptoOperationHelper<RevokeKeyringParcel, RevokeResult> mRevokeOpHelper; + + private long[] mMasterKeyIds; + private boolean mHasSecret; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mDeleteOpHelper = new CryptoOperationHelper<>(1, DeleteKeyDialogActivity.this, + getDeletionCallback(), R.string.progress_deleting); + + mRevokeOpHelper = new CryptoOperationHelper<>(2, this, + getRevocationCallback(), R.string.progress_revoking_uploading); + + mMasterKeyIds = getIntent().getLongArrayExtra(EXTRA_DELETE_MASTER_KEY_IDS); + mHasSecret = getIntent().getBooleanExtra(EXTRA_HAS_SECRET, false); + + if (mMasterKeyIds.length > 1 && mHasSecret) { + // secret keys can only be deleted individually + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_DEL_ERROR_MULTI_SECRET, 0); + returnResult(new DeleteResult(OperationResult.RESULT_ERROR, log, 0, + mMasterKeyIds.length)); + } + + if (mMasterKeyIds.length == 1 && mHasSecret) { + // if mMasterKeyIds.length == 0 we let the DeleteOperation respond + try { + HashMap<String, Object> data = new ProviderHelper(this).getUnifiedData( + mMasterKeyIds[0], new String[]{ + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED + }, new int[]{ + ProviderHelper.FIELD_TYPE_STRING, + ProviderHelper.FIELD_TYPE_INTEGER + } + ); + + String name; + KeyRing.UserId mainUserId = KeyRing.splitUserId( + (String) data.get(KeychainContract.KeyRings.USER_ID)); + if (mainUserId.name != null) { + name = mainUserId.name; + } else { + name = getString(R.string.user_id_no_name); + } + + if ((long) data.get(KeychainContract.KeyRings.IS_REVOKED) > 0) { + showNormalDeleteDialog(); + } else { + showRevokeDeleteDialog(name); + } + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, + "Secret key to delete not found at DeleteKeyDialogActivity for " + + mMasterKeyIds[0], e); + finish(); + } + } else { + showNormalDeleteDialog(); + } + } + + private void showNormalDeleteDialog() { + + DeleteKeyDialogFragment deleteKeyDialogFragment + = DeleteKeyDialogFragment.newInstance(mMasterKeyIds, mHasSecret); + + deleteKeyDialogFragment.show(getSupportFragmentManager(), "deleteKeyDialog"); + + } + + private void showRevokeDeleteDialog(String keyname) { + + RevokeDeleteDialogFragment fragment = RevokeDeleteDialogFragment.newInstance(keyname); + fragment.show(getSupportFragmentManager(), "deleteRevokeDialog"); + } + + private void startRevocationOperation() { + mRevokeOpHelper.cryptoOperation(new CryptoInputParcel(new Date(), false)); + } + + private void startDeletionOperation() { + mDeleteOpHelper.cryptoOperation(); + } + + private CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult> getRevocationCallback() { + + return new CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult>() { + @Override + public RevokeKeyringParcel createOperationInput() { + return new RevokeKeyringParcel(mMasterKeyIds[0], true, + getIntent().getStringExtra(EXTRA_KEYSERVER)); + } + + @Override + public void onCryptoOperationSuccess(RevokeResult result) { + returnResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void onCryptoOperationError(RevokeResult result) { + returnResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + } + + private CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> getDeletionCallback() { + + return new CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult>() { + @Override + public DeleteKeyringParcel createOperationInput() { + return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); + } + + @Override + public void onCryptoOperationSuccess(DeleteResult result) { + returnResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void onCryptoOperationError(DeleteResult result) { + returnResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + } + + private void returnResult(OperationResult result) { + Intent intent = new Intent(); + intent.putExtra(OperationResult.EXTRA_RESULT, result); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); + mRevokeOpHelper.handleActivityResult(requestCode, resultCode, data); + } + + public static class DeleteKeyDialogFragment extends DialogFragment { + + private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; + private static final String ARG_HAS_SECRET = "has_secret"; + + private TextView mMainMessage; + private View mInflateView; + + /** + * Creates new instance of this delete file dialog fragment + */ + public static DeleteKeyDialogFragment newInstance(long[] masterKeyIds, boolean hasSecret) { + DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); + Bundle args = new Bundle(); + + args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); + args.putBoolean(ARG_HAS_SECRET, hasSecret); + + frag.setArguments(args); + + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + + final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); + final boolean hasSecret = getArguments().getBoolean(ARG_HAS_SECRET); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + + // Setup custom View to display in AlertDialog + LayoutInflater inflater = LayoutInflater.from(theme); + mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); + builder.setView(mInflateView); + + mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); + + // If only a single key has been selected + if (masterKeyIds.length == 1) { + long masterKeyId = masterKeyIds[0]; + + try { + HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData( + masterKeyId, new String[]{ + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.HAS_ANY_SECRET + }, new int[]{ + ProviderHelper.FIELD_TYPE_STRING, + ProviderHelper.FIELD_TYPE_INTEGER + } + ); + String name; + KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeychainContract.KeyRings.USER_ID)); + if (mainUserId.name != null) { + name = mainUserId.name; + } else { + name = getString(R.string.user_id_no_name); + } + + if (hasSecret) { + // show title only for secret key deletions, + // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior + builder.setTitle(getString(R.string.title_delete_secret_key, name)); + mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); + } else { + mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); + } + } catch (ProviderHelper.NotFoundException e) { + dismiss(); + return null; + } + } else { + mMainMessage.setText(R.string.key_deletion_confirmation_multi); + } + + builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + ((DeleteKeyDialogActivity) getActivity()).startDeletionOperation(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + return builder.show(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + } + + public static class RevokeDeleteDialogFragment extends DialogFragment { + + public static final String ARG_KEY_NAME = "arg_key_name"; + + public static RevokeDeleteDialogFragment newInstance(String keyName) { + Bundle args = new Bundle(); + args.putString(ARG_KEY_NAME, keyName); + RevokeDeleteDialogFragment frag = new RevokeDeleteDialogFragment(); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + + final String CHOICE_REVOKE = getString(R.string.del_rev_dialog_choice_rev_upload); + final String CHOICE_DELETE = getString(R.string.del_rev_dialog_choice_delete); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + builder.setTitle(getString(R.string.del_rev_dialog_title, + getArguments().get(ARG_KEY_NAME))); + + LayoutInflater inflater = LayoutInflater.from(theme); + View view = inflater.inflate(R.layout.del_rev_dialog, null); + builder.setView(view); + + final Spinner spinner = (Spinner) view.findViewById(R.id.spinner); + + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + builder.setPositiveButton(R.string.del_rev_dialog_btn_revoke, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + String choice = spinner.getSelectedItem().toString(); + if (choice.equals(CHOICE_REVOKE)) { + ((DeleteKeyDialogActivity) activity) + .startRevocationOperation(); + } else if (choice.equals(CHOICE_DELETE)) { + ((DeleteKeyDialogActivity) activity) + .showNormalDeleteDialog(); + } else { + throw new AssertionError( + "Unsupported delete type in RevokeDeleteDialogFragment"); + } + } + }); + + final AlertDialog alertDialog = builder.show(); + + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { + + String choice = parent.getItemAtPosition(pos).toString(); + + if (choice.equals(CHOICE_REVOKE)) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setText(R.string.del_rev_dialog_btn_revoke); + } else if (choice.equals(CHOICE_DELETE)) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setText(R.string.del_rev_dialog_btn_delete); + } else { + throw new AssertionError( + "Unsupported delete type in RevokeDeleteDialogFragment"); + } + } + + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + return alertDialog; + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index be21cdde1..3c8e972b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -41,13 +41,7 @@ public class DisplayTextActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index 7b3af48cc..dc06e9115 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -29,9 +33,9 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.ShareHelper; public class DisplayTextFragment extends DecryptFragment { @@ -80,8 +84,19 @@ public class DisplayTextFragment extends DecryptFragment { } private void copyToClipboard(String text) { - ClipboardReflection.copyToClipboard(getActivity(), text); - Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show(); + Activity activity = getActivity(); + if (activity == null) { + return; + } + + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + return; + } + + clipMan.setPrimaryClip(ClipData.newPlainText(Constants.CLIPBOARD_LABEL, text)); + Notify.create(activity, R.string.text_copied_to_clipboard, Style.OK).show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1363d44f2..4769e68d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -57,7 +57,7 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; @@ -68,7 +68,9 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult> +import java.util.Date; + +public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyringParcel, OperationResult> implements LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_DATA_URI = "uri"; @@ -151,7 +153,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, if (mDataUri == null) { returnKeyringParcel(); } else { - cryptoOperation(new CryptoInputParcel()); + cryptoOperation(new CryptoInputParcel(new Date())); } } }, new OnClickListener() { @@ -192,7 +194,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, private void loadData(Uri dataUri) { mDataUri = dataUri; - Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + Log.i(Constants.TAG, "mDataUri: " + mDataUri); // load the secret key ring. we do verify here that the passphrase is correct, so cached won't do try { @@ -437,44 +439,50 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, } break; } - case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: { - Activity activity = EditKeyFragment.this.getActivity(); - SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); - if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || - secretKeyType == SecretKeyType.GNU_DUMMY) { - Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) - .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); - break; - } - int algorithm = mSubkeysAdapter.getAlgorithm(position); - // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN - if (algorithm != 1 && algorithm != 2 && algorithm != 3) { - Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) - .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); - break; - } - if (mSubkeysAdapter.getKeySize(position) != 2048) { - Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) - .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); - break; - } - - - SubkeyChange change; - change = mSaveKeyringParcel.getSubkeyChange(keyId); - if (change == null) { - mSaveKeyringParcel.mChangeSubKeys.add( - new SubkeyChange(keyId, false, true) - ); - break; - } - // toggle - change.mMoveKeyToCard = !change.mMoveKeyToCard; - if (change.mMoveKeyToCard && change.mDummyStrip) { - // User had chosen to strip key, but now wants to divert it. - change.mDummyStrip = false; - } + case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: { + // TODO: enable later when Admin PIN handling is resolved + Notify.create(getActivity(), + "This feature will be available in an upcoming OpenKeychain version.", + Notify.Style.WARN).show(); break; + +// Activity activity = EditKeyFragment.this.getActivity(); +// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); +// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || +// secretKeyType == SecretKeyType.GNU_DUMMY) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// int algorithm = mSubkeysAdapter.getAlgorithm(position); +// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN +// if (algorithm != 1 && algorithm != 2 && algorithm != 3) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// if (mSubkeysAdapter.getKeySize(position) != 2048) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// +// +// SubkeyChange change; +// change = mSaveKeyringParcel.getSubkeyChange(keyId); +// if (change == null) { +// mSaveKeyringParcel.mChangeSubKeys.add( +// new SubkeyChange(keyId, false, true) +// ); +// break; +// } +// // toggle +// change.mMoveKeyToCard = !change.mMoveKeyToCard; +// if (change.mMoveKeyToCard && change.mDummyStrip) { +// // User had chosen to strip key, but now wants to divert it. +// change.mDummyStrip = false; +// } +// break; } } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); @@ -612,13 +620,16 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, } @Override - public void onCryptoOperationSuccess(OperationResult result) { + public void onQueuedOperationSuccess(OperationResult result) { + + // null-protected from Queueing*Fragment + Activity activity = getActivity(); // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, result); - getActivity().setResult(EditKeyActivity.RESULT_OK, intent); - getActivity().finish(); + activity.setResult(EditKeyActivity.RESULT_OK, intent); + activity.finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index 779b22535..fc72a6c9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -124,8 +124,7 @@ public class EncryptDecryptOverviewFragment extends Fragment { super.onResume(); // get text from clipboard - final CharSequence clipboardText = - ClipboardReflection.getClipboardText(getActivity()); + final CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); // if it's null, nothing to do here /o/ if (clipboardText == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index d51b6e0ff..136787984 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -41,12 +42,7 @@ public class EncryptFilesActivity extends EncryptActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 215af5885..3dc93872d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -35,6 +36,7 @@ import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.v4.app.FragmentActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -56,6 +58,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; @@ -276,18 +279,21 @@ public class EncryptFilesFragment public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.encrypt_save: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SAVE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SHARE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_copy: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.COPY; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.check_use_armor: { @@ -386,7 +392,13 @@ public class EncryptFilesFragment } @Override - public void onCryptoOperationSuccess(final SignEncryptResult result) { + public void onQueuedOperationSuccess(final SignEncryptResult result) { + super.onQueuedOperationSuccess(result); + + hideKeyboard(); + + // protected by Queueing*Fragment + FragmentActivity activity = getActivity(); if (mDeleteAfterEncrypt) { // TODO make behavior coherent here @@ -400,13 +412,18 @@ public class EncryptFilesFragment // Share encrypted message/file startActivity(sendWithChooserExcludingEncrypt()); } else { + Activity activity = getActivity(); + if (activity == null) { + // it's gone, there's nothing we can do here + return; + } // Save encrypted file - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); } } }); - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + deleteFileDialog.show(activity.getSupportFragmentManager(), "deleteDialog"); } else { switch (mAfterEncryptAction) { @@ -417,25 +434,24 @@ public class EncryptFilesFragment break; case COPY: - Activity activity = getActivity(); - if (activity == null) { - // it's gone, there's nothing we can do here - return; - } ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + break; + } ClipData clip = new ClipData(getString(R.string.label_clip_title), // make available as application/pgp-encrypted new String[] { "text/plain" }, new ClipData.Item(mOutputUris.get(0)) ); clipMan.setPrimaryClip(clip); - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); break; case SAVE: // Encrypted file was saved already, just show notification - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); break; } } @@ -652,7 +668,7 @@ public class EncryptFilesFragment mOutputUris.add(data.getData()); // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); } return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index cb26ea452..a849cdf12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; @@ -39,12 +40,7 @@ public class EncryptTextActivity extends EncryptActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index 886c52651..32257eba5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.Editable; @@ -33,11 +36,11 @@ import android.widget.TextView; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; @@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ShareHelper; +import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -164,13 +168,15 @@ public class EncryptTextFragment // break; // } case R.id.encrypt_copy: { + hideKeyboard(); mShareAfterEncrypt = false; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { + hideKeyboard(); mShareAfterEncrypt = true; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } default: { @@ -265,8 +271,21 @@ public class EncryptTextFragment return data; } - private void copyToClipboard(byte[] resultBytes) { - ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes)); + private void copyToClipboard(SignEncryptResult result) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + return; + } + + ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, new String(result.getResultBytes())); + clipMan.setPrimaryClip(clip); + result.createNotify(activity).show(); } /** @@ -316,18 +335,17 @@ public class EncryptTextFragment } @Override - public void onCryptoOperationSuccess(SignEncryptResult result) { + public void onQueuedOperationSuccess(SignEncryptResult result) { + super.onQueuedOperationSuccess(result); + + hideKeyboard(); if (mShareAfterEncrypt) { // Share encrypted message/file startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes())); } else { // Copy to clipboard - copyToClipboard(result.getResultBytes()); - result.createNotify(getActivity()).show(); - // Notify.create(EncryptTextActivity.this, - // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK) - // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); + copyToClipboard(result); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java index ac4b94d64..acbb695df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,7 +51,7 @@ public class HelpAboutFragment extends Fragment { try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(R.raw.help_about)); - aboutTextView.setHtmlFromString(html, true); + aboutTextView.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java index 97d39feb1..73ce7e03e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui; -import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.TypedValue; @@ -34,9 +33,6 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class HelpMarkdownFragment extends Fragment { - private Activity mActivity; - - private int mHtmlFile; public static final String ARG_MARKDOWN_RES = "htmlFile"; @@ -56,15 +52,13 @@ public class HelpMarkdownFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mActivity = getActivity(); - - mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES); + int mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES); - ScrollView scroller = new ScrollView(mActivity); - HtmlTextView text = new HtmlTextView(mActivity); + ScrollView scroller = new ScrollView(getActivity()); + HtmlTextView text = new HtmlTextView(getActivity()); // padding - int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity + int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getActivity() .getResources().getDisplayMetrics()); text.setPadding(padding, padding, padding, 0); @@ -72,8 +66,9 @@ public class HelpMarkdownFragment extends Fragment { // load markdown from raw resource try { - String html = new Markdown4jProcessor().process(getActivity().getResources().openRawResource(mHtmlFile)); - text.setHtmlFromString(html, true); + String html = new Markdown4jProcessor().process( + getActivity().getResources().openRawResource(mHtmlFile)); + text.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index ba4f759e6..4ef6c40dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -92,6 +93,7 @@ public class ImportKeysActivity extends BaseNfcActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setFullScreenDialogClose(Activity.RESULT_CANCELED, true); mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override @@ -224,9 +226,9 @@ public class ImportKeysActivity extends BaseNfcActivity Notify.Style.WARN).show(mTopFragment); // we just set the keyserver startCloudFragment(savedInstanceState, null, false, keyserver); - // it's not necessary to set the keyserver for ImportKeysListFragment since - // it'll be taken care of by ImportKeysCloudFragment when the user clicks - // the search button + // we don't set the keyserver for ImportKeysListFragment since + // it'll be set in the cloudSearchPrefs of ImportKeysCloudFragment + // which is used when the user clicks on the search button startListFragment(savedInstanceState, null, null, null, null); } else { // we allow our users to edit the query if they wish @@ -316,7 +318,8 @@ public class ImportKeysActivity extends BaseNfcActivity * specified in user preferences */ - private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) { + private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String + keyserver) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -346,7 +349,7 @@ public class ImportKeysActivity extends BaseNfcActivity } } - public void loadCallback(ImportKeysListFragment.LoaderState loaderState) { + public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { mListFragment.loadNew(loaderState); } @@ -395,7 +398,7 @@ public class ImportKeysActivity extends BaseNfcActivity } mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>( - this, this, R.string.progress_importing + 1, this, this, R.string.progress_importing ); ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); @@ -448,10 +451,8 @@ public class ImportKeysActivity extends BaseNfcActivity } @Override - protected void onNfcPerform() throws IOException { - // this displays the key or moves to the yubikey import dialogue. - super.onNfcPerform(); - // either way, finish afterwards + protected void onNfcPostExecute() throws IOException { + // either way, finish after NFC AsyncTask finish(); } @@ -463,7 +464,7 @@ public class ImportKeysActivity extends BaseNfcActivity } } - public void handleResult (ImportKeyResult result) { + public void handleResult(ImportKeyResult result) { if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index bf7e41045..53e5efabe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; +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.ImportKeysListCloudLoader; @@ -41,7 +43,9 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -62,6 +66,7 @@ public class ImportKeysListFragment extends ListFragment implements private Activity mActivity; private ImportKeysAdapter mAdapter; + private ParcelableProxy mParcelableProxy; private LoaderState mLoaderState; @@ -71,6 +76,8 @@ public class ImportKeysListFragment extends ListFragment implements private LongSparseArray<ParcelableKeyRing> mCachedKeyData; private boolean mNonInteractive; + private boolean mShowingOrbotDialog; + public LoaderState getLoaderState() { return mLoaderState; } @@ -126,6 +133,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * 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 + * Will immediately load data if non-null bytes/dataUri/serverQuery * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -141,7 +149,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * Visually consists of a list of keyrings with checkboxes to specify which are to be imported - * Can immediately load keyrings specified by any of its parameters + * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -259,6 +267,7 @@ public class ImportKeysListFragment extends ListFragment implements } public void loadNew(LoaderState loaderState) { + mLoaderState = loaderState; restartLoaders(); @@ -301,7 +310,8 @@ public class ImportKeysListFragment extends ListFragment implements } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; - return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs); + return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, + mParcelableProxy); } default: @@ -349,6 +359,46 @@ public class ImportKeysListFragment extends ListFragment implements if (getKeyResult.success()) { // No error + } else if (getKeyResult.isPending()) { + if (getKeyResult.getRequiredInputParcel().mType == + RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) { + if (mShowingOrbotDialog) { + // to prevent dialogs stacking + return; + } + + // this is because we can't commit fragment dialogs in onLoadFinished + Runnable showOrbotDialog = new Runnable() { + @Override + public void run() { + final Runnable ignoreTor = new Runnable() { + @Override + public void run() { + mParcelableProxy = ParcelableProxy + .getForNoProxy(); + mShowingOrbotDialog = false; + restartLoaders(); + } + }; + + final Runnable dialogDismiss = new Runnable() { + @Override + public void run() { + mShowingOrbotDialog = false; + } + }; + + if (OrbotHelper.putOrbotInRequiredState( + ignoreTor, dialogDismiss, getActivity())) { + // looks like we didn't have to show the + // dialog after all + restartLoaders(); + } + } + }; + new Handler().post(showOrbotDialog ); + mShowingOrbotDialog = true; + } } else { getKeyResult.createNotify(getActivity()).show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index da713d0d8..b60f3984c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -110,7 +110,16 @@ public class ImportKeysProxyActivity extends FragmentActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { - mImportOpHelper.cryptoOperation(); + if (!mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) { + // if a result has been returned, and it does not belong to mImportOpHelper, + // return it down to other activity + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + returnResult(data); + } else { + super.onActivityResult(requestCode, resultCode, data); + finish(); + } + } } if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) { @@ -128,13 +137,6 @@ public class ImportKeysProxyActivity extends FragmentActivity return; } - // if a result has been returned, return it down to other activity - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - returnResult(data); - } else { - super.onActivityResult(requestCode, resultCode, data); - finish(); - } } private void processScannedContent(String content) { @@ -157,8 +159,7 @@ public class ImportKeysProxyActivity extends FragmentActivity returnResult(intent); return; } - - String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); + final String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); if (!fingerprint.matches("[a-fA-F0-9]{40}")) { SingletonResult result = new SingletonResult( SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE_FP); @@ -222,7 +223,7 @@ public class ImportKeysProxyActivity extends FragmentActivity mKeyList = keyRings; - mImportOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } @@ -256,7 +257,6 @@ public class ImportKeysProxyActivity extends FragmentActivity Intent data = new Intent(); data.putExtras(returnData); returnResult(data); - return; } @Override @@ -273,7 +273,8 @@ public class ImportKeysProxyActivity extends FragmentActivity // only one message sent during the beam NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present - byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + final byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + importKeys(receivedKeyringBytes); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index e038cf948..0488d8235 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -18,6 +18,11 @@ package org.sufficientlysecure.keychain.ui; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; @@ -28,9 +33,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -51,12 +53,10 @@ import android.widget.TextView; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -65,22 +65,15 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; - import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; @@ -93,10 +86,9 @@ public class KeyListFragment extends LoaderFragment LoaderManager.LoaderCallbacks<Cursor>, FabContainer, CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { - static final int REQUEST_REPEAT_PASSPHRASE = 1; - static final int REQUEST_ACTION = 2; - - ExportHelper mExportHelper; + static final int REQUEST_ACTION = 1; + private static final int REQUEST_DELETE = 2; + private static final int REQUEST_VIEW_KEY = 3; private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; @@ -116,18 +108,6 @@ public class KeyListFragment extends LoaderFragment // for ConsolidateOperation private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper; - // This ids for multiple key export. - private ArrayList<Long> mIdsForRepeatAskPassphrase; - // This index for remembering the number of master key. - private int mIndex; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mExportHelper = new ExportHelper(getActivity()); - } - /** * Load custom layout with StickyListView from library */ @@ -238,19 +218,7 @@ public class KeyListFragment extends LoaderFragment } case R.id.menu_key_list_multi_delete: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_export: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showMultiExportDialog(ids); - break; - } - case R.id.menu_key_list_multi_select_all: { - // select all - for (int i = 0; i < mAdapter.getCount(); i++) { - mStickyList.setItemChecked(i, true); - } + showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected()); break; } } @@ -366,7 +334,7 @@ public class KeyListFragment extends LoaderFragment Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); viewIntent.setData( KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position))); - startActivity(viewIntent); + startActivityForResult(viewIntent, REQUEST_VIEW_KEY); } protected void encrypt(ActionMode mode, long[] masterKeyIds) { @@ -384,38 +352,15 @@ public class KeyListFragment extends LoaderFragment * * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ - public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { - // Can only work on singular secret keys - if (hasSecret && masterKeyIds.length > 1) { - Notify.create(getActivity(), R.string.secret_cannot_multiple, - Notify.Style.ERROR).show(); - return; + public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) { + Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); + if (hasSecret) { + intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(getActivity()).getPreferredKeyserver()); } - - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.arg1 == DeleteKeyDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data != null) { - DeleteResult result = data.getParcelable(DeleteResult.EXTRA_RESULT); - if (result != null) { - result.createNotify(getActivity()).show(); - } - } - mode.finish(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - masterKeyIds); - - deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); + startActivityForResult(intent, REQUEST_DELETE); } @@ -470,18 +415,10 @@ public class KeyListFragment extends LoaderFragment createKey(); return true; - case R.id.menu_key_list_export: - mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); - return true; - case R.id.menu_key_list_update_all_keys: updateAllKeys(); return true; - case R.id.menu_key_list_debug_cons: - consolidate(); - return true; - case R.id.menu_key_list_debug_read: try { KeychainDatabase.debugBackup(getActivity(), true); @@ -512,6 +449,10 @@ public class KeyListFragment extends LoaderFragment getActivity().finish(); return true; + case R.id.menu_key_list_debug_cons: + consolidate(); + return true; + default: return super.onOptionsItemSelected(item); } @@ -602,7 +543,7 @@ public class KeyListFragment extends LoaderFragment mKeyserver = cloudPrefs.keyserver; } - mImportOpHelper = new CryptoOperationHelper<>(this, + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); mImportOpHelper.cryptoOperation(); } @@ -639,45 +580,11 @@ public class KeyListFragment extends LoaderFragment }; mConsolidateOpHelper = - new CryptoOperationHelper<>(this, callback, R.string.progress_importing); + new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } - private void showMultiExportDialog(long[] masterKeyIds) { - mIdsForRepeatAskPassphrase = new ArrayList<>(); - for (long id : masterKeyIds) { - try { - if (PassphraseCacheService.getCachedPassphrase( - getActivity(), id, id) == null) { - mIdsForRepeatAskPassphrase.add(id); - } - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - // and ignore this key. - } - } - mIndex = 0; - if (mIdsForRepeatAskPassphrase.size() != 0) { - startPassphraseActivity(); - return; - } - long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()]; - for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) { - idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i); - } - mExportHelper.showExportKeysDialog(idsForMultiExport, - Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - } - - private void startPassphraseActivity() { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++); - intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId); - startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE); - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { @@ -688,31 +595,37 @@ public class KeyListFragment extends LoaderFragment mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); } - if (requestCode == REQUEST_REPEAT_PASSPHRASE) { - if (resultCode != Activity.RESULT_OK) { - return; - } - if (mIndex < mIdsForRepeatAskPassphrase.size()) { - startPassphraseActivity(); - return; - } - long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()]; - for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) { - idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i); - } - mExportHelper.showExportKeysDialog(idsForMultiExport, - Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - } + switch (requestCode) { + case REQUEST_DELETE: + if (mActionMode != null) { + mActionMode.finish(); + } + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + break; - if (requestCode == REQUEST_ACTION) { - // if a result has been returned, display a notify - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(getActivity()).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } + case REQUEST_ACTION: + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + break; + + case REQUEST_VIEW_KEY: + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + break; } } @@ -763,8 +676,11 @@ public class KeyListFragment extends LoaderFragment private HashMap<Integer, Boolean> mSelection = new HashMap<>(); + private Context mContext; + public KeyListAdapter(Context context, Cursor c, int flags) { super(context, c, flags); + mContext = context; } @Override @@ -793,9 +709,11 @@ public class KeyListFragment extends LoaderFragment // let the adapter handle setting up the row views View v = super.getView(position, convertView, parent); + int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); + if (mSelection.get(position) != null) { // selected position color - v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + v.setBackgroundColor(colorEmphasis); } else { // default color v.setBackgroundColor(Color.TRANSPARENT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 3c8fbf8c9..1cd1a3099 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; @@ -57,10 +58,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe OperationResult mResult; public static final String EXTRA_RESULT = "log"; + protected int mTextColor; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mTextColor = FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorText); setHasOptionsMenu(true); } @@ -357,13 +360,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(), subEntry.mParameters)); } - ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (subEntry.mType.mLevel) { case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case INFO: ih.mSecondImg.setBackgroundColor(mTextColor); break; case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case START: ih.mSecondImg.setBackgroundColor(mTextColor); break; case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } @@ -388,13 +391,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe entry.mParameters)); } convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); - ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (entry.mType.mLevel) { case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break; + case INFO: ih.mImg.setBackgroundColor(mTextColor); break; case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mImg.setBackgroundColor(Color.BLACK); break; + case START: ih.mImg.setBackgroundColor(mTextColor); break; case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index ec6fd1bbe..6f5d98afd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -33,6 +33,7 @@ import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.iconics.typeface.FontAwesome; import com.mikepenz.materialdrawer.Drawer; +import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; @@ -48,14 +49,15 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac static final int ID_KEYS = 1; static final int ID_ENCRYPT_DECRYPT = 2; static final int ID_APPS = 3; - static final int ID_SETTINGS = 4; - static final int ID_HELP = 5; + static final int ID_BACKUP = 4; + static final int ID_SETTINGS = 5; + static final int ID_HELP = 6; // both of these are used for instrumentation testing only public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time"; public static final String EXTRA_INIT_FRAG = "init_frag"; - public Drawer.Result mDrawerResult; + public Drawer mDrawer; private Toolbar mToolbar; @Override @@ -67,7 +69,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac mToolbar.setTitle(R.string.app_name); setSupportActionBar(mToolbar); - mDrawerResult = new Drawer() + mDrawer = new DrawerBuilder() .withActivity(this) .withHeader(R.layout.main_drawer_header) .withToolbar(mToolbar) @@ -77,7 +79,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock) .withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false), new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps) - .withIdentifier(ID_APPS).withCheckable(false) + .withIdentifier(ID_APPS).withCheckable(false), + new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore) + .withIdentifier(ID_BACKUP).withCheckable(false) ) .addStickyDrawerItems( // display and stick on bottom of drawer @@ -86,7 +90,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { + public boolean onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { if (drawerItem != null) { Intent intent = null; switch(drawerItem.getIdentifier()) { @@ -99,6 +103,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac case ID_APPS: onAppsSelected(); break; + case ID_BACKUP: + onBackupSelected(); + break; case ID_SETTINGS: intent = new Intent(MainActivity.this, SettingsActivity.class); break; @@ -110,6 +117,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac MainActivity.this.startActivity(intent); } } + + return false; } }) .withSelectedItem(-1) @@ -128,6 +137,11 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac getSupportFragmentManager().addOnBackStackChangedListener(this); + // all further initialization steps are saved as instance state + if (savedInstanceState != null) { + return; + } + Intent data = getIntent(); // If we got an EXTRA_RESULT in the intent, show the notification if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { @@ -135,20 +149,18 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac result.createNotify(this).show(); } - if (savedInstanceState == null) { - // always initialize keys fragment to the bottom of the backstack - onKeysSelected(); - - if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { - // initialize FragmentLayout with KeyListFragment at first - switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { - case ID_ENCRYPT_DECRYPT: - onEnDecryptSelected(); - break; - case ID_APPS: - onAppsSelected(); - break; - } + // always initialize keys fragment to the bottom of the backstack + onKeysSelected(); + + if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { + // initialize FragmentLayout with KeyListFragment at first + switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { + case ID_ENCRYPT_DECRYPT: + onEnDecryptSelected(); + break; + case ID_APPS: + onAppsSelected(); + break; } } @@ -170,37 +182,44 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac private void onKeysSelected() { mToolbar.setTitle(R.string.app_name); - mDrawerResult.setSelectionByIdentifier(ID_KEYS, false); + mDrawer.setSelectionByIdentifier(ID_KEYS, false); Fragment frag = new KeyListFragment(); setFragment(frag, false); } private void onEnDecryptSelected() { mToolbar.setTitle(R.string.nav_encrypt_decrypt); - mDrawerResult.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); + mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); Fragment frag = new EncryptDecryptOverviewFragment(); setFragment(frag, true); } private void onAppsSelected() { mToolbar.setTitle(R.string.nav_apps); - mDrawerResult.setSelectionByIdentifier(ID_APPS, false); + mDrawer.setSelectionByIdentifier(ID_APPS, false); Fragment frag = new AppsListFragment(); setFragment(frag, true); } + private void onBackupSelected() { + mToolbar.setTitle(R.string.nav_backup); + mDrawer.setSelectionByIdentifier(ID_APPS, false); + Fragment frag = new BackupFragment(); + setFragment(frag, true); + } + @Override protected void onSaveInstanceState(Bundle outState) { // add the values which need to be saved from the drawer to the bundle - outState = mDrawerResult.saveInstanceState(outState); + outState = mDrawer.saveInstanceState(outState); super.onSaveInstanceState(outState); } @Override public void onBackPressed() { // close the drawer first and if the drawer is closed do regular backstack handling - if (mDrawerResult != null && mDrawerResult.isDrawerOpen()) { - mDrawerResult.closeDrawer(); + if (mDrawer != null && mDrawer.isDrawerOpen()) { + mDrawer.closeDrawer(); } else { super.onBackPressed(); } @@ -238,11 +257,17 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac // make sure the selected icon is the one shown at this point if (frag instanceof KeyListFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_KEYS), false); + mToolbar.setTitle(R.string.app_name); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false); } else if (frag instanceof EncryptDecryptOverviewFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false); + mToolbar.setTitle(R.string.nav_encrypt_decrypt); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false); } else if (frag instanceof AppsListFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false); + mToolbar.setTitle(R.string.nav_apps); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false); + } else if (frag instanceof BackupFragment) { + mToolbar.setTitle(R.string.nav_backup); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index addfb6a23..1584b87ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -7,8 +7,13 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; +import android.view.View; import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -28,27 +33,37 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Date; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant * NFC devices. * <p/> * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created + * internally and is NOT meant to be used by signing operations before adding signature time */ public class NfcOperationActivity extends BaseNfcActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; // passthrough for OpenPgpService public static final String EXTRA_SERVICE_INTENT = "data"; - public static final String RESULT_DATA = "result_data"; + public static final String RESULT_CRYPTO_INPUT = "result_data"; + + public ViewAnimator vAnimator; + public TextView vErrorText; + public Button vErrorTryAgainButton; private RequiredInputParcel mRequiredInput; private Intent mServiceIntent; private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + private CryptoInputParcel mInputParcel; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,6 +71,34 @@ public class NfcOperationActivity extends BaseNfcActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + + if (mInputParcel == null) { + // for compatibility when used from OpenPgpService + // (or any place other than CryptoOperationHelper) + // NOTE: This CryptoInputParcel cannot be used for signing without adding signature time + mInputParcel = new CryptoInputParcel(); + } + + setTitle(R.string.nfc_text); + + vAnimator = (ViewAnimator) findViewById(R.id.view_animator); + vAnimator.setDisplayedChild(0); + vErrorText = (TextView) findViewById(R.id.nfc_activity_3_error_text); + vErrorTryAgainButton = (Button) findViewById(R.id.nfc_activity_3_error_try_again); + vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resumeTagHandling(); + + // obtain passphrase for this subkey + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) { + obtainYubiKeyPin(mRequiredInput); + } + vAnimator.setDisplayedChild(0); + } + }); + Intent intent = getIntent(); Bundle data = intent.getExtras(); @@ -70,33 +113,44 @@ public class NfcOperationActivity extends BaseNfcActivity { @Override protected void initLayout() { - setContentView(R.layout.nfc_activity); + setContentView(R.layout.nfc_operation_activity); } @Override - protected void onNfcPerform() throws IOException { + public void onNfcPreExecute() { + // start with indeterminate progress + vAnimator.setDisplayedChild(1); + } - CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime); + @Override + protected void doNfcInBackground() throws IOException { switch (mRequiredInput.mType) { case NFC_DECRYPT: { - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] hash = mRequiredInput.mInputHashes[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); - inputParcel.addCryptoData(hash, decryptedSessionKey); + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { + byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; + byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; } case NFC_SIGN: { - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] hash = mRequiredInput.mInputHashes[i]; + if (mInputParcel.getSignatureTime() == null) { + mInputParcel.addSignatureTime(new Date()); + } + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { + byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; byte[] signedHash = nfcCalculateSignature(hash, algo); - inputParcel.addCryptoData(hash, signedHash); + mInputParcel.addCryptoData(hash, signedHash); } break; } case NFC_MOVE_KEY_TO_CARD: { + // TODO: assume PIN and Admin PIN to be default for this operation + mPin = new Passphrase("123456"); + mAdminPin = new Passphrase("12345678"); + ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; try { @@ -107,8 +161,11 @@ public class NfcOperationActivity extends BaseNfcActivity { throw new IOException("Couldn't find subkey for key to card operation."); } - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] subkeyBytes = mRequiredInput.mInputHashes[i]; + byte[] newPin = mRequiredInput.mInputData[0]; + byte[] newAdminPin = mRequiredInput.mInputData[1]; + + for (int i = 2; i < mRequiredInput.mInputData.length; i++) { + byte[] subkeyBytes = mRequiredInput.mInputData[i]; ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); long subkeyId = buf.getLong(); @@ -155,21 +212,71 @@ public class NfcOperationActivity extends BaseNfcActivity { throw new IOException("Inappropriate key flags for smart card key."); } - inputParcel.addCryptoData(subkeyBytes, cardSerialNumber); + // TODO: Is this really needed? + mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber); } + + // change PINs afterwards + nfcModifyPIN(0x81, newPin); + nfcModifyPIN(0x83, newAdminPin); + + break; + } + default: { + throw new AssertionError("Unhandled mRequiredInput.mType"); } } + } + + @Override + protected void onNfcPostExecute() throws IOException { if (mServiceIntent != null) { - CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel); + // if we're triggered by OpenPgpService + CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel); + mServiceIntent.putExtra(EXTRA_CRYPTO_INPUT, + getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT)); setResult(RESULT_OK, mServiceIntent); } else { Intent result = new Intent(); - result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel); + result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel); + // send back the CryptoInputParcel we receive, to conform with the pattern in + // CryptoOperationHelper setResult(RESULT_OK, result); } - finish(); + // show finish + vAnimator.setDisplayedChild(2); + + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + // check all 200ms if YubiKey has been taken away + while (true) { + if (isNfcConnected()) { + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + } else { + return null; + } + } + } + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } + + @Override + protected void onNfcError(String error) { + pauseTagHandling(); + + vErrorText.setText(error + "\n\n" + getString(R.string.nfc_try_again_text)); + vAnimator.setDisplayedChild(3); } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { @@ -199,8 +306,6 @@ public class NfcOperationActivity extends BaseNfcActivity { // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); - - obtainYubiKeyPin(mRequiredInput); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java new file mode 100644 index 000000000..587044659 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a + * dialog to enable orbot. + */ +public class OrbotRequiredDialogActivity extends FragmentActivity { + + // to provide any previous crypto input into which proxy preference is merged + public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; + + public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; + + private CryptoInputParcel mCryptoInputParcel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + if (mCryptoInputParcel == null) { + mCryptoInputParcel = new CryptoInputParcel(); + } + showDialog(); + } + + /** + * Displays an install or start orbot dialog depending on orbot's presence and state + */ + public void showDialog() { + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + Intent intent = new Intent(); + mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy()); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + finish(); + } + }; + + Runnable dialogDismissed = new Runnable() { + @Override + public void run() { + finish(); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, dialogDismissed, + Preferences.getPreferences(OrbotRequiredDialogActivity.this) + .getProxyPrefs(), + OrbotRequiredDialogActivity.this)) { + // no action required after all + Intent intent = new Intent(); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + } + } + }); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index bb12cd7ff..d7224bd04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.view.ContextThemeWrapper; @@ -58,6 +59,7 @@ import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -65,17 +67,22 @@ import org.sufficientlysecure.keychain.util.Preferences; /** * We can not directly create a dialog on the application context. * This activity encapsulates a DialogFragment to emulate a dialog. + * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created + * internally and is NOT meant to be used by signing operations before adding a signature time */ public class PassphraseDialogActivity extends FragmentActivity { public static final String RESULT_CRYPTO_INPUT = "result_data"; public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_SUBKEY_ID = "secret_key_id"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; // special extra for OpenPgpService public static final String EXTRA_SERVICE_INTENT = "data"; private long mSubKeyId; + private CryptoInputParcel mCryptoInputParcel; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -89,6 +96,15 @@ public class PassphraseDialogActivity extends FragmentActivity { ); } + mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + + if (mCryptoInputParcel == null) { + // not all usages of PassphraseActivity are from CryptoInputOperation + // NOTE: This CryptoInputParcel cannot be used for signing operations without setting + // signature time + mCryptoInputParcel = new CryptoInputParcel(); + } + // this activity itself has no content view (see manifest) if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) { @@ -112,7 +128,8 @@ public class PassphraseDialogActivity extends FragmentActivity { SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); - returnIntent.putExtra(RESULT_CRYPTO_INPUT, new CryptoInputParcel(new Passphrase(""))); + mCryptoInputParcel.mPassphrase = new Passphrase(""); + returnIntent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); setResult(RESULT_OK, returnIntent); finish(); return; @@ -136,8 +153,8 @@ public class PassphraseDialogActivity extends FragmentActivity { } @Override - protected void onResume() { - super.onResume(); + protected void onResumeFragments() { + super.onResumeFragments(); /* Show passphrase dialog to cache a new passphrase the user enters for using it later for * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks @@ -175,14 +192,12 @@ public class PassphraseDialogActivity extends FragmentActivity { private Intent mServiceIntent; + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - // if the dialog is displayed from the application class, design is missing - // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay - ContextThemeWrapper theme = new ContextThemeWrapper(activity, - R.style.Theme_AppCompat_Light_Dialog); + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID); mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT); @@ -299,10 +314,10 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); - if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); + if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) + || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { + mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); + mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); } else { mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } @@ -328,11 +343,16 @@ public class PassphraseDialogActivity extends FragmentActivity { public void onClick(View v) { final Passphrase passphrase = new Passphrase(mPassphraseEditText); + CryptoInputParcel cryptoInputParcel = + ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + // Early breakout if we are dealing with a symmetric key if (mSecretRing == null) { - PassphraseCacheService.addCachedPassphrase(getActivity(), - Constants.key.symmetric, Constants.key.symmetric, passphrase, - getString(R.string.passp_cache_notif_pwd)); + if (cryptoInputParcel.mCachePassphrase) { + PassphraseCacheService.addCachedPassphrase(getActivity(), + Constants.key.symmetric, Constants.key.symmetric, passphrase, + getString(R.string.passp_cache_notif_pwd)); + } finishCaching(passphrase); return; @@ -385,15 +405,24 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - // cache the new passphrase - Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); + // cache the new passphrase as specified in CryptoInputParcel + Log.d(Constants.TAG, "Everything okay!"); - try { - PassphraseCacheService.addCachedPassphrase(getActivity(), - mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, - mSecretRing.getPrimaryUserIdWithFallback()); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "adding of a passphrase failed", e); + CryptoInputParcel cryptoInputParcel + = ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + + if (cryptoInputParcel.mCachePassphrase) { + Log.d(Constants.TAG, "Caching entered passphrase"); + + try { + PassphraseCacheService.addCachedPassphrase(getActivity(), + mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, + mSecretRing.getPrimaryUserIdWithFallback()); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "adding of a passphrase failed", e); + } + } else { + Log.d(Constants.TAG, "Not caching entered passphrase!"); } finishCaching(passphrase); @@ -409,9 +438,12 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase); + CryptoInputParcel inputParcel = + ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + inputParcel.mPassphrase = passphrase; if (mServiceIntent != null) { - CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel); + CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, + inputParcel); getActivity().setResult(RESULT_OK, mServiceIntent); } else { // also return passphrase back to activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java index 2e838535d..e55494145 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; @@ -43,7 +43,9 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -207,7 +209,7 @@ public class PassphraseWizardActivity extends FragmentActivity { // transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to write on NFC tag", e); } } else if (readNFC && AUTHENTICATION.equals(selectedAction)) { @@ -232,7 +234,7 @@ public class PassphraseWizardActivity extends FragmentActivity { } } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read NFC tag", e); } } } @@ -263,7 +265,7 @@ public class PassphraseWizardActivity extends FragmentActivity { try { password = readText(ndefRecord); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read password from tag", e); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java new file mode 100644 index 000000000..2a00e8b70 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; + +public class RetryUploadDialogActivity extends FragmentActivity { + + public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; + + public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + UploadRetryDialogFragment.newInstance().show(getSupportFragmentManager(), + "uploadRetryDialog"); + } + + public static class UploadRetryDialogFragment extends DialogFragment { + public static UploadRetryDialogFragment newInstance() { + return new UploadRetryDialogFragment(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity()); + + CustomAlertDialogBuilder dialogBuilder = new CustomAlertDialogBuilder(theme); + dialogBuilder.setTitle(R.string.retry_up_dialog_title); + dialogBuilder.setMessage(R.string.retry_up_dialog_message); + + dialogBuilder.setNegativeButton(R.string.retry_up_dialog_btn_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + }); + + dialogBuilder.setPositiveButton(R.string.retry_up_dialog_btn_reupload, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(); + intent.putExtra(RESULT_CRYPTO_INPUT, getActivity() + .getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT)); + getActivity().setResult(RESULT_OK, intent); + getActivity().finish(); + } + }); + + return dialogBuilder.show(); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index a1cb77546..534dbfd05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; @@ -81,7 +82,7 @@ public class SafeSlingerActivity extends BaseActivity }); ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image); - buttonIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + buttonIcon.setColorFilter(FormattingUtils.getColorFromAttr(this, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View button = findViewById(R.id.safe_slinger_button); @@ -144,7 +145,7 @@ public class SafeSlingerActivity extends BaseActivity cache.writeCache(it.size(), it.iterator()); mOperationHelper = - new CryptoOperationHelper(this, this, R.string.progress_importing); + new CryptoOperationHelper(1, this, this, R.string.progress_importing); mKeyList = null; mKeyserver = null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index a552e1c55..2fe868b8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -18,14 +18,21 @@ package org.sufficientlysecure.keychain.ui; +import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; @@ -33,8 +40,12 @@ import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.List; @@ -42,15 +53,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD"; public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; + public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY"; + public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI"; public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; private PreferenceScreen mKeyServerPreference = null; private static Preferences sPreferences; + private ThemeChanger mThemeChanger; @Override protected void onCreate(Bundle savedInstanceState) { sPreferences = Preferences.getPreferences(this); + + mThemeChanger = new ThemeChanger(this); + mThemeChanger.changeTheme(); super.onCreate(savedInstanceState); setupToolbar(); @@ -95,6 +112,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity { initializeUseNumKeypadForYubiKeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); + } else if (action != null && action.equals(ACTION_PREFS_GUI)) { + addPreferencesFromResource(R.xml.gui_preferences); + + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mThemeChanger.changeTheme()) { + Intent intent = getIntent(); + finish(); + startActivity(intent); } } @@ -124,27 +156,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); - mKeyServerPreference.setSummary(keyserverSummary(this)); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - } - - @Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); loadHeadersFromResource(R.xml.preference_headers, target); @@ -190,12 +201,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); + // update preference, in case it changed mKeyServerPreference.setSummary(keyserverSummary(getActivity())); break; } @@ -234,9 +240,236 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } } + public static class ProxyPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + new Initializer(this).initialize(); + + } + + public static class Initializer { + private CheckBoxPreference mUseTor; + private CheckBoxPreference mUseNormalProxy; + private EditTextPreference mProxyHost; + private EditTextPreference mProxyPort; + private ListPreference mProxyType; + private PreferenceActivity mActivity; + private PreferenceFragment mFragment; + + public Initializer(PreferenceFragment fragment) { + mFragment = fragment; + } + + public Initializer(PreferenceActivity activity) { + mActivity = activity; + } + + public Preference automaticallyFindPreference(String key) { + if (mFragment != null) { + return mFragment.findPreference(key); + } else { + return mActivity.findPreference(key); + } + } + + public void initialize() { + // makes android's preference framework write to our file instead of default + // This allows us to use the "persistent" attribute to simplify code + if (mFragment != null) { + Preferences.setPreferenceManagerFileAndMode(mFragment.getPreferenceManager()); + // Load the preferences from an XML resource + mFragment.addPreferencesFromResource(R.xml.proxy_prefs); + } else { + Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager()); + // Load the preferences from an XML resource + mActivity.addPreferencesFromResource(R.xml.proxy_prefs); + } + + mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); + mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); + mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST); + mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT); + mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE); + initializeUseTorPref(); + initializeUseNormalProxyPref(); + initializeEditTextPreferences(); + initializeProxyTypePreference(); + + if (mUseTor.isChecked()) { + disableNormalProxyPrefs(); + } + else if (mUseNormalProxy.isChecked()) { + disableUseTorPrefs(); + } else { + disableNormalProxySettings(); + } + } + + private void initializeUseTorPref() { + mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if ((Boolean) newValue) { + boolean installed = OrbotHelper.isOrbotInstalled(activity); + if (!installed) { + Log.d(Constants.TAG, "Prompting to install Tor"); + OrbotHelper.getPreferenceInstallDialogFragment().show(activity.getFragmentManager(), + "installDialog"); + // don't let the user check the box until he's installed orbot + return false; + } else { + disableNormalProxyPrefs(); + // let the enable tor box be checked + return true; + } + } else { + // we're unchecking Tor, so enable other proxy + enableNormalProxyCheckbox(); + return true; + } + } + }); + } + + private void initializeUseNormalProxyPref() { + mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if ((Boolean) newValue) { + disableUseTorPrefs(); + enableNormalProxySettings(); + } else { + enableUseTorPrefs(); + disableNormalProxySettings(); + } + return true; + } + }); + } + + private void initializeEditTextPreferences() { + mProxyHost.setSummary(mProxyHost.getText()); + mProxyPort.setSummary(mProxyPort.getText()); + + mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if (TextUtils.isEmpty((String) newValue)) { + Notify.create( + activity, + R.string.pref_proxy_host_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } else { + mProxyHost.setSummary((CharSequence) newValue); + return true; + } + } + }); + + mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + try { + int port = Integer.parseInt((String) newValue); + if (port < 0 || port > 65535) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + // no issues, save port + mProxyPort.setSummary("" + port); + return true; + } catch (NumberFormatException e) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + } + }); + } + + private void initializeProxyTypePreference() { + mProxyType.setSummary(mProxyType.getEntry()); + + mProxyType.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + CharSequence entry = mProxyType.getEntries()[mProxyType.findIndexOfValue((String) newValue)]; + mProxyType.setSummary(entry); + return true; + } + }); + } + + private void disableNormalProxyPrefs() { + mUseNormalProxy.setChecked(false); + mUseNormalProxy.setEnabled(false); + disableNormalProxySettings(); + } + + private void enableNormalProxyCheckbox() { + mUseNormalProxy.setEnabled(true); + } + + private void enableNormalProxySettings() { + mProxyHost.setEnabled(true); + mProxyPort.setEnabled(true); + mProxyType.setEnabled(true); + } + + private void disableNormalProxySettings() { + mProxyHost.setEnabled(false); + mProxyPort.setEnabled(false); + mProxyType.setEnabled(false); + } + + private void disableUseTorPrefs() { + mUseTor.setChecked(false); + mUseTor.setEnabled(false); + } + + private void enableUseTorPrefs() { + mUseTor.setEnabled(true); + } + } + } + + /** + * This fragment shows gui preferences. + */ + public static class GuiPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.gui_preferences); + + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + } + } + protected boolean isValidFragment(String fragmentName) { return AdvancedPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) + || ProxyPrefsFragment.class.getName().equals(fragmentName) + || GuiPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } @@ -265,6 +498,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } + private static void initializeTheme(final ListPreference mTheme) { + mTheme.setValue(sPreferences.getTheme()); + mTheme.setSummary(mTheme.getEntry()); + mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mTheme.setValue((String) newValue); + mTheme.setSummary(mTheme.getEntry()); + sPreferences.setTheme((String) newValue); + return false; + } + }); + } + private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) { Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); mSearchKeyserver.setChecked(prefs.searchKeyserver); @@ -295,7 +541,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { String[] servers = sPreferences.getKeyServers(); String serverSummary = context.getResources().getQuantityString( R.plurals.n_keyservers, servers.length, servers.length); - return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver(); + return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences + .getPreferredKeyserver(); } private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index dc203756f..f61ada84f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -17,89 +17,23 @@ package org.sufficientlysecure.keychain.ui; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.widget.Editor; -import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; -import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; -import java.util.Vector; - -public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener, - EditorListener { +public class SettingsKeyServerActivity extends BaseActivity { public static final String EXTRA_KEY_SERVERS = "key_servers"; - private LayoutInflater mInflater; - private ViewGroup mEditors; - private View mAdd; - private View mRotate; - private TextView mTitle; - private TextView mSummary; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Inflate a "Done"/"Cancel" custom action bar view - setFullScreenDialogDoneClose(R.string.btn_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - okClicked(); - } - }, - new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - }); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mTitle = (TextView) findViewById(R.id.title); - mSummary = (TextView) findViewById(R.id.summary); - mSummary.setText(getText(R.string.label_first_keyserver_is_used)); - - mTitle.setText(R.string.label_keyservers); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mAdd = findViewById(R.id.add); - mAdd.setOnClickListener(this); - - mRotate = findViewById(R.id.rotate); - mRotate.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - Vector<String> servers = serverList(); - String first = servers.get(0); - if (first != null) { - servers.remove(0); - servers.add(first); - String[] dummy = {}; - makeServerList(servers.toArray(dummy)); - } - } - }); - Intent intent = getIntent(); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); - makeServerList(servers); + loadFragment(savedInstanceState, servers); } @Override @@ -107,118 +41,22 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi setContentView(R.layout.key_server_preference); } - private void makeServerList(String[] servers) { - if (servers != null) { - mEditors.removeAllViews(); - for (String serv : servers) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate( - R.layout.key_server_editor, mEditors, false); - view.setEditorListener(this); - view.setValue(serv); - mEditors.addView(view); - } + private void loadFragment(Bundle savedInstanceState, String[] keyservers) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; } - } - - public void onDeleted(Editor editor, boolean wasNewItem) { - // nothing to do - } - @Override - public void onEdited() { + SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers); - } - - // button to add keyserver clicked - public void onClick(View v) { - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - Bundle data = message.getData(); - switch (message.what) { - case AddKeyserverDialogFragment.MESSAGE_OKAY: { - boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED); - if (verified) { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_verified, Notify.Style.OK).show(); - } else { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_without_verification, - Notify.Style.WARN).show(); - } - String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER); - addKeyserver(keyserver); - break; - } - case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { - AddKeyserverDialogFragment.FailureReason failureReason = - (AddKeyserverDialogFragment.FailureReason) data.getSerializable( - AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON); - switch (failureReason) { - case CONNECTION_FAILED: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_connection_failed, - Notify.Style.ERROR).show(); - break; - } - case INVALID_URL: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_invalid_url, - Notify.Style.ERROR).show(); - break; - } - } - break; - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment - .newInstance(messenger); - dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog"); - } - - public void addKeyserver(String keyserverUrl) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, - mEditors, false); - view.setEditorListener(this); - view.setValue(keyserverUrl); - mEditors.addView(view); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private Vector<String> serverList() { - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - return servers; - } - - private void okClicked() { - Intent data = new Intent(); - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - String[] dummy = new String[0]; - data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy)); - setResult(RESULT_OK, data); - finish(); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.keyserver_settings_fragment_container, fragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java new file mode 100644 index 000000000..2ae64d90b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.support.v4.view.MotionEventCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener { + + private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array"; + private ItemTouchHelper mItemTouchHelper; + + private ArrayList<String> mKeyservers; + private KeyserverListAdapter mAdapter; + + public static SettingsKeyserverFragment newInstance(String[] keyservers) { + Bundle args = new Bundle(); + args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers); + + SettingsKeyserverFragment fragment = new SettingsKeyserverFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle + savedInstanceState) { + + return inflater.inflate(R.layout.settings_keyserver_fragment, null); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY); + mKeyservers = new ArrayList<>(Arrays.asList(keyservers)); + + mAdapter = new KeyserverListAdapter(mKeyservers); + + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view); + // recyclerView.setHasFixedSize(true); // the size of the first item changes + recyclerView.setAdapter(mAdapter); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + + ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter); + mItemTouchHelper = new ItemTouchHelper(callback); + mItemTouchHelper.attachToRecyclerView(recyclerView); + + // for clicks + recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this)); + + // can't use item decoration because it doesn't move with drag and drop + // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.keyserver_pref_menu, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_add_keyserver: + startAddKeyserverDialog(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + private void startAddKeyserverDialog() { + // keyserver and position have no meaning + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1); + } + + private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action, + String keyserver, final int position) { + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + Bundle data = message.getData(); + switch (message.what) { + case AddEditKeyserverDialogFragment.MESSAGE_OKAY: { + boolean deleted = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED + , false); + if (deleted) { + Notify.create(getActivity(), + getActivity().getString( + R.string.keyserver_preference_deleted, mKeyservers.get(position)), + Notify.Style.OK) + .show(); + deleteKeyserver(position); + return; + } + boolean verified = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED); + if (verified) { + Notify.create(getActivity(), + R.string.add_keyserver_verified, Notify.Style.OK).show(); + } else { + Notify.create(getActivity(), + R.string.add_keyserver_without_verification, + Notify.Style.WARN).show(); + } + String keyserver = data.getString( + AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER); + + AddEditKeyserverDialogFragment.DialogAction dialogAction + = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION); + switch (dialogAction) { + case ADD: + addKeyserver(keyserver); + break; + case EDIT: + editKeyserver(keyserver, position); + break; + } + break; + } + case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { + AddEditKeyserverDialogFragment.FailureReason failureReason = + (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON); + switch (failureReason) { + case CONNECTION_FAILED: { + Notify.create(getActivity(), + R.string.add_keyserver_connection_failed, + Notify.Style.ERROR).show(); + break; + } + case INVALID_URL: { + Notify.create(getActivity(), + R.string.add_keyserver_invalid_url, + Notify.Style.ERROR).show(); + break; + } + } + break; + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment + .newInstance(messenger, action, keyserver, position); + dialogFragment.show(getFragmentManager(), "addKeyserverDialog"); + } + + private void addKeyserver(String keyserver) { + mKeyservers.add(keyserver); + mAdapter.notifyItemInserted(mKeyservers.size() - 1); + saveKeyserverList(); + } + + private void editKeyserver(String newKeyserver, int position) { + mKeyservers.set(position, newKeyserver); + mAdapter.notifyItemChanged(position); + saveKeyserverList(); + } + + private void deleteKeyserver(int position) { + if (mKeyservers.size() == 1) { + Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last, + Notify.Style.ERROR).show(); + return; + } + mKeyservers.remove(position); + // we use this + mAdapter.notifyItemRemoved(position); + if (position == 0 && mKeyservers.size() > 0) { + // if we deleted the first item, we need the adapter to redraw the new first item + mAdapter.notifyItemChanged(0); + } + saveKeyserverList(); + } + + private void saveKeyserverList() { + String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]); + Preferences.getPreferences(getActivity()).setKeyServers(servers); + } + + @Override + public void onItemClick(View view, int position) { + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT, + mKeyservers.get(position), position); + } + + public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder> + implements ItemTouchHelperAdapter { + + // to update the ViewHolder associated with first item, for when an item is deleted + private ViewHolder mFirstItem; + + private final List<String> mKeyservers; + + public KeyserverListAdapter(List<String> keyservers) { + mKeyservers = keyservers; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.settings_keyserver_item, parent, false); + ViewHolder viewHolder = new ViewHolder(view); + return viewHolder; + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + if (position == 0) { + mFirstItem = holder; + } + holder.keyserverUrl.setText(mKeyservers.get(position)); + + // Start a drag whenever the handle view it touched + holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + mItemTouchHelper.startDrag(holder); + } + return false; + } + }); + + selectUnselectKeyserver(holder, position); + } + + private void selectUnselectKeyserver(ViewHolder holder, int position) { + + if (position == 0) { + holder.showAsSelectedKeyserver(); + } else { + holder.showAsUnselectedKeyserver(); + } + } + + @Override + public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition) { + Collections.swap(mKeyservers, fromPosition, toPosition); + saveKeyserverList(); + selectUnselectKeyserver((ViewHolder) target, fromPosition); + // we don't want source to change color while dragging, therefore we just set + // isSelectedKeyserver instead of selectUnselectKeyserver + ((ViewHolder) source).isSelectedKeyserver = toPosition == 0; + + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public int getItemCount() { + return mKeyservers.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements + ItemTouchHelperViewHolder { + + public final ViewGroup outerLayout; + public final TextView selectedServerLabel; + public final TextView keyserverUrl; + public final ImageView dragHandleView; + + private boolean isSelectedKeyserver = false; + + public ViewHolder(View itemView) { + super(itemView); + outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout); + selectedServerLabel = (TextView) itemView.findViewById( + R.id.selected_keyserver_title); + keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv); + dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle); + + itemView.setClickable(true); + } + + public void showAsSelectedKeyserver() { + isSelectedKeyserver = true; + selectedServerLabel.setVisibility(View.VISIBLE); + outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + } + + public void showAsUnselectedKeyserver() { + isSelectedKeyserver = false; + selectedServerLabel.setVisibility(View.GONE); + outerLayout.setBackgroundColor(Color.WHITE); + } + + @Override + public void onItemSelected() { + selectedServerLabel.setVisibility(View.GONE); + itemView.setBackgroundColor(Color.LTGRAY); + } + + @Override + public void onItemClear() { + if (isSelectedKeyserver) { + showAsSelectedKeyserver(); + } else { + showAsUnselectedKeyserver(); + } + } + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 8b49f3b96..0415128a2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -26,7 +26,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -108,7 +107,7 @@ public class UploadKeyActivity extends BaseActivity String server = (String) mKeyServerSpinner.getSelectedItem(); mKeyserver = server; - mUploadOpHelper = new CryptoOperationHelper(this, this, R.string.progress_uploading); + mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } @@ -132,8 +131,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationSuccess(ExportResult result) { - Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, - Toast.LENGTH_SHORT).show(); + result.createNotify(this).show(); } @Override @@ -143,7 +141,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationError(ExportResult result) { - // TODO: Implement proper log for key upload then show error + result.createNotify(this).show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index d3849c892..1d0e085da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -31,8 +31,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.provider.ContactsContract; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; @@ -52,11 +50,12 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; + import com.getbase.floatingactionbutton.FloatingActionButton; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -66,13 +65,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -88,7 +82,6 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; public class ViewKeyActivity extends BaseNfcActivity implements LoaderManager.LoaderCallbacks<Cursor>, @@ -99,11 +92,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; static final int REQUEST_QR_FINGERPRINT = 1; - static final int REQUEST_DELETE = 2; - static final int REQUEST_EXPORT = 3; + static final int REQUEST_BACKUP = 2; + static final int REQUEST_CERTIFY = 3; + static final int REQUEST_DELETE = 4; + public static final String EXTRA_DISPLAY_RESULT = "display_result"; - ExportHelper mExportHelper; ProviderHelper mProviderHelper; protected Uri mDataUri; @@ -148,13 +142,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements private String mFingerprint; private long mMasterKeyId; + private byte[] mNfcFingerprints; + private String mNfcUserId; + private byte[] mNfcAid; + @SuppressLint("InflateParams") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mExportHelper = new ExportHelper(this); mProviderHelper = new ProviderHelper(this); + mOperationHelper = new CryptoOperationHelper<>(1, this, this, null); setTitle(null); @@ -325,31 +323,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements return true; } case R.id.menu_key_view_export_file: { - try { - if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) { - exportToFile(mDataUri, mExportHelper, mProviderHelper); - return true; - } - - startPassphraseActivity(REQUEST_EXPORT); - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - exportToFile(mDataUri, mExportHelper, mProviderHelper); - } + startPassphraseActivity(REQUEST_BACKUP); return true; } case R.id.menu_key_view_delete: { - try { - if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) { - deleteKey(); - return true; - } - - startPassphraseActivity(REQUEST_DELETE); - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - deleteKey(); - } + deleteKey(); return true; } case R.id.menu_key_view_advanced: { @@ -382,6 +360,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements public boolean onPrepareOptionsMenu(Menu menu) { MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); editKey.setVisible(mIsSecret); + MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file); + exportKey.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); @@ -399,37 +379,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); - startCertifyIntent(intent); + startActivityForResult(intent, REQUEST_CERTIFY); } private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); - startCertifyIntent(intent); - } - - private void startCertifyIntent(Intent intent) { - // Message is received after signing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - Bundle data = message.getData(); - CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); - - result.createNotify(ViewKeyActivity.this).show(); - } - } - }; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - startActivityForResult(intent, 0); + startActivityForResult(intent, REQUEST_CERTIFY); } private void showQrCodeDialog() { @@ -454,99 +411,95 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(intent, requestCode); } - private void exportToFile(Uri dataUri, ExportHelper exportHelper, ProviderHelper providerHelper) { - try { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri); - - HashMap<String, Object> data = providerHelper.getGenericData( - baseUri, - new String[] {KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, - new int[] {ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); - - exportHelper.showExportKeysDialog( - new long[] {(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, - Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0) - ); - } catch (ProviderHelper.NotFoundException e) { - Notify.create(this, R.string.error_key_not_found, Notify.Style.ERROR).show(); - Log.e(Constants.TAG, "Key not found", e); - } + private void backupToFile() { + new ExportHelper(this).showExportKeysDialog( + mMasterKeyId, Constants.Path.APP_DIR_FILE, true); } private void deleteKey() { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - setResult(RESULT_CANCELED); - finish(); - } - } - }; + Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class); + + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, + new long[]{mMasterKeyId}); + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, mIsSecret); + if (mIsSecret) { + // for upload in case key is secret + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(this).getPreferredKeyserver()); + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[] {mMasterKeyId}); - deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog"); + startActivityForResult(deleteIntent, REQUEST_DELETE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mOperationHelper != null) { - mOperationHelper.handleActivityResult(requestCode, resultCode, data); + if (mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + return; + } + + if (resultCode != Activity.RESULT_OK) { + return; } - if (requestCode == REQUEST_QR_FINGERPRINT && resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case REQUEST_QR_FINGERPRINT: { - // If there is an EXTRA_RESULT, that's an error. Just show it. - if (data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); + // If there is an EXTRA_RESULT, that's an error. Just show it. + if (data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + return; + } + + String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT); + if (fp == null) { + Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); + return; + } + if (mFingerprint.equalsIgnoreCase(fp)) { + certifyImmediate(); + } else { + Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); + } return; } - String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT); - if (fp == null) { - Notify.create(this, "Error scanning fingerprint!", - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + case REQUEST_BACKUP: { + backupToFile(); return; } - if (mFingerprint.equalsIgnoreCase(fp)) { - certifyImmediate(); - } else { - Notify.create(this, "Fingerprints did not match!", - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - } - return; - } + case REQUEST_CERTIFY: { + if (data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } + return; + } - if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK) { - deleteKey(); + case REQUEST_DELETE: { + setResult(RESULT_OK, data); + finish(); + return; + } } - if (requestCode == REQUEST_EXPORT && resultCode == Activity.RESULT_OK) { - exportToFile(mDataUri, mExportHelper, mProviderHelper); - } + super.onActivityResult(requestCode, resultCode, data); - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } } @Override - protected void onNfcPerform() throws IOException { + protected void doNfcInBackground() throws IOException { + + mNfcFingerprints = nfcGetFingerprints(); + mNfcUserId = nfcGetUserId(); + mNfcAid = nfcGetAid(); + } - final byte[] nfcFingerprints = nfcGetFingerprints(); - final String nfcUserId = nfcGetUserId(); - final byte[] nfcAid = nfcGetAid(); + @Override + protected void onNfcPostExecute() throws IOException { - long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); + long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); try { @@ -557,7 +510,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // if the master key of that key matches this one, just show the yubikey dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprint)) { - showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid); + showYubiKeyFragment(mNfcFingerprints, mNfcUserId, mNfcAid); return; } @@ -570,9 +523,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); finish(); } @@ -586,15 +539,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); finish(); } }, R.string.snack_yubikey_import).show(); } - } public void showYubiKeyFragment( @@ -629,7 +581,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements long keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[] {keyId}; + long[] encryptionKeyIds = new long[]{keyId}; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -647,38 +599,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements } } - private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) - throws ProviderHelper.NotFoundException { - - mIsRefreshing = true; - mRefreshItem.setEnabled(false); - mRefreshItem.setActionView(mRefresh); - mRefresh.startAnimation(mRotate); - - byte[] blob = (byte[]) providerHelper.getGenericData( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), - KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); - ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); - entries.add(keyEntry); - mKeyList = entries; - - // search config - { - Preferences prefs = Preferences.getPreferences(this); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - mKeyserver = cloudPrefs.keyserver; - } - - mOperationHelper = new CryptoOperationHelper<>( - this, this, R.string.progress_importing); - - mOperationHelper.cryptoOperation(); - } - private void editKey(Uri dataUri) { Intent editIntent = new Intent(this, EditKeyActivity.class); editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); @@ -735,7 +655,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[] { + static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, @@ -771,6 +691,25 @@ public class ViewKeyActivity extends BaseNfcActivity implements int mPreviousColor = 0; + /** + * Calculate a reasonable color for the status bar based on the given toolbar color. + * Style guides want the toolbar color to be a "700" on the Android scale and the status + * bar should be the same color at "500", this is roughly 17 / 20th of the value in each + * channel. + * http://www.google.com/design/spec/style/color.html#color-color-palette + */ + static public int getStatusBarBackgroundColor(int color) { + int r = (color >> 16) & 0xff; + int g = (color >> 8) & 0xff; + int b = color & 0xff; + + r = r * 17 / 20; + g = g * 17 / 20; + b = b * 17 / 20; + + return (0xff << 24) | (r << 16) | (g << 8) | b; + } + @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, @@ -823,7 +762,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements AsyncTask<Long, Void, Bitmap> photoTask = new AsyncTask<Long, Void, Bitmap>() { protected Bitmap doInBackground(Long... mMasterKeyId) { - return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); + return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), + mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { @@ -839,7 +779,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.REVOKED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.GONE); mActionEncryptText.setVisibility(View.GONE); @@ -855,7 +795,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.EXPIRED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.GONE); mActionEncryptText.setVisibility(View.GONE); @@ -865,7 +805,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements } else if (mIsSecret) { mStatusText.setText(R.string.view_key_my_key); mStatusImage.setVisibility(View.GONE); - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.key_flag_green); // reload qr code only if the fingerprint changed if (!mFingerprint.equals(mQrCodeLoaded)) { loadQrCode(mFingerprint); @@ -903,6 +843,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mActionNfc.setVisibility(View.GONE); } mFab.setVisibility(View.VISIBLE); + // noinspection deprecation (no getDrawable with theme at current minApi level 15!) mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { mActionEncryptFile.setVisibility(View.VISIBLE); @@ -915,7 +856,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.VERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.key_flag_green); photoTask.execute(mMasterKeyId); mFab.setVisibility(View.GONE); @@ -924,20 +865,21 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.UNVERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.android_orange_light); + color = getResources().getColor(R.color.key_flag_orange); mFab.setVisibility(View.VISIBLE); } } if (mPreviousColor == 0 || mPreviousColor == color) { - mStatusBar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color)); mBigToolbar.setBackgroundColor(color); mPreviousColor = color; } else { ObjectAnimator colorFade1 = ObjectAnimator.ofObject(mStatusBar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); + new ArgbEvaluator(), mPreviousColor, + getStatusBarBackgroundColor(color)); ObjectAnimator colorFade2 = ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); @@ -965,6 +907,36 @@ public class ViewKeyActivity extends BaseNfcActivity implements // CryptoOperationHelper.Callback functions + + private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) + throws ProviderHelper.NotFoundException { + + mIsRefreshing = true; + mRefreshItem.setEnabled(false); + mRefreshItem.setActionView(mRefresh); + mRefresh.startAnimation(mRotate); + + byte[] blob = (byte[]) providerHelper.getGenericData( + KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), + KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); + + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); + entries.add(keyEntry); + mKeyList = entries; + + // search config + { + Preferences prefs = Preferences.getPreferences(this); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + mKeyserver = cloudPrefs.keyserver; + } + + mOperationHelper.cryptoOperation(); + } + @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); @@ -989,6 +961,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; + return true; } -}
\ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 6669f2654..673092e61 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -211,18 +211,18 @@ public class ViewKeyAdvActivity extends BaseActivity implements // Note: order is important int color; if (isRevoked || isExpired) { - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); } else if (isSecret) { - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.android_green_light); } else { if (isVerified) { - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.android_green_light); } else { - color = getResources().getColor(R.color.android_orange_light); + color = getResources().getColor(R.color.key_flag_orange); } } mToolbar.setBackgroundColor(color); - mStatusBar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color)); mSlidingTabLayout.setBackgroundColor(color); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index b44e6dc78..4a46896bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -25,6 +25,9 @@ import java.io.OutputStreamWriter; import android.app.Activity; import android.app.ActivityOptions; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; @@ -49,16 +52,18 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; +import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; @@ -79,6 +84,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private Uri mDataUri; private byte[] mFingerprint; + private long mMasterKeyId; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -101,11 +107,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share); + View vKeySafeButton = view.findViewById(R.id.view_key_action_key_export); View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc); View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger); View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload); - vKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); vFingerprintShareButton.setOnClickListener(new View.OnClickListener() { @@ -126,6 +133,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements share(false, false); } }); + vKeySafeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(); + } + }); vKeyClipboardButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -161,6 +174,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements return root; } + private void exportToFile() { + new ExportHelper(getActivity()).showExportKeysDialog( + mMasterKeyId, Constants.Path.APP_DIR_FILE, false); + } + private void startSafeSlinger(Uri dataUri) { long keyId = 0; try { @@ -197,7 +215,15 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } if (toClipboard) { - ClipboardReflection.copyToClipboard(activity, content); + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR); + return; + } + + ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content); + clipMan.setPrimaryClip(clip); + Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard : R.string.key_copied_to_clipboard, Notify.Style.OK).show(); return; @@ -344,6 +370,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements /** Load QR Code asynchronously and with a fade in animation */ private void setFingerprint(byte[] fingerprintBlob) { mFingerprint = fingerprintBlob; + mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintBlob); final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index 092ab40d6..b118c3ad0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -51,6 +51,9 @@ import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Hashtable; @@ -197,8 +200,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements mStartSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mStartSearch.setEnabled(false); - new DescribeKey().execute(fingerprint); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } } }); } @@ -229,6 +246,11 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // look for evidence from keybase in the background, make tabular version of result // private class DescribeKey extends AsyncTask<String, Void, ResultPage> { + ParcelableProxy mParcelableProxy; + + public DescribeKey(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } @Override protected ResultPage doInBackground(String... args) { @@ -237,7 +259,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>(); final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>(); try { - User keybaseUser = User.findByFingerprint(fingerprint); + User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy()); for (Proof proof : keybaseUser.getProofs()) { Integer proofType = proof.getType(); appendIfOK(proofs, proofType, proof); @@ -376,7 +398,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements mProofVerifyDetail.setVisibility(View.GONE); - mKeybaseOpHelper = new CryptoOperationHelper<>(this, this, + mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_verifying_signature); mKeybaseOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java index f8c3b59ea..f980f297b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; + import java.nio.ByteBuffer; import java.util.Arrays; @@ -39,12 +40,12 @@ import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; public class ViewKeyYubiKeyFragment - extends CryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult> + extends QueueingCryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult> implements LoaderCallbacks<Cursor> { public static final String ARG_MASTER_KEY_ID = "master_key_id"; @@ -75,6 +76,10 @@ public class ViewKeyYubiKeyFragment return frag; } + public ViewKeyYubiKeyFragment() { + super(null); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -210,7 +215,8 @@ public class ViewKeyYubiKeyFragment } @Override - protected void onCryptoOperationResult(PromoteKeyResult result) { + public void onQueuedOperationSuccess(PromoteKeyResult result) { result.createNotify(getActivity()).show(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 93c6593ab..0be7e8f76 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -177,9 +177,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.bg_gray); + 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.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray); } if (entry.isRevoked() || entry.isExpired()) { @@ -188,9 +188,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { // no more space for algorithm display holder.algorithm.setVisibility(View.GONE); - holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); - holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); - holder.keyId.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + 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); @@ -198,11 +198,11 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { if (entry.isSecretKey()) { holder.mainUserId.setTextColor(Color.RED); } else { - holder.mainUserId.setTextColor(Color.BLACK); + holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); } - holder.mainUserIdRest.setTextColor(Color.BLACK); - holder.keyId.setTextColor(Color.BLACK); + holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); + holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); } if (entry.getUserIds().size() == 1) { @@ -242,9 +242,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0); if (entry.isRevoked() || entry.isExpired()) { - uidView.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); } else { - uidView.setTextColor(getContext().getResources().getColor(R.color.black)); + uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); } holder.userIdsList.addView(uidView); @@ -258,9 +258,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { emailView.setText(highlighter.highlight(email)); if (entry.isRevoked() || entry.isExpired()) { - emailView.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); } else { - emailView.setTextColor(getContext().getResources().getColor(R.color.black)); + emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); } holder.userIdsList.addView(emailView); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index af919f3b6..e77c92923 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; +import android.support.annotation.Nullable; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; @@ -26,8 +27,12 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; @@ -38,15 +43,27 @@ public class ImportKeysListCloudLoader Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; + private ParcelableProxy mParcelableProxy; private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>(); private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; - public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { + /** + * 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, + * will enforce fingerprint check + * @param cloudPrefs contains keyserver to search on, whether to search on the keyserver, + * and whether to search keybase.io + * @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences + */ + public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, + @Nullable ParcelableProxy parcelableProxy) { super(context); mContext = context; mServerQuery = serverQuery; mCloudPrefs = cloudPrefs; + mParcelableProxy = parcelableProxy; } @Override @@ -95,9 +112,32 @@ public class ImportKeysListCloudLoader * Query keyserver */ private void queryServer(boolean enforceFingerprint) { + ParcelableProxy parcelableProxy; + + if (mParcelableProxy == null) { + // no explicit proxy specified, fetch from preferences + if (OrbotHelper.isOrbotInRequiredState(mContext)) { + parcelableProxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy; + } else { + // user needs to enable/install orbot + mEntryList.clear(); + GetKeyResult pendingResult = new GetKeyResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), + new CryptoInputParcel()); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, pendingResult); + return; + } + } else { + parcelableProxy = mParcelableProxy; + } + try { - ArrayList<ImportKeysListEntry> searchResult - = CloudSearch.search(mServerQuery, mCloudPrefs); + ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search( + mServerQuery, + mCloudPrefs, + parcelableProxy.getProxy() + ); mEntryList.clear(); // add result to data diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index e545b007b..aba1eb0d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Date; import java.util.List; @@ -43,6 +42,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -50,6 +50,7 @@ public class KeyAdapter extends CursorAdapter { protected String mQuery; protected LayoutInflater mInflater; + protected Context mContext; // These are the rows that we will retrieve. public static final String[] PROJECTION = new String[]{ @@ -78,6 +79,7 @@ public class KeyAdapter extends CursorAdapter { public KeyAdapter(Context context, Cursor c, int flags) { super(context, c, flags); + mContext = context; mInflater = LayoutInflater.from(context); } @@ -107,7 +109,7 @@ public class KeyAdapter extends CursorAdapter { mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } - public void setData(Context context, KeyItem item, Highlighter highlighter) { + public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { mDisplayedItem = item; @@ -126,36 +128,39 @@ public class KeyAdapter extends CursorAdapter { } } + // sort of a hack: if this item isn't enabled, we make it clickable + // to intercept its click events + mView.setClickable(!enabled); + { // set edit button and status, specific by key type mMasterKeyId = item.mKeyId; + int textColor; + // Note: order is important! if (item.mIsRevoked) { KeyFormattingUtils - .setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray); + .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); + textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsExpired) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); + textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsSecret) { mStatus.setVisibility(View.GONE); if (mSlingerButton.hasOnClickListeners()) { mSlingerButton.setColorFilter( - context.getResources().getColor(R.color.tertiary_text_light), + FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); mSlinger.setVisibility(View.VISIBLE); } else { mSlinger.setVisibility(View.GONE); } - mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } else { // this is a public key - show if it's verified if (item.mIsVerified) { @@ -166,10 +171,16 @@ public class KeyAdapter extends CursorAdapter { mStatus.setVisibility(View.VISIBLE); } mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + if (!enabled) { + textColor = context.getResources().getColor(R.color.key_flag_gray); } + mMainUserId.setTextColor(textColor); + mMainUserIdRest.setTextColor(textColor); + if (item.mHasDuplicate) { String dateTime = DateUtils.formatDateTime(context, item.mCreation.getTime(), @@ -179,6 +190,7 @@ public class KeyAdapter extends CursorAdapter { mCreationDate.setText(context.getString(R.string.label_key_created, dateTime)); + mCreationDate.setTextColor(textColor); mCreationDate.setVisibility(View.VISIBLE); } else { mCreationDate.setVisibility(View.GONE); @@ -205,9 +217,11 @@ public class KeyAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { Highlighter highlighter = new Highlighter(context, mQuery); - KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); KeyItem item = new KeyItem(cursor); - h.setData(context, item, highlighter); + boolean isEnabled = isEnabled(cursor); + + KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); + h.setData(context, item, highlighter, isEnabled); } public boolean isSecretAvailable(int id) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index a6cb52977..4ea651bb5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -149,11 +149,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean enabled; if (cursor.getInt(mIndexIsRevoked) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.key_flag_gray); enabled = false; } else if (cursor.getInt(mIndexIsExpiry) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.key_flag_gray); enabled = false; } else { h.statusIcon.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 87539ea05..24f5f04a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -284,27 +284,27 @@ public class SubkeysAdapter extends CursorAdapter { vStatus.setVisibility(View.VISIBLE); vCertifyIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vSignIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vEncryptIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vAuthenticateIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); if (isRevoked) { vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } else if (isExpired) { vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index d1103ac1f..e2c6b0928 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -128,7 +128,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { if (isRevoked) { // set revocation icon (can this even be primary?) - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.key_flag_gray); // disable revoked user ids vName.setEnabled(false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 847d76f30..66b784f9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -29,6 +30,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** * Setups Toolbar @@ -36,14 +38,28 @@ import org.sufficientlysecure.keychain.R; public abstract class BaseActivity extends AppCompatActivity { protected Toolbar mToolbar; protected View mStatusBar; + protected ThemeChanger mThemeChanger; @Override protected void onCreate(Bundle savedInstanceState) { + mThemeChanger = new ThemeChanger(this); + mThemeChanger.changeTheme(); super.onCreate(savedInstanceState); initLayout(); initToolbar(); } + @Override + protected void onResume() { + super.onResume(); + + if (mThemeChanger.changeTheme()) { + Intent intent = getIntent(); + finish(); + startActivity(intent); + } + } + protected void initLayout() { } @@ -87,9 +103,7 @@ public abstract class BaseActivity extends AppCompatActivity { mToolbar.setNavigationOnClickListener(cancelOnClickListener); } - /** - * Close button only - */ + /** Close button only */ protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener, boolean white) { if (white) { setActionBarIcon(R.drawable.ic_close_white_24dp); @@ -104,6 +118,17 @@ public abstract class BaseActivity extends AppCompatActivity { setFullScreenDialogClose(cancelOnClickListener, true); } + /** Close button only, with finish-action and given return status, white. */ + protected void setFullScreenDialogClose(final int result, boolean white) { + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(result); + finish(); + } + }, white); + } + /** * Inflate custom design with two buttons using drawables. * This does not conform to the Material Design Guidelines, but we deviate here as this is used diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index bede16b2a..c472eaa4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -29,7 +29,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.nfc.NfcAdapter; import android.nfc.Tag; +import android.nfc.TagLostException; import android.nfc.tech.IsoDep; +import android.os.AsyncTask; import android.os.Bundle; import android.widget.Toast; @@ -63,6 +65,8 @@ public abstract class BaseNfcActivity extends BaseActivity { public static final int REQUEST_CODE_PIN = 1; + public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; + protected Passphrase mPin; protected Passphrase mAdminPin; protected boolean mPw1ValidForMultipleSignatures; @@ -71,13 +75,123 @@ public abstract class BaseNfcActivity extends BaseActivity { protected boolean mPw3Validated; private NfcAdapter mNfcAdapter; private IsoDep mIsoDep; + private boolean mTagHandlingEnabled; private static final int TIMEOUT = 100000; + private byte[] mNfcFingerprints; + private String mNfcUserId; + private byte[] mNfcAid; + + /** + * Override to change UI before NFC handling (UI thread) + */ + protected void onNfcPreExecute() { + } + + /** + * Override to implement NFC operations (background thread) + */ + protected void doNfcInBackground() throws IOException { + mNfcFingerprints = nfcGetFingerprints(); + mNfcUserId = nfcGetUserId(); + mNfcAid = nfcGetAid(); + } + + /** + * Override to handle result of NFC operations (UI thread) + */ + protected void onNfcPostExecute() throws IOException { + + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + + try { + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + long masterKeyId = ring.getMasterKeyId(); + + Intent intent = new Intent(this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + } catch (PgpKeyNotFoundException e) { + Intent intent = new Intent(this, CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + } + } + + /** + * Override to use something different than Notify (UI thread) + */ + protected void onNfcError(String error) { + Notify.create(this, error, Style.WARN).show(); + } + + public void handleIntentInBackground(final Intent intent) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + new AsyncTask<Void, Void, Exception>() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onNfcPreExecute(); + } + + @Override + protected Exception doInBackground(Void... params) { + try { + handleTagDiscoveredIntent(intent); + } catch (CardException e) { + return e; + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(Exception exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleNfcError(exception); + return; + } + + try { + onNfcPostExecute(); + } catch (IOException e) { + handleNfcError(e); + } + } + }.execute(); + } + + protected void pauseTagHandling() { + mTagHandlingEnabled = false; + } + + protected void resumeTagHandling() { + mTagHandlingEnabled = true; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Check whether we're recreating a previously destroyed instance + if (savedInstanceState != null) { + // Restore value of members from saved state + mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); + } else { + mTagHandlingEnabled = true; + } + Intent intent = getIntent(); String action = intent.getAction(); if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { @@ -86,36 +200,43 @@ public abstract class BaseNfcActivity extends BaseActivity { } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); + } + /** * This activity is started as a singleTop activity. * All new NFC Intents which are delivered to this activity are handled here */ @Override - public void onNewIntent(Intent intent) { - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { - try { - handleTagDiscoveredIntent(intent); - } catch (CardException e) { - handleNfcError(e); - } catch (IOException e) { - handleNfcError(e); - } + public void onNewIntent(final Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()) + && mTagHandlingEnabled) { + handleIntentInBackground(intent); } } - public void handleNfcError(IOException e) { - + private void handleNfcError(Exception e) { Log.e(Constants.TAG, "nfc error", e); - Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); - } - public void handleNfcError(CardException e) { - Log.e(Constants.TAG, "card error", e); + if (e instanceof TagLostException) { + onNfcError(getString(R.string.error_nfc_tag_lost)); + return; + } - short status = e.getResponseCode(); + short status; + if (e instanceof CardException) { + status = ((CardException) e).getResponseCode(); + } else { + status = -1; + } // When entering a PIN, a status of 63CX indicates X attempts remaining. if ((status & (short)0xFFF0) == 0x63C0) { - Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show(); + int tries = status & 0x000F; + onNfcError(getResources().getQuantityString(R.plurals.error_pin, tries, tries)); return; } @@ -124,63 +245,62 @@ public abstract class BaseNfcActivity extends BaseActivity { // These errors should not occur in everyday use; if they are returned, it means we // made a mistake sending data to the card, or the card is misbehaving. case 0x6A80: { - Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_bad_data)); break; } case 0x6883: { - Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_chaining_error)); break; } case 0x6B00: { - Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "P1/P2")); break; } case 0x6D00: { - Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "INS")); break; } case 0x6E00: { - Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_terminated)); break; } case 0x6700: { - Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_wrong_length)); break; } case 0x6982: { - Notify.create(this, getString(R.string.error_nfc_security_not_satisfied), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_security_not_satisfied)); break; } case 0x6983: { - Notify.create(this, getString(R.string.error_nfc_authentication_blocked), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_authentication_blocked)); break; } case 0x6985: { - Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the smart card. case 0x6F00: { - Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_unknown)); + break; + } + default: { + onNfcError(getString(R.string.error_nfc, e.getMessage())); break; } - default: - Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); } } @@ -311,43 +431,12 @@ public abstract class BaseNfcActivity extends BaseActivity { mPw1ValidatedForDecrypt = false; mPw3Validated = false; - // TODO: Handle non-default Admin PIN - mAdminPin = new Passphrase("12345678"); - - onNfcPerform(); - - mIsoDep.close(); - mIsoDep = null; + doNfcInBackground(); } - protected void onNfcPerform() throws IOException { - - final byte[] nfcFingerprints = nfcGetFingerprints(); - final String nfcUserId = nfcGetUserId(); - final byte[] nfcAid = nfcGetAid(); - - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); - - try { - CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - long masterKeyId = ring.getMasterKeyId(); - - Intent intent = new Intent(this, ViewKeyActivity.class); - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); - startActivity(intent); - } catch (PgpKeyNotFoundException e) { - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); - startActivity(intent); - } - + public boolean isNfcConnected() { + return mIsoDep.isConnected(); } /** Return the key id from application specific data stored on tag, or null @@ -569,12 +658,12 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void nfcVerifyPIN(int mode) throws IOException { if (mPin != null || mode == 0x83) { - byte[] pin; + byte[] pin; if (mode == 0x83) { - pin = new String(mAdminPin.getCharArray()).getBytes(); + pin = mAdminPin.toStringUnsafe().getBytes(); } else { - pin = new String(mPin.getCharArray()).getBytes(); + pin = mPin.toStringUnsafe().getBytes(); } // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. @@ -609,14 +698,13 @@ public abstract class BaseNfcActivity extends BaseActivity { * conformance to the card's requirements for key length. * * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPinString The new PW1 or PW3. + * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(int pw, String newPinString) throws IOException { + public void nfcModifyPIN(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; byte[] pwStatusBytes = nfcGetPwStatusBytes(); - byte[] newPin = newPinString.getBytes(); if (pw == 0x81) { if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { @@ -631,11 +719,10 @@ public abstract class BaseNfcActivity extends BaseActivity { } byte[] pin; - if (pw == 0x83) { - pin = new String(mAdminPin.getCharArray()).getBytes(); + pin = mAdminPin.toStringUnsafe().getBytes(); } else { - pin = new String(mPin.getCharArray()).getBytes(); + pin = mPin.toStringUnsafe().getBytes(); } // Command APDU for CHANGE REFERENCE DATA command (page 32) @@ -700,7 +787,7 @@ public abstract class BaseNfcActivity extends BaseActivity { throw new IOException("Invalid key slot"); } - RSAPrivateCrtKey crtSecretKey = null; + RSAPrivateCrtKey crtSecretKey; try { secretKey.unlock(passphrase); crtSecretKey = secretKey.getCrtSecretKey(); @@ -719,7 +806,7 @@ public abstract class BaseNfcActivity extends BaseActivity { } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW1 with mode 83) + nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) } byte[] header= Hex.decode( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java index 17e4e6ede..06361e8cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java @@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult> - extends CryptoOperationFragment<T, S> { + extends QueueingCryptoOperationFragment<T, S> { public static final String ARG_CACHED_ACTIONS = "cached_actions"; @@ -31,8 +31,12 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex } @Override - protected void onCryptoOperationResult(S result) { - super.onCryptoOperationResult(result); + public void onQueuedOperationSuccess(S result) { + mCachedActionsParcel = null; + } + + @Override + public void onQueuedOperationError(S result) { mCachedActionsParcel = null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 19d808ba6..52507f3e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -18,78 +18,112 @@ package org.sufficientlysecure.keychain.ui.base; +import android.content.Context; import android.content.Intent; +import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.KeychainService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -/** - * All fragments executing crypto operations need to extend this class. +/** This is a base class for fragments which implement a cryptoOperation. + * + * Subclasses of this class can call the cryptoOperation method to execute an + * operation in KeychainService which takes a parcelable of type T as its input + * and returns an OperationResult of type S as a result. + * + * The input (of type T) is not given directly to the cryptoOperation method, + * but must be provided by the overriden createOperationInput method to be + * available upon request during execution of the cryptoOperation. + * + * After running cryptoOperation, one of the onCryptoOperation*() methods will + * be called, depending on the success status of the operation. The subclass + * must override at least onCryptoOperationSuccess to proceed after a + * successful operation. + * + * @see KeychainService + * */ -public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult> +abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult> extends Fragment implements CryptoOperationHelper.Callback<T, S> { - private CryptoOperationHelper<T, S> mOperationHelper; - - public CryptoOperationFragment() { + final private CryptoOperationHelper<T, S> mOperationHelper; - mOperationHelper = new CryptoOperationHelper<>(this, this); + public CryptoOperationFragment(Integer initialProgressMsg) { + mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg); } - public void setProgressMessageResource(int id) { - mOperationHelper.setProgressMessageResource(id); + public CryptoOperationFragment() { + mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing); } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { mOperationHelper.handleActivityResult(requestCode, resultCode, data); } - @Override - public abstract T createOperationInput(); - + /** Starts execution of the cryptographic operation. + * + * During this process, the createOperationInput() method will be called, + * this input will be handed to KeychainService, where it is executed in + * the appropriate *Operation class. If the result is a PendingInputResult, + * it is handled accordingly. Otherwise, it is returned in one of the + * onCryptoOperation* callbacks. + */ protected void cryptoOperation() { - cryptoOperation(new CryptoInputParcel()); + mOperationHelper.cryptoOperation(); } protected void cryptoOperation(CryptoInputParcel cryptoInput) { - cryptoOperation(cryptoInput); + mOperationHelper.cryptoOperation(cryptoInput); } - protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { - mOperationHelper.cryptoOperation(cryptoInput, showProgress); - } + @Override @Nullable + /** Creates input for the crypto operation. Called internally after the + * crypto operation is started by a call to cryptoOperation(). Silently + * cancels operation if this method returns null. */ + public abstract T createOperationInput(); + /** Returns false, indicating that we did not handle progress ourselves. */ public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } - @Override - public void onCryptoOperationError(S result) { - onCryptoOperationResult(result); - result.createNotify(getActivity()).show(); + public void setProgressMessageResource(int id) { + mOperationHelper.setProgressMessageResource(id); } @Override - public void onCryptoOperationCancelled() { - } + /** Called when the cryptoOperation() was successful. No default behavior + * here, this should always be implemented by a subclass! */ + abstract public void onCryptoOperationSuccess(S result); @Override - public void onCryptoOperationSuccess(S result) { - onCryptoOperationResult(result); + abstract public void onCryptoOperationError(S result); + + @Override + public void onCryptoOperationCancelled() { } - /** - * - * To be overriden by subclasses, if desired. Provides a way to access the method by the - * same name in CryptoOperationHelper, if super.onCryptoOperationSuccess and - * super.onCryptoOperationError are called at the start of the respective functions in the - * subclass overriding them - * @param result - */ - protected void onCryptoOperationResult(S result) { + public void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 240dd0972..b33128978 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -28,10 +28,9 @@ import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; - import android.support.v4.app.FragmentManager; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.KeychainService; @@ -39,7 +38,9 @@ import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.RetryUploadDialogActivity; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -52,23 +53,33 @@ import org.sufficientlysecure.keychain.util.Log; */ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> { - public interface Callback <T extends Parcelable, S extends OperationResult> { + public interface Callback<T extends Parcelable, S extends OperationResult> { T createOperationInput(); + void onCryptoOperationSuccess(S result); + void onCryptoOperationCancelled(); + void onCryptoOperationError(S result); + boolean onCryptoSetProgress(String msg, int progress, int max); } - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; - public static final int REQUEST_CODE_NFC = 0x00008002; + // request codes from CryptoOperationHelper are created essentially + // a static property, used to identify requestCodes meant for this + // particular helper. a request code looks as follows: + // (id << 9) + (1<<8) + REQUEST_CODE_X + // that is, starting from LSB, there are 8 bits request code, 1 + // fixed bit set, then 7 bit operator-id code. the first two + // summands are stored in the mId for easy operation. + private final int mId; - // keeps track of request code used to start an activity from this CryptoOperationHelper. - // this is necessary when multiple CryptoOperationHelpers are used in the same fragment/activity - // otherwise all CryptoOperationHandlers may respond to the same onActivityResult - private int mRequestedCode = -1; + public static final int REQUEST_CODE_PASSPHRASE = 1; + public static final int REQUEST_CODE_NFC = 2; + public static final int REQUEST_CODE_ENABLE_ORBOT = 3; + public static final int REQUEST_CODE_RETRY_UPLOAD = 4; - private int mProgressMessageResource; + private Integer mProgressMessageResource; private FragmentActivity mActivity; private Fragment mFragment; @@ -78,10 +89,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu /** * If OperationHelper is being integrated into an activity - * - * @param activity */ - public CryptoOperationHelper(FragmentActivity activity, Callback<T, S> callback, int progressMessageString) { + public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback, + Integer progressMessageString) { + mId = (id << 9) + (1<<8); mActivity = activity; mUseFragment = false; mCallback = callback; @@ -90,48 +101,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu /** * if OperationHelper is being integrated into a fragment - * - * @param fragment */ - public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback, int progressMessageString) { + public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) { + mId = (id << 9) + (1<<8); mFragment = fragment; mUseFragment = true; mProgressMessageResource = progressMessageString; mCallback = callback; } - /** - * if OperationHelper is being integrated into a fragment with default message for the progress dialog - * - * @param fragment - */ - public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback) { - mFragment = fragment; - mUseFragment = true; - mProgressMessageResource = R.string.progress_building_key; - mCallback = callback; - } - public void setProgressMessageResource(int id) { mProgressMessageResource = id; } - private void initiateInputActivity(RequiredInputParcel requiredInput) { + private void initiateInputActivity(RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { Activity activity = mUseFragment ? mFragment.getActivity() : mActivity; switch (requiredInput.mType) { + // always use CryptoOperationHelper.startActivityForResult! case NFC_MOVE_KEY_TO_CARD: case NFC_DECRYPT: case NFC_SIGN: { Intent intent = new Intent(activity, NfcOperationActivity.class); intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); - mRequestedCode = REQUEST_CODE_NFC; - if (mUseFragment) { - mFragment.startActivityForResult(intent, mRequestedCode); - } else { - mActivity.startActivityForResult(intent, mRequestedCode); - } + intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_NFC); return; } @@ -139,37 +135,55 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu case PASSPHRASE_SYMMETRIC: { Intent intent = new Intent(activity, PassphraseDialogActivity.class); intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); - mRequestedCode = REQUEST_CODE_PASSPHRASE; - if (mUseFragment) { - mFragment.startActivityForResult(intent, mRequestedCode); - } else { - mActivity.startActivityForResult(intent, mRequestedCode); - } + intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); return; } + + case ENABLE_ORBOT: { + Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class); + intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_ENABLE_ORBOT); + return; + } + + case UPLOAD_FAIL_RETRY: { + Intent intent = new Intent(activity, RetryUploadDialogActivity.class); + intent.putExtra(RetryUploadDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_RETRY_UPLOAD); + return; + } + + default: { + throw new RuntimeException("Unhandled pending result!"); + } } + } - throw new RuntimeException("Unhandled pending result!"); + protected void startActivityForResult(Intent intent, int requestCode) { + if (mUseFragment) { + mFragment.startActivityForResult(intent, mId + requestCode); + } else { + mActivity.startActivityForResult(intent, mId + requestCode); + } } /** - * Attempts the result of an activity started by this helper. Returns true if requestCode is recognized, - * false otherwise. - * - * @param requestCode - * @param resultCode - * @param data + * Attempts the result of an activity started by this helper. Returns true if requestCode is + * recognized, false otherwise. * @return true if requestCode was recognized, false otherwise */ public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { Log.d(Constants.TAG, "received activity result in OperationHelper"); - if (mRequestedCode != requestCode) { + if ((requestCode & mId) != mId) { // this wasn't meant for us to handle return false; - } else { - mRequestedCode = -1; } + Log.d(Constants.TAG, "handling activity result in OperationHelper"); + // filter out mId from requestCode + requestCode ^= mId; + if (resultCode == Activity.RESULT_CANCELED) { mCallback.onCryptoOperationCancelled(); return true; @@ -181,7 +195,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu CryptoInputParcel cryptoInput = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); cryptoOperation(cryptoInput); - return true; } break; } @@ -189,17 +202,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu case REQUEST_CODE_NFC: { if (resultCode == Activity.RESULT_OK && data != null) { CryptoInputParcel cryptoInput = - data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); + data.getParcelableExtra(NfcOperationActivity.RESULT_CRYPTO_INPUT); cryptoOperation(cryptoInput); - return true; } break; } - default: { - return false; + case REQUEST_CODE_ENABLE_ORBOT: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra( + OrbotRequiredDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + } + break; + } + + case REQUEST_CODE_RETRY_UPLOAD: { + if (resultCode == Activity.RESULT_OK) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra( + RetryUploadDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + } + break; } } + return true; } @@ -225,7 +254,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu } - public void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { + public void cryptoOperation(final CryptoInputParcel cryptoInput) { FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity; @@ -264,7 +293,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu @Override protected void onSetProgress(String msg, int progress, int max) { // allow handling of progress in fragment, or delegate upwards - if ( ! mCallback.onCryptoSetProgress(msg, progress, max)) { + if (!mCallback.onCryptoSetProgress(msg, progress, max)) { super.onSetProgress(msg, progress, max); } } @@ -274,7 +303,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu Messenger messenger = new Messenger(saveHandler); intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - if (showProgress) { + if (mProgressMessageResource != null) { saveHandler.showProgressDialog( activity.getString(mProgressMessageResource), ProgressDialog.STYLE_HORIZONTAL, false); @@ -283,31 +312,18 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu activity.startService(intent); } - public void cryptoOperation(CryptoInputParcel cryptoInputParcel) { - cryptoOperation(cryptoInputParcel, true); - } - public void cryptoOperation() { cryptoOperation(new CryptoInputParcel()); } - protected void onCryptoOperationResult(S result) { - if (result.success()) { - mCallback.onCryptoOperationSuccess(result); - } else { - mCallback.onCryptoOperationError(result); - } - } - public void onHandleResult(OperationResult result) { Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success()); if (result instanceof InputPendingResult) { InputPendingResult pendingResult = (InputPendingResult) result; if (pendingResult.isPending()) { - RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); - initiateInputActivity(requiredInput); + initiateInputActivity(requiredInput, pendingResult.mCryptoInputParcel); return; } } @@ -315,12 +331,16 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu dismissProgress(); try { - // noinspection unchecked, because type erasure :( - onCryptoOperationResult((S) result); + if (result.success()) { + // noinspection unchecked, because type erasure :( + mCallback.onCryptoOperationSuccess((S) result); + } else { + // noinspection unchecked, because type erasure :( + mCallback.onCryptoOperationError((S) result); + } } catch (ClassCastException e) { throw new AssertionError("bad return class (" + result.getClass().getSimpleName() + "), this is a programming error!"); } - } }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java new file mode 100644 index 000000000..65e0ce941 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java @@ -0,0 +1,93 @@ +package org.sufficientlysecure.keychain.ui.base; + + +import android.os.Bundle; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.operations.results.OperationResult; + + +/** CryptoOperationFragment which calls crypto operation results only while + * attached to Activity. + * + * This subclass of CryptoOperationFragment substitutes the onCryptoOperation* + * methods for onQueuedOperation* ones, which are ensured to be called while + * the fragment is attached to an Activity, possibly delaying the call until + * the Fragment is re-attached. + * + * TODO merge this functionality into CryptoOperationFragment? + * + * @see CryptoOperationFragment + */ +public abstract class QueueingCryptoOperationFragment<T extends Parcelable, S extends OperationResult> + extends CryptoOperationFragment<T,S> { + + public static final String ARG_QUEUED_RESULT = "queued_result"; + private S mQueuedResult; + + public QueueingCryptoOperationFragment() { + super(); + } + + public QueueingCryptoOperationFragment(Integer initialProgressMsg) { + super(initialProgressMsg); + } + + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + + if (mQueuedResult != null) { + try { + if (mQueuedResult.success()) { + onQueuedOperationSuccess(mQueuedResult); + } else { + onQueuedOperationError(mQueuedResult); + } + } finally { + mQueuedResult = null; + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(ARG_QUEUED_RESULT, mQueuedResult); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mQueuedResult = savedInstanceState.getParcelable(ARG_QUEUED_RESULT); + } + } + + public abstract void onQueuedOperationSuccess(S result); + + public void onQueuedOperationError(S result) { + hideKeyboard(); + result.createNotify(getActivity()).show(); + } + + @Override + final public void onCryptoOperationSuccess(S result) { + if (getActivity() == null) { + mQueuedResult = result; + return; + } + onQueuedOperationSuccess(result); + } + + @Override + final public void onCryptoOperationError(S result) { + if (getActivity() == null) { + mQueuedResult = result; + return; + } + onQueuedOperationError(result); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 2c1714b67..691cc009d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -24,7 +24,7 @@ import java.net.URI; import java.net.URISyntaxException; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; @@ -48,13 +48,23 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { - private static final String ARG_MESSENGER = "messenger"; +import java.net.Proxy; + +public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { + private static final String ARG_MESSENGER = "arg_messenger"; + private static final String ARG_ACTION = "arg_dialog_action"; + private static final String ARG_POSITION = "arg_position"; + private static final String ARG_KEYSERVER = "arg_keyserver"; public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_VERIFICATION_FAILED = 2; @@ -62,20 +72,37 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit public static final String MESSAGE_KEYSERVER = "new_keyserver"; public static final String MESSAGE_VERIFIED = "verified"; public static final String MESSAGE_FAILURE_REASON = "failure_reason"; + public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted"; + public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action"; + public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position"; private Messenger mMessenger; + private DialogAction mDialogAction; + private int mPosition; + private EditText mKeyserverEditText; private CheckBox mVerifyKeyserverCheckBox; + public enum DialogAction { + ADD, + EDIT + } + public enum FailureReason { INVALID_URL, CONNECTION_FAILED } - public static AddKeyserverDialogFragment newInstance(Messenger messenger) { - AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment(); + public static AddEditKeyserverDialogFragment newInstance(Messenger messenger, + DialogAction action, + String keyserver, + int position) { + AddEditKeyserverDialogFragment frag = new AddEditKeyserverDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); + args.putSerializable(ARG_ACTION, action); + args.putString(ARG_KEYSERVER, keyserver); + args.putInt(ARG_POSITION, position); frag.setArguments(args); @@ -88,11 +115,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit final Activity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); + mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION); + mPosition = getArguments().getInt(ARG_POSITION); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - alert.setTitle(R.string.add_keyserver_dialog_title); - LayoutInflater inflater = activity.getLayoutInflater(); View view = inflater.inflate(R.layout.add_keyserver_dialog, null); alert.setView(view); @@ -100,14 +127,26 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text); mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox); - // we don't want dialog to be dismissed on click, thereby requiring the hack seen below - // and in onStart + switch (mDialogAction) { + case ADD: { + alert.setTitle(R.string.add_keyserver_dialog_title); + break; + } + case EDIT: { + alert.setTitle(R.string.edit_keyserver_dialog_title); + mKeyserverEditText.setText(getArguments().getString(ARG_KEYSERVER)); + break; + } + } + + // we don't want dialog to be dismissed on click for keyserver addition or edit, + // thereby requiring the hack seen below and in onStart alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // we need to have an empty listener to prevent errors on some devices as mentioned // at http://stackoverflow.com/q/13746412/3000919 - // actual listener set in onStart + // actual listener set in onStart for adding keyservers or editing them } }); @@ -119,6 +158,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit } }); + switch (mDialogAction) { + case EDIT: { + alert.setNeutralButton(R.string.label_keyserver_dialog_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteKeyserver(mPosition); + } + }); + break; + } + case ADD: { + // do nothing + break; + } + } + // Hack to open keyboard. // This is the only method that I found to work across all Android versions // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ @@ -155,25 +211,53 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String keyserverUrl = mKeyserverEditText.getText().toString(); + // behaviour same for edit and add + final String keyserverUrl = mKeyserverEditText.getText().toString(); if (mVerifyKeyserverCheckBox.isChecked()) { - verifyConnection(keyserverUrl); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + verifyConnection(keyserverUrl, null); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy()); + } } else { dismiss(); // return unverified keyserver back to activity - addKeyserver(keyserverUrl, false); + keyserverEdited(keyserverUrl, false); } } }); } } - public void addKeyserver(String keyserver, boolean verified) { + public void keyserverEdited(String keyserver, boolean verified) { dismiss(); Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, mDialogAction); data.putString(MESSAGE_KEYSERVER, keyserver); data.putBoolean(MESSAGE_VERIFIED, verified); + if (mDialogAction == DialogAction.EDIT) { + data.putInt(MESSAGE_EDIT_POSITION, mPosition); + } + + sendMessageToHandler(MESSAGE_OKAY, data); + } + + public void deleteKeyserver(int position) { + dismiss(); + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, DialogAction.EDIT); + data.putInt(MESSAGE_EDIT_POSITION, position); + data.putBoolean(MESSAGE_KEYSERVER_DELETED, true); + sendMessageToHandler(MESSAGE_OKAY, data); } @@ -184,7 +268,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data); } - public void verifyConnection(String keyserver) { + public void verifyConnection(String keyserver, final Proxy proxy) { new AsyncTask<String, Void, FailureReason>() { ProgressDialog mProgressDialog; @@ -218,10 +302,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit } URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment); - Log.d(Constants.TAG, "Converted URL" + newKeyserver); + Log.d("Converted URL", newKeyserver.toString()); - // just see if we can get a connection, then immediately close - TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close(); + OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); + TlsHelper.pinCertificateIfNecessary(client, newKeyserver.toURL()); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; } catch (MalformedURLException | URISyntaxException e) { @@ -238,7 +323,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit protected void onPostExecute(FailureReason failureReason) { mProgressDialog.dismiss(); if (failureReason == null) { - addKeyserver(mKeyserver, true); + keyserverEdited(mKeyserver, true); } else { verificationFailed(failureReason); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java index 5b91b9d37..c55c75b55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index 0b1d39fc1..b51d081e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -18,12 +18,12 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index fe4ba0262..bc82feb70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java index d2fa37cf7..d96879119 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java @@ -27,7 +27,7 @@ import org.sufficientlysecure.keychain.R; public class AdvancedAppSettingsDialogFragment extends DialogFragment { private static final String ARG_PACKAGE_NAME = "package_name"; - private static final String ARG_SIGNATURE = "signature"; + private static final String ARG_CERTIFICATE = "certificate"; /** * Creates new instance of this fragment @@ -36,7 +36,7 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment { AdvancedAppSettingsDialogFragment frag = new AdvancedAppSettingsDialogFragment(); Bundle args = new Bundle(); args.putString(ARG_PACKAGE_NAME, packageName); - args.putString(ARG_SIGNATURE, digest); + args.putString(ARG_CERTIFICATE, digest); frag.setArguments(args); return frag; @@ -62,10 +62,10 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment { }); String packageName = getArguments().getString(ARG_PACKAGE_NAME); - String signature = getArguments().getString(ARG_SIGNATURE); + String certificate = getArguments().getString(ARG_CERTIFICATE); alert.setMessage(getString(R.string.api_settings_package_name) + ": " + packageName + "\n\n" - + getString(R.string.api_settings_package_signature) + ": " + signature); + + getString(R.string.api_settings_package_certificate) + ": " + certificate); return alert.show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java index 794af5b15..840b95a3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java @@ -17,42 +17,15 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.app.AlertDialog; import android.content.Context; -import android.view.View; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; +import android.support.v7.app.AlertDialog; /** - * This class extends AlertDiaog.Builder, styling the header using emphasis color. - * Note that this class is a huge hack, because dialog boxes aren't easily stylable. - * Also, the dialog NEEDS to be called with show() directly, not create(), otherwise - * the order of internal operations will lead to a crash! + * Uses support lib's dialog builder. We can apply a theme here later! */ public class CustomAlertDialogBuilder extends AlertDialog.Builder { public CustomAlertDialogBuilder(Context context) { super(context); } - - @Override - public AlertDialog show() { - AlertDialog dialog = super.show(); - - int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null); - View divider = dialog.findViewById(dividerId); - if (divider != null) { - divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.header_text)); - } - - int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null); - TextView tv = (TextView) dialog.findViewById(textViewId); - if (tv != null) { - tv.setTextColor(dialog.getContext().getResources().getColor(R.color.header_text)); - } - - return dialog; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java deleted file mode 100644 index 7fc9da9ec..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.HashMap; - -public class DeleteKeyDialogFragment extends DialogFragment - implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> { - private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; - - public static final int MESSAGE_OKAY = 1; - public static final int MESSAGE_ERROR = 0; - - private TextView mMainMessage; - private View mInflateView; - - private Messenger mMessenger; - - // for CryptoOperationHelper.Callback - private long[] mMasterKeyIds; - private boolean mHasSecret; - private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper; - - /** - * Creates new instance of this delete file dialog fragment - */ - public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] masterKeyIds) { - DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); - Bundle args = new Bundle(); - - args.putParcelable(ARG_MESSENGER, messenger); - args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); - - frag.setArguments(args); - - return frag; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mDeleteOpHelper != null) { - mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final FragmentActivity activity = getActivity(); - mMessenger = getArguments().getParcelable(ARG_MESSENGER); - - final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); - - CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity); - - // Setup custom View to display in AlertDialog - LayoutInflater inflater = activity.getLayoutInflater(); - mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); - builder.setView(mInflateView); - - mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); - - final boolean hasSecret; - - // If only a single key has been selected - if (masterKeyIds.length == 1) { - long masterKeyId = masterKeyIds[0]; - - try { - HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData( - masterKeyId, new String[]{ - KeyRings.USER_ID, - KeyRings.HAS_ANY_SECRET - }, new int[]{ - ProviderHelper.FIELD_TYPE_STRING, - ProviderHelper.FIELD_TYPE_INTEGER - } - ); - String name; - KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID)); - if (mainUserId.name != null) { - name = mainUserId.name; - } else { - name = getString(R.string.user_id_no_name); - } - hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; - - if (hasSecret) { - // show title only for secret key deletions, - // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior - builder.setTitle(getString(R.string.title_delete_secret_key, name)); - mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); - } else { - mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); - } - } catch (ProviderHelper.NotFoundException e) { - dismiss(); - return null; - } - } else { - mMainMessage.setText(R.string.key_deletion_confirmation_multi); - hasSecret = false; - } - - builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - mMasterKeyIds = masterKeyIds; - mHasSecret = hasSecret; - - mDeleteOpHelper = new CryptoOperationHelper<> - (DeleteKeyDialogFragment.this, DeleteKeyDialogFragment.this, - R.string.progress_deleting); - mDeleteOpHelper.cryptoOperation(); - // do NOT dismiss here, it'll give - // OperationHelper a null fragmentManager - // dismiss(); - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); - } - }); - - return builder.show(); - } - - @Override - public DeleteKeyringParcel createOperationInput() { - return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); - } - - @Override - public void onCryptoOperationSuccess(DeleteResult result) { - handleResult(result); - } - - @Override - public void onCryptoOperationCancelled() { - - } - - @Override - public void onCryptoOperationError(DeleteResult result) { - handleResult(result); - } - - @Override - public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; - } - - public void handleResult(DeleteResult result) { - try { - Bundle data = new Bundle(); - data.putParcelable(OperationResult.EXTRA_RESULT, result); - Message msg = Message.obtain(); - msg.arg1 = ServiceProgressHandler.MessageStatus.OKAY.ordinal(); - msg.setData(data); - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "messenger error", e); - } - dismiss(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index eafa129f0..b51648740 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -35,7 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { public static final int MESSAGE_CHANGE_EXPIRY = 1; public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_STRIP = 3; - public static final int MESSAGE_KEYTOCARD = 4; + public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; private Messenger mMessenger; @@ -78,7 +78,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { sendMessageToHandler(MESSAGE_STRIP, null); break; case 3: - sendMessageToHandler(MESSAGE_KEYTOCARD, null); + sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); break; default: break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java index 37e05a61d..c9fb990a2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -36,6 +37,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Log; import java.util.Calendar; @@ -72,17 +75,19 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { /** * Creates dialog */ + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); + Activity activity = getActivity(); + mMessenger = getArguments().getParcelable(ARG_MESSENGER); long creation = getArguments().getLong(ARG_CREATION); long expiry = getArguments().getLong(ARG_EXPIRY); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(new Date(creation * 1000)); - final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expiryCal.setTime(new Date(expiry * 1000)); + final Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + creationCal.setTimeInMillis(creation * 1000); + Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTimeInMillis(expiry * 1000); // date picker works with default time zone, we need to convert from UTC to default timezone creationCal.setTimeZone(TimeZone.getDefault()); @@ -123,11 +128,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { noExpiry.setChecked(false); expiryLayout.setVisibility(View.VISIBLE); - // convert from UTC to time zone of device - Calendar expiryCalTimeZone = (Calendar) expiryCal.clone(); - expiryCalTimeZone.setTimeZone(TimeZone.getDefault()); currentExpiry.setText(DateFormat.getDateFormat( - getActivity()).format(expiryCalTimeZone.getTime())); + getActivity()).format(expiryCal.getTime())); } // date picker works based on default time zone @@ -175,10 +177,13 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { selectedCal.setTimeZone(TimeZone.getTimeZone("UTC")); long numDays = (selectedCal.getTimeInMillis() / 86400000) - - (expiryCal.getTimeInMillis() / 86400000); + - (creationCal.getTimeInMillis() / 86400000); if (numDays <= 0) { - Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!"); - throw new RuntimeException(); + Activity activity = getActivity(); + if (activity != null) { + Notify.create(activity, R.string.error_expiry_past, Style.ERROR).show(); + } + return; } expiry = selectedCal.getTime().getTime() / 1000; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 63b6d26ac..5ef8618ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -136,6 +136,7 @@ public class FileDialogFragment extends DialogFragment { mCheckBox.setEnabled(true); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setText(checkboxText); + mCheckBox.setChecked(true); } alert.setView(view); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java new file mode 100644 index 000000000..d1d22b6d7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Activity; +import android.support.v7.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * displays a dialog asking the user to enable Tor + */ +public class OrbotStartDialogFragment extends DialogFragment { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + + public static final int MESSAGE_MIDDLE_BUTTON = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; // for either cancel or enable pressed + + public static OrbotStartDialogFragment newInstance(Messenger messenger, int title, int message, int middleButton) { + Bundle args = new Bundle(); + args.putParcelable(ARG_MESSENGER, messenger); + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + + OrbotStartDialogFragment fragment = new OrbotStartDialogFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER); + int title = getArguments().getInt(ARG_TITLE); + final int message = getArguments().getInt(ARG_MESSAGE); + int middleButton = getArguments().getInt(ARG_MIDDLE_BUTTON); + final Activity activity = getActivity(); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_start_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + + } + }); + + builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getActivity().startActivityForResult(OrbotHelper.getOrbotStartIntent(), 1); + + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = new Message(); + msg.what = MESSAGE_MIDDLE_BUTTON; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + return builder.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java new file mode 100644 index 000000000..3f8bce28b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class PreferenceInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static PreferenceInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + PreferenceInstallDialogFragment frag = new PreferenceInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static PreferenceInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index 52a90b323..764291dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -35,6 +35,7 @@ import android.widget.Button; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** * meant to be used @@ -98,10 +99,7 @@ public class ProgressDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - // if the progress dialog is displayed from the application class, design is missing - // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay - ContextThemeWrapper context = new ContextThemeWrapper(activity, - R.style.Theme_AppCompat_Light); + ContextThemeWrapper context = ThemeChanger.getDialogThemeWrapper(activity); ProgressDialog dialog = new ProgressDialog(context); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index 4eb253825..a990682f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java new file mode 100644 index 000000000..b2b71b364 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class SupportInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static SupportInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + SupportInstallDialogFragment frag = new SupportInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static SupportInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java index eb5c3df45..902a7ec56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java @@ -18,9 +18,11 @@ package org.sufficientlysecure.keychain.ui.util; import android.content.Context; +import android.content.res.Resources.Theme; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.StrikethroughSpan; +import android.util.TypedValue; public class FormattingUtils { @@ -32,4 +34,10 @@ public class FormattingUtils { return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f); } + public static int getColorFromAttr(Context context, int attr) { + TypedValue typedValue = new TypedValue(); + Theme theme = context.getTheme(); + theme.resolveAttribute(attr, typedValue, true); + return typedValue.data; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java index 69338aa3e..ac34d5526 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java @@ -22,6 +22,7 @@ import android.text.Spannable; import android.text.style.ForegroundColorSpan; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,9 +45,12 @@ public class Highlighter { Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")"); Matcher matcher = pattern.matcher(text); + + int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); + while (matcher.find()) { highlight.setSpan( - new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), + new ForegroundColorSpan(colorEmphasis), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java new file mode 100644 index 000000000..b2213ed10 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v7.app.AlertDialog; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; + +public class InstallDialogFragmentHelper { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + private static final String ARG_INSTALL_PATH = "installPath"; + private static final String ARG_USE_MIDDLE_BUTTON = "useMiddleButton"; + + private static final String PLAY_STORE_PATH = "market://search?q=pname:"; + + public static void wrapIntoArgs(Messenger messenger, int title, int message, String packageToInstall, + int middleButton, boolean useMiddleButton, Bundle args) { + args.putParcelable(ARG_MESSENGER, messenger); + + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + args.putString(ARG_INSTALL_PATH, PLAY_STORE_PATH + packageToInstall); + args.putBoolean(ARG_USE_MIDDLE_BUTTON, useMiddleButton); + } + + public static AlertDialog getInstallDialogFromArgs(Bundle args, final Activity activity, + final int messengerMiddleButtonClicked, + final int messengerDialogDimissed) { + final Messenger messenger = args.getParcelable(ARG_MESSENGER); + + final int title = args.getInt(ARG_TITLE); + final int message = args.getInt(ARG_MESSAGE); + final int middleButton = args.getInt(ARG_MIDDLE_BUTTON); + final String installPath = args.getString(ARG_INSTALL_PATH); + final boolean useMiddleButton = args.getBoolean(ARG_USE_MIDDLE_BUTTON); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_install_dialog_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setPositiveButton(R.string.orbot_install_dialog_install, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Uri uri = Uri.parse(installPath); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity.startActivity(intent); + + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + + if (useMiddleButton) { + builder.setNeutralButton(middleButton, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerMiddleButtonClicked; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + } + + return builder.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index a3cd63d13..224e0085b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.Log; import java.nio.ByteBuffer; @@ -449,11 +450,11 @@ public class KeyFormattingUtils { if (signatureResult != null && signatureResult.isSignatureOnly()) { encIcon = R.drawable.status_lock_open_24dp; encText = R.string.decrypt_result_not_encrypted; - encColor = R.color.android_red_light; + encColor = R.color.key_flag_red; } else { encIcon = R.drawable.status_lock_closed_24dp; encText = R.string.decrypt_result_encrypted; - encColor = R.color.android_green_light; + encColor = R.color.key_flag_green; } int encColorRes = context.getResources().getColor(encColor); @@ -470,7 +471,7 @@ public class KeyFormattingUtils { sigText = R.string.decrypt_result_no_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; - sigColor = R.color.bg_gray; + sigColor = R.color.key_flag_gray; // won't be used, but makes compiler happy sigActionText = 0; @@ -481,7 +482,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { sigText = R.string.decrypt_result_signature_certified; sigIcon = R.drawable.status_signature_verified_cutout_24dp; - sigColor = R.color.android_green_light; + sigColor = R.color.key_flag_green; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -491,7 +492,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { sigText = R.string.decrypt_result_signature_uncertified; sigIcon = R.drawable.status_signature_unverified_cutout_24dp; - sigColor = R.color.android_orange_light; + sigColor = R.color.key_flag_orange; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -501,7 +502,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { sigText = R.string.decrypt_result_signature_revoked_key; sigIcon = R.drawable.status_signature_revoked_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -511,7 +512,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { sigText = R.string.decrypt_result_signature_expired_key; sigIcon = R.drawable.status_signature_expired_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -521,7 +522,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { sigText = R.string.decrypt_result_signature_missing_key; sigIcon = R.drawable.status_signature_unknown_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_Lookup; sigActionIcon = R.drawable.ic_file_download_grey_24dp; @@ -532,7 +533,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_ERROR: { sigText = R.string.decrypt_result_invalid_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -595,7 +596,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_green_light; + color = R.color.key_flag_green; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -608,7 +609,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_lock_closed_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_green_light; + color = R.color.key_flag_green; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -627,7 +628,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_orange_light; + color = R.color.key_flag_orange; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -640,7 +641,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -659,7 +660,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -677,7 +678,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -690,7 +691,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_lock_open_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -703,7 +704,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -716,7 +717,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -730,7 +731,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.bg_gray; + color = R.color.key_flag_gray; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java new file mode 100644 index 000000000..f53e43528 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 Thialfihar <thi@thialfihar.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util; + +import android.content.Context; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Preferences; + +public class ThemeChanger { + private Context mContext; + private Preferences mPreferences; + private String mCurrentTheme = null; + + static public ContextThemeWrapper getDialogThemeWrapper(Context context) { + Preferences preferences = Preferences.getPreferences(context); + + // if the dialog is displayed from the application class, design is missing. + // hack to get holo design (which is not automatically applied due to activity's + // Theme.NoDisplay) + if (Constants.Pref.Theme.DARK.equals(preferences.getTheme())) { + return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Dialog); + } else { + return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Light_Dialog); + } + } + + public ThemeChanger(Context context) { + mContext = context; + mPreferences = Preferences.getPreferences(mContext); + } + + /** + * Apply the theme set in preferences if it isn't equal to mCurrentTheme + * anymore or mCurrentTheme hasn't been set yet. + * If a new theme is applied in this method, then return true, so + * the caller can re-create the activity, if need be. + */ + public boolean changeTheme() { + String newTheme = mPreferences.getTheme(); + if (mCurrentTheme != null && mCurrentTheme.equals(newTheme)) { + return false; + } + + int themeId = R.style.LightTheme; + if (Constants.Pref.Theme.DARK.equals(newTheme)) { + themeId = R.style.DarkTheme; + } + + ContextThemeWrapper w = new ContextThemeWrapper(mContext, themeId); + mContext.getTheme().setTo(w.getTheme()); + mCurrentTheme = newTheme; + + return true; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java new file mode 100644 index 000000000..95199bcd5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{ + android.R.attr.listDivider + }; + + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; + + private Drawable mDivider; + + private int mOrientation; + + public DividerItemDecoration(Context context, int orientation) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + setOrientation(orientation); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { + throw new IllegalArgumentException("invalid orientation"); + } + mOrientation = orientation; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + public void drawVertical(Canvas c, RecyclerView parent) { + final int left = parent.getPaddingLeft(); + final int right = parent.getWidth() - parent.getPaddingRight(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int top = child.getBottom() + params.bottomMargin; + final int bottom = top + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + public void drawHorizontal(Canvas c, RecyclerView parent) { + final int top = parent.getPaddingTop(); + final int bottom = parent.getHeight() - parent.getPaddingBottom(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int left = child.getRight() + params.rightMargin; + final int right = left + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java new file mode 100644 index 000000000..c691182bf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/> + * <br/> + * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @param fromPosition The start position of the moved item. + * @param toPosition Then resolved position of the moved item. + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java new file mode 100644 index 000000000..0fd24581d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/> + * </br/> + * Expects the <code>RecyclerView.Adapter</code> to listen for {@link + * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement + * {@link ItemTouchHelperViewHolder}. + */ +public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback { + + private final ItemTouchHelperAdapter mAdapter; + + public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + // Enable drag and swipe in both directions + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + // Notify the adapter of the move + mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // we don't support swipe + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java new file mode 100644 index 000000000..97e70d71e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * android.support.v7.widget.helper.ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java new file mode 100644 index 000000000..7efcbb30c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Jacob Tabak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +/** + * based on http://stackoverflow.com/a/26196831/3000919 + */ +public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { + private OnItemClickListener mListener; + private boolean mIgnoreTouch = false; + + public interface OnItemClickListener { + void onItemClick(View view, int position); + } + + GestureDetector mGestureDetector; + + public RecyclerItemClickListener(Context context, OnItemClickListener listener) { + mListener = listener; + mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + }); + } + + @Override + public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { + if (mIgnoreTouch) { + return false; + } + View childView = view.findChildViewUnder(e.getX(), e.getY()); + if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { + mListener.onItemClick(childView, view.getChildAdapterPosition(childView)); + return true; + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { + // TODO: should we move mListener.onItemClick here + } + + @Override + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + mIgnoreTouch = disallowIntercept; + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 0fed46544..6cd33aada 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -109,14 +109,18 @@ public class CertifyKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { + // "none" entry is always enabled! + if (cursor.getPosition() == 0) { + return true; + } + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } - // don't invalidate the "None" entry, which is also null! - if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { + if (cursor.isNull(mIndexHasCertify)) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java deleted file mode 100644 index ec91b9fe4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -public interface Editor { - public interface EditorListener { - public void onDeleted(Editor editor, boolean wasNewItem); - public void onEdited(); - } - - public void setEditorListener(EditorListener listener); - public boolean needsSaving(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java index e55f6b1ad..494ccb6d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java @@ -75,10 +75,10 @@ public class EmailEditText extends AppCompatAutoCompleteTextView { Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); if (emailMatcher.matches()) { EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_ok, 0); + R.drawable.ic_stat_retyped_ok, 0); } else { EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_bad, 0); + R.drawable.ic_stat_retyped_bad, 0); } } else { // remove drawable if email is empty diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java deleted file mode 100644 index 3fd01958a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; - -public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - ImageButton mDeleteButton; - TextView mServer; - - public KeyServerEditor(Context context) { - super(context); - } - - public KeyServerEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mServer = (TextView) findViewById(R.id.server); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - super.onFinishInflate(); - } - - public void setValue(String value) { - mServer.setText(value); - } - - public String getValue() { - return mServer.getText().toString().trim(); - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this, false); - } - } - } - - @Override - public boolean needsSaving() { - return false; - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index 5050c01af..1884daf12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -84,7 +84,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (mListener != null) { - mListener.onKeyChanged(id); + long keyId = getSelectedKeyId(getItemAtPosition(position)); + mListener.onKeyChanged(keyId); } } @@ -137,6 +138,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements public long getSelectedKeyId() { Object item = getSelectedItem(); + return getSelectedKeyId(item); + } + + public long getSelectedKeyId(Object item) { if (item instanceof KeyItem) { return ((KeyItem) item).mKeyId; } @@ -186,7 +191,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements } @Override - public Object getItem(int position) { + public KeyItem getItem(int position) { if (position == 0) { return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java index 0ec145657..a7ead8039 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java @@ -31,7 +31,9 @@ import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; /** * Created by Matt Allen @@ -97,9 +99,9 @@ public class PasswordStrengthView extends View { public PasswordStrengthView(Context context, AttributeSet attrs) { super(context, attrs); - int COLOR_FAIL = getResources().getColor(R.color.android_red_light); - int COLOR_WEAK = getResources().getColor(R.color.android_orange_light); - int COLOR_STRONG = getResources().getColor(R.color.android_green_light); + int COLOR_FAIL = getResources().getColor(R.color.password_strength_low); + int COLOR_WEAK = getResources().getColor(R.color.password_strength_medium); + int COLOR_STRONG = getResources().getColor(R.color.password_strength_high); TypedArray style = context.getTheme().obtainStyledAttributes( attrs, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index c59ad7a12..8fb9e38aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -72,13 +72,18 @@ public class SignKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { + // "none" entry is always enabled! + if (cursor.getPosition() == 0) { + return true; + } + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } - if (cursor.getInt(mIndexHasSign) == 0) { + if (cursor.isNull(mIndexHasSign)) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java index b814f72b2..d7491ab26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import java.net.Proxy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -34,6 +35,7 @@ import java.util.Locale; import java.util.Set; public class EmailKeyHelper { + // TODO: Make this not require a proxy in it's constructor, redesign when it is to be used // to import keys, simply use CryptoOperationHelper with this callback public abstract class ImportContactKeysCallback implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { @@ -41,14 +43,15 @@ public class EmailKeyHelper { private ArrayList<ParcelableKeyRing> mKeyList; private String mKeyserver; - public ImportContactKeysCallback(Context context, String keyserver) { - this(context, ContactHelper.getContactMails(context), keyserver); + public ImportContactKeysCallback(Context context, String keyserver, Proxy proxy) { + this(context, ContactHelper.getContactMails(context), keyserver, proxy); } - public ImportContactKeysCallback(Context context, List<String> mails, String keyserver) { + public ImportContactKeysCallback(Context context, List<String> mails, String keyserver, + Proxy proxy) { Set<ImportKeysListEntry> entries = new HashSet<>(); for (String mail : mails) { - entries.addAll(getEmailKeys(context, mail)); + entries.addAll(getEmailKeys(context, mail, proxy)); } // Put them in a list and import @@ -65,7 +68,7 @@ public class EmailKeyHelper { } } - public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) { + public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail, Proxy proxy) { Set<ImportKeysListEntry> keys = new HashSet<>(); // Try _hkp._tcp SRV record first @@ -73,7 +76,7 @@ public class EmailKeyHelper { if (mailparts.length == 2) { HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]); if (hkp != null) { - keys.addAll(getEmailKeys(mail, hkp)); + keys.addAll(getEmailKeys(mail, hkp, proxy)); } } @@ -82,16 +85,17 @@ public class EmailKeyHelper { String server = Preferences.getPreferences(context).getPreferredKeyserver(); if (server != null) { HkpKeyserver hkp = new HkpKeyserver(server); - keys.addAll(getEmailKeys(mail, hkp)); + keys.addAll(getEmailKeys(mail, hkp, proxy)); } } return keys; } - public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) { + public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer, + Proxy proxy) { Set<ImportKeysListEntry> keys = new HashSet<>(); try { - for (ImportKeysListEntry key : keyServer.search(mail)) { + for (ImportKeysListEntry key : keyServer.search(mail, proxy)) { if (key.isRevoked() || key.isExpired()) continue; for (String userId : key.getUserIds()) { if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 9567fc9c0..5f2329170 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -17,6 +17,9 @@ package org.sufficientlysecure.keychain.util; + +import java.io.File; + import android.support.v4.app.FragmentActivity; import org.sufficientlysecure.keychain.Constants; @@ -25,15 +28,12 @@ import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import java.io.File; - public class ExportHelper implements CryptoOperationHelper.Callback <ExportKeyringParcel, ExportResult> { protected File mExportFile; FragmentActivity mActivity; - private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mExportOpHelper; private boolean mExportSecret; private long[] mMasterKeyIds; @@ -42,15 +42,13 @@ public class ExportHelper this.mActivity = activity; } - /** - * Show dialog where to export keys - */ - public void showExportKeysDialog(final long[] masterKeyIds, final File exportFile, - final boolean showSecretCheckbox) { + /** Show dialog where to export keys */ + public void showExportKeysDialog(final Long masterKeyId, final File exportFile, + final boolean exportSecret) { mExportFile = exportFile; - String title = null; - if (masterKeyIds == null) { + String title; + if (masterKeyId == null) { // export all keys title = mActivity.getString(R.string.title_export_keys); } else { @@ -58,17 +56,24 @@ public class ExportHelper title = mActivity.getString(R.string.title_export_key); } - String message = mActivity.getString(R.string.specify_file_to_export_to); - String checkMsg = showSecretCheckbox ? - mActivity.getString(R.string.also_export_secret_keys) : null; + String message; + if (exportSecret) { + message = mActivity.getString(masterKeyId == null + ? R.string.specify_backup_dest_secret + : R.string.specify_backup_dest_secret_single); + } else { + message = mActivity.getString(masterKeyId == null + ? R.string.specify_backup_dest + : R.string.specify_backup_dest_single); + } FileHelper.saveFile(new FileHelper.FileDialogCallback() { @Override public void onFileSelected(File file, boolean checked) { mExportFile = file; - exportKeys(masterKeyIds, checked); + exportKeys(masterKeyId == null ? null : new long[] { masterKeyId }, exportSecret); } - }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg); + }, mActivity.getSupportFragmentManager(), title, message, exportFile, null); } // TODO: If ExportHelper requires pending data (see CryptoOPerationHelper), activities using @@ -82,8 +87,9 @@ public class ExportHelper mExportSecret = exportSecret; mMasterKeyIds = masterKeyIds; // if masterKeyIds is null it means export all - mExportOpHelper = new CryptoOperationHelper(mActivity, this, R.string.progress_exporting); - mExportOpHelper.cryptoOperation(); + CryptoOperationHelper<ExportKeyringParcel, ExportResult> exportOpHelper = + new CryptoOperationHelper<>(1, mActivity, this, R.string.progress_exporting); + exportOpHelper.cryptoOperation(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java new file mode 100644 index 000000000..b00b03ec7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.util; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +/** + * used to simply transport java.net.Proxy objects created using InetSockets between services/activities + */ +public class ParcelableProxy implements Parcelable { + private String mProxyHost; + private int mProxyPort; + private Proxy.Type mProxyType; + + public ParcelableProxy(String hostName, int port, Proxy.Type type) { + mProxyHost = hostName; + + if (hostName == null) { + return; // represents a null proxy + } + + mProxyPort = port; + + mProxyType = type; + } + + public static ParcelableProxy getForNoProxy() { + return new ParcelableProxy(null, -1, null); + } + + public Proxy getProxy() { + if (mProxyHost == null) { + return null; + } + + return new Proxy(mProxyType, new InetSocketAddress(mProxyHost, mProxyPort)); + } + + protected ParcelableProxy(Parcel in) { + mProxyHost = in.readString(); + mProxyPort = in.readInt(); + mProxyType = (Proxy.Type) in.readSerializable(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mProxyHost); + dest.writeInt(mProxyPort); + dest.writeSerializable(mProxyType); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator<ParcelableProxy> CREATOR = new Parcelable.Creator<ParcelableProxy>() { + @Override + public ParcelableProxy createFromParcel(Parcel in) { + return new ParcelableProxy(in); + } + + @Override + public ParcelableProxy[] newArray(int size) { + return new ParcelableProxy[size]; + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java index 06efdde4d..fe42c7a2c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java @@ -117,6 +117,13 @@ public class Passphrase implements Parcelable { } } + /** + * Creates a new String from the char[]. This is considered unsafe! + */ + public String toStringUnsafe() { + return new String(mPassphrase); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -127,11 +134,7 @@ public class Passphrase implements Parcelable { } Passphrase that = (Passphrase) o; - if (!Arrays.equals(mPassphrase, that.mPassphrase)) { - return false; - } - - return true; + return Arrays.equals(mPassphrase, that.mPassphrase); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index a5b0088c0..0596b0079 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -21,9 +21,13 @@ package org.sufficientlysecure.keychain.util; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; +import android.preference.PreferenceManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; +import org.sufficientlysecure.keychain.R; +import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.ListIterator; @@ -35,6 +39,10 @@ import java.util.Vector; public class Preferences { private static Preferences sPreferences; private SharedPreferences mSharedPreferences; + private Resources mResources; + + private static String PREF_FILE_NAME = "APG.main"; + private static int PREF_FILE_MODE = Context.MODE_MULTI_PROCESS; public static synchronized Preferences getPreferences(Context context) { return getPreferences(context, false); @@ -51,12 +59,18 @@ public class Preferences { } private Preferences(Context context) { + mResources = context.getResources(); updateSharedPreferences(context); } + public static void setPreferenceManagerFileAndMode(PreferenceManager manager) { + manager.setSharedPreferencesName(PREF_FILE_NAME); + manager.setSharedPreferencesMode(PREF_FILE_MODE); + } + public void updateSharedPreferences(Context context) { // multi-process safe preferences - mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS); + mSharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, PREF_FILE_MODE); } public String getLanguage() { @@ -138,6 +152,9 @@ public class Preferences { public String[] getKeyServers() { String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS, Constants.Defaults.KEY_SERVERS); + if (rawData.equals("")) { + return new String[0]; + } Vector<String> servers = new Vector<>(); String chunks[] = rawData.split(","); for (String c : chunks) { @@ -150,7 +167,8 @@ public class Preferences { } public String getPreferredKeyserver() { - return getKeyServers()[0]; + String[] keyservers = getKeyServers(); + return keyservers.length == 0 ? null : keyservers[0]; } public void setKeyServers(String[] value) { @@ -202,7 +220,15 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.TEXT_USE_COMPRESSION, true); } + public String getTheme() { + return mSharedPreferences.getString(Pref.THEME, Pref.Theme.LIGHT); + } + public void setTheme(String value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(Constants.Pref.THEME, value); + editor.commit(); + } public void setUseArmor(boolean useArmor) { SharedPreferences.Editor editor = mSharedPreferences.edit(); @@ -224,6 +250,89 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.ENCRYPT_FILENAMES, true); } + // proxy preference functions start here + + public boolean getUseNormalProxy() { + return mSharedPreferences.getBoolean(Constants.Pref.USE_NORMAL_PROXY, false); + } + + public boolean getUseTorProxy() { + return mSharedPreferences.getBoolean(Constants.Pref.USE_TOR_PROXY, false); + } + + public String getProxyHost() { + return mSharedPreferences.getString(Constants.Pref.PROXY_HOST, null); + } + + /** + * we store port as String for easy interfacing with EditTextPreference, but return it as an integer + * + * @return port number of proxy + */ + public int getProxyPort() { + return Integer.parseInt(mSharedPreferences.getString(Pref.PROXY_PORT, "-1")); + } + + /** + * we store port as String for easy interfacing with EditTextPreference, but return it as an integer + * + * @param port proxy port + */ + public void setProxyPort(String port) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(Pref.PROXY_PORT, port); + editor.commit(); + } + + public Proxy.Type getProxyType() { + final String typeHttp = mResources.getString(R.string.pref_proxy_type_value_http); + final String typeSocks = mResources.getString(R.string.pref_proxy_type_value_socks); + + String type = mSharedPreferences.getString(Pref.PROXY_TYPE, typeHttp); + + if (type.equals(typeHttp)) return Proxy.Type.HTTP; + else if (type.equals(typeSocks)) return Proxy.Type.SOCKS; + else { // shouldn't happen + Log.e(Constants.TAG, "Invalid Proxy Type in preferences"); + return null; + } + } + + public ProxyPrefs getProxyPrefs() { + boolean useTor = getUseTorProxy(); + boolean useNormalProxy = getUseNormalProxy(); + + if (useTor) { + return new ProxyPrefs(true, false, Constants.Orbot.PROXY_HOST, Constants.Orbot.PROXY_PORT, + Constants.Orbot.PROXY_TYPE); + } else if (useNormalProxy) { + return new ProxyPrefs(useTor, useNormalProxy, getProxyHost(), getProxyPort(), getProxyType()); + } else { + return new ProxyPrefs(false, false, null, -1, null); + } + } + + public static class ProxyPrefs { + public final ParcelableProxy parcelableProxy; + public final boolean torEnabled; + public final boolean normalPorxyEnabled; + + /** + * torEnabled and normalProxyEnabled are not expected to both be true + * + * @param torEnabled if Tor is to be used + * @param normalPorxyEnabled if user-specified proxy is to be used + */ + public ProxyPrefs(boolean torEnabled, boolean normalPorxyEnabled, String hostName, int port, Proxy.Type type) { + this.torEnabled = torEnabled; + this.normalPorxyEnabled = normalPorxyEnabled; + if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null); + else this.parcelableProxy = new ParcelableProxy(hostName, port, type); + } + } + + // proxy preference functions ends here + public CloudSearchPrefs getCloudSearchPrefs() { return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true), @@ -247,7 +356,7 @@ public class Preferences { } } - public void updatePreferences() { + public void upgradePreferences() { if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) != Constants.Defaults.PREF_VERSION) { switch (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0)) { @@ -281,6 +390,10 @@ public class Preferences { } // fall through case 4: { + setTheme(Constants.Pref.Theme.DEFAULT); + } + // fall through + case 5: { } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 4ff14e3bb..d1d1ada2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; +import com.squareup.okhttp.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -26,7 +27,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.net.URLConnection; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -61,7 +61,7 @@ public class TlsHelper { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int reads = is.read(); - while(reads != -1){ + while (reads != -1) { baos.write(reads); reads = is.read(); } @@ -74,15 +74,56 @@ public class TlsHelper { } } - public static URLConnection openConnection(URL url) throws IOException, TlsHelperException { + public static void pinCertificateIfNecessary(OkHttpClient client, URL url) throws TlsHelperException, IOException { if (url.getProtocol().equals("https")) { for (String domain : sStaticCA.keySet()) { if (url.getHost().endsWith(domain)) { - return openCAConnection(sStaticCA.get(domain), url); + pinCertificate(sStaticCA.get(domain), client); } } } - return url.openConnection(); + } + + /** + * Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the + * client. + * Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate. + * TODO: Refactor - More like SSH StrictHostKeyChecking than pinning? + * + * @param certificate certificate to pin + * @param client OkHttpClient to enforce pinning on + * @throws TlsHelperException + * @throws IOException + */ + private static void pinCertificate(byte[] certificate, OkHttpClient client) + throws TlsHelperException, IOException { + // We don't use OkHttp's CertificatePinner since it depends on a TrustManager to verify it too. Refer to + // note at end of description: http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html + // Creating our own TrustManager that trusts only our certificate eliminates the need for certificate pinning + try { + // Load CA + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate)); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + client.setSslSocketFactory(context.getSocketFactory()); + } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { + throw new TlsHelperException(e); + } } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java new file mode 100644 index 000000000..9eb92f92f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java @@ -0,0 +1,252 @@ +/* This is the license for Orlib, a free software project to + provide anonymity on the Internet from a Google Android smartphone. + + For more information about Orlib, see https://guardianproject.info/ + + If you got this file as a part of a larger bundle, there may be other + license terms that you should be aware of. + =============================================================================== + Orlib is distributed under this license (aka the 3-clause BSD license) + + Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***** + Orlib contains a binary distribution of the JSocks library: + http://code.google.com/p/jsocks-mirror/ + which is licensed under the GNU Lesser General Public License: + http://www.gnu.org/licenses/lgpl.html + + ***** +*/ + +package org.sufficientlysecure.keychain.util.orbot; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.SupportInstallDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.OrbotStartDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PreferenceInstallDialogFragment; +import org.sufficientlysecure.keychain.util.Preferences; + +/** + * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/ + */ +public class OrbotHelper { + + public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; + public final static String TOR_BIN_PATH = "/data/data/org.torproject.android/app_bin/tor"; + + public final static String ACTION_START_TOR = "org.torproject.android.START_TOR"; + + public static boolean isOrbotRunning() { + int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH); + + return (procId != -1); + } + + public static boolean isOrbotInstalled(Context context) { + return isAppInstalled(ORBOT_PACKAGE_NAME, context); + } + + private static boolean isAppInstalled(String uri, Context context) { + PackageManager pm = context.getPackageManager(); + + boolean installed; + try { + pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); + installed = true; + } catch (PackageManager.NameNotFoundException e) { + installed = false; + } + return installed; + } + + /** + * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment + * + * @return + */ + public static android.app.DialogFragment getPreferenceInstallDialogFragment() { + return PreferenceInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME); + } + + public static DialogFragment getInstallDialogFragment() { + return SupportInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME); + } + + public static DialogFragment getInstallDialogFragmentWithThirdButton(Messenger messenger, int middleButton) { + return SupportInstallDialogFragment.newInstance(messenger, R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME, middleButton, true); + } + + public static DialogFragment getOrbotStartDialogFragment(Messenger messenger, int middleButton) { + return OrbotStartDialogFragment.newInstance(messenger, R.string.orbot_start_dialog_title, R.string + .orbot_start_dialog_content, + middleButton); + } + + public static Intent getOrbotStartIntent() { + Intent intent = new Intent(ACTION_START_TOR); + intent.setPackage(ORBOT_PACKAGE_NAME); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + /** + * checks preferences to see if Orbot is required, and if yes, if it is installed and running + * + * @param context used to retrieve preferences + * @return false if Tor is selected proxy and Orbot is not installed or running, true + * otherwise + */ + public static boolean isOrbotInRequiredState(Context context) { + Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(context).getProxyPrefs(); + if (!proxyPrefs.torEnabled) { + return true; + } else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning()) { + return false; + } + return true; + } + + /** + * checks if Tor is enabled and if it is, that Orbot is installed and runnign. Generates appropriate dialogs. + * + * @param middleButton resourceId of string to display as the middle button of install and enable dialogs + * @param middleButtonRunnable runnable to be executed if the user clicks on the middle button + * @param proxyPrefs + * @param fragmentActivity + * @return true if Tor is not enabled or Tor is enabled and Orbot is installed and running, else false + */ + public static boolean putOrbotInRequiredState(final int middleButton, + final Runnable middleButtonRunnable, + final Runnable dialogDismissRunnable, + Preferences.ProxyPrefs proxyPrefs, + FragmentActivity fragmentActivity) { + + if (!proxyPrefs.torEnabled) { + return true; + } + + if (!OrbotHelper.isOrbotInstalled(fragmentActivity)) { + Handler ignoreTorHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SupportInstallDialogFragment.MESSAGE_MIDDLE_CLICKED: + middleButtonRunnable.run(); + break; + case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED: + dialogDismissRunnable.run(); + break; + } + } + }; + + OrbotHelper.getInstallDialogFragmentWithThirdButton( + new Messenger(ignoreTorHandler), + middleButton + ).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog"); + + return false; + } else if (!OrbotHelper.isOrbotRunning()) { + + Handler ignoreTorHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON: + middleButtonRunnable.run(); + break; + case OrbotStartDialogFragment.MESSAGE_DIALOG_DISMISSED: + dialogDismissRunnable.run(); + break; + } + } + }; + + OrbotHelper.getOrbotStartDialogFragment(new Messenger(ignoreTorHandler), + middleButton) + .show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotStartDialog"); + + return false; + } else { + return true; + } + } + + public static boolean putOrbotInRequiredState(final int middleButton, + final Runnable middleButtonRunnable, + Preferences.ProxyPrefs proxyPrefs, + FragmentActivity fragmentActivity) { + Runnable emptyRunnable = new Runnable() { + @Override + public void run() { + + } + }; + return putOrbotInRequiredState(middleButton, middleButtonRunnable, emptyRunnable, + proxyPrefs, fragmentActivity); + } + + /** + * generates a standard Orbot install/enable dialog if necessary, based on proxy settings in + * preferences + * + * @param ignoreTorRunnable run when the "Ignore Tor" button is pressed + * @param fragmentActivity used to start the activ + * @return + */ + public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable, + FragmentActivity fragmentActivity) { + return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable, + Preferences.getPreferences(fragmentActivity).getProxyPrefs(), fragmentActivity); + } + + public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable, + Runnable dismissDialogRunnable, + FragmentActivity fragmentActivity) { + return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable, + dismissDialogRunnable, + Preferences.getPreferences(fragmentActivity).getProxyPrefs(), + fragmentActivity); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java new file mode 100644 index 000000000..d11e80f7a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java @@ -0,0 +1,144 @@ +/* This is the license for Orlib, a free software project to + provide anonymity on the Internet from a Google Android smartphone. + + For more information about Orlib, see https://guardianproject.info/ + + If you got this file as a part of a larger bundle, there may be other + license terms that you should be aware of. + =============================================================================== + Orlib is distributed under this license (aka the 3-clause BSD license) + + Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***** + Orlib contains a binary distribution of the JSocks library: + http://code.google.com/p/jsocks-mirror/ + which is licensed under the GNU Lesser General Public License: + http://www.gnu.org/licenses/lgpl.html + + ***** +*/ + +package org.sufficientlysecure.keychain.util.orbot; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.net.URLEncoder; +import java.util.StringTokenizer; + +/** + * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/ + */ +public class TorServiceUtils { + // various console cmds + public final static String SHELL_CMD_PS = "ps"; + public final static String SHELL_CMD_PIDOF = "pidof"; + + public static int findProcessId(String command) { + int procId = -1; + + try { + procId = findProcessIdWithPidOf(command); + + if (procId == -1) { + procId = findProcessIdWithPS(command); + } + } catch (Exception e) { + try { + procId = findProcessIdWithPS(command); + } catch (Exception e2) { + Log.e(Constants.TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2); + } + } + + return procId; + } + + // use 'pidof' command + public static int findProcessIdWithPidOf(String command) throws Exception { + + int procId = -1; + Runtime r = Runtime.getRuntime(); + Process procPs; + + String baseName = new File(command).getName(); + // fix contributed my mikos on 2010.12.10 + procPs = r.exec(new String[]{ + SHELL_CMD_PIDOF, baseName + }); + // procPs = r.exec(SHELL_CMD_PIDOF); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line; + + while ((line = reader.readLine()) != null) { + + try { + // this line should just be the process id + procId = Integer.parseInt(line.trim()); + break; + } catch (NumberFormatException e) { + Log.e("TorServiceUtils", "unable to parse process pid: " + line, e); + } + } + + return procId; + } + + // use 'ps' command + public static int findProcessIdWithPS(String command) throws Exception { + int procId = -1; + Runtime r = Runtime.getRuntime(); + Process procPs; + + procPs = r.exec(SHELL_CMD_PS); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(' ' + command)) { + + StringTokenizer st = new StringTokenizer(line, " "); + st.nextToken(); // proc owner + + procId = Integer.parseInt(st.nextToken().trim()); + + break; + } + } + + return procId; + } +} |