diff options
Diffstat (limited to 'OpenKeychain/src')
43 files changed, 890 insertions, 689 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 337ad73e0..1ac9eb257 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -681,6 +681,22 @@ <data android:pathPattern="/pks/lookup.*" /> </intent-filter> + <!-- VIEW from facebook public key urls opened in a browser --> + <intent-filter android:label="@string/intent_import_key"> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + + <data android:scheme="https" /> + <data android:scheme="http" /> + + <data android:host="www.facebook.com" /> + <data android:host="facebook.com" /> + + <data android:pathPattern="/..*/publickey/download" /> + </intent-filter> + <!-- IMPORT_KEY with files TODO: does this work? --> <intent-filter android:label="@string/intent_import_key"> <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" /> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 3390fb729..57fce633e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.content.Intent; @@ -25,6 +27,7 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Environment; +import android.widget.Toast; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.provider.KeychainDatabase; @@ -90,8 +93,13 @@ public class KeychainApplication extends Application { FormattingUtils.getColorFromAttr(getApplicationContext(), R.attr.colorPrimary)); // Add OpenKeychain account to Android to link contacts with keys and keyserver sync - KeyserverSyncAdapterService.enableKeyserverSync(this); - ContactSyncAdapterService.enableContactsSync(this); + createAccountIfNecessary(); + + // if first time, enable keyserver and contact sync + if (Preferences.getPreferences(this).isFirstTime()) { + KeyserverSyncAdapterService.enableKeyserverSync(this); + ContactSyncAdapterService.enableContactsSync(this); + } // Update keyserver list as needed Preferences.getPreferences(this).upgradePreferences(this); @@ -108,6 +116,23 @@ public class KeychainApplication extends Application { } } + private void createAccountIfNecessary() { + try { + AccountManager manager = AccountManager.get(this); + Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); + + Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); + if (accounts.length == 0) { + if (!manager.addAccountExplicitly(account, null, null)) { + Log.d(Constants.TAG, "account already exists, the account is null, or another error occured"); + } + } + } catch (SecurityException e) { + Log.e(Constants.TAG, "SecurityException when adding the account", e); + Toast.makeText(this, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show(); + } + } + public static HashMap<String,Bitmap> qrCodeCache = new HashMap<>(); @Override 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 869d107ab..df45de11f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -48,6 +48,9 @@ public class CloudSearch { if (cloudPrefs.searchKeybase) { servers.add(new KeybaseKeyserver(proxy)); } + if (cloudPrefs.searchFacebook) { + servers.add(new FacebookKeyserver(proxy)); + } final ImportKeysList results = new ImportKeysList(servers.size()); ArrayList<Thread> searchThreads = new ArrayList<>(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java new file mode 100644 index 000000000..d87a82a24 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 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.keyimport; + +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.net.Proxy; + +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; + +public class FacebookKeyserver extends Keyserver { + + private static final String FB_KEY_URL_FORMAT + = "https://www.facebook.com/%s/publickey/download"; + private static final String FB_HOST = "facebook.com"; + private static final String FB_HOST_WWW = "www." + FB_HOST; + + public static final String FB_URL = "https://" + FB_HOST_WWW; + + public static final String ORIGIN = FB_URL; + + private final Proxy mProxy; + + public FacebookKeyserver(Proxy proxy) { + mProxy = proxy; + } + + @Override + public List<ImportKeysListEntry> search(String fbUsername) + throws QueryFailedException, QueryNeedsRepairException { + List<ImportKeysListEntry> entry = new ArrayList<>(1); + + String data = get(fbUsername); + // if we're here that means key retrieval succeeded, + // would have thrown an exception otherwise + try { + UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data.getBytes()); + try { + entry.add(getEntry(keyRing, fbUsername)); + } catch (UnsupportedOperationException e) { + Log.e(Constants.TAG, "Parsing retrieved Facebook key failed!"); + } + } catch (PgpGeneralException | IOException e) { + Log.e(Constants.TAG, "Failed parsing key from Facebook during search", e); + throw new QueryFailedException("No valid key found on Facebook"); + } + return entry; + } + + @Override + public String get(String fbUsername) throws QueryFailedException { + Log.d(Constants.TAG, "FacebookKeyserver get: " + fbUsername + " using Proxy: " + mProxy); + + String data = query(fbUsername); + + if (data == null) { + throw new QueryFailedException("data is null"); + } + + Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); + if (matcher.find()) { + return matcher.group(1); + } + throw new QueryFailedException("data is null"); + } + + private String query(String fbUsername) throws QueryFailedException { + try { + String request = String.format(FB_KEY_URL_FORMAT, fbUsername); + Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy); + + OkHttpClient client = new OkHttpClient(); + client.setProxy(mProxy); + + URL url = new URL(request); + + Response response = client.newCall(new Request.Builder().url(url).build()).execute(); + + // contains body both in case of success or failure + String responseBody = response.body().string(); + + if (response.isSuccessful()) { + return responseBody; + } else { + // probably a 404 indicating that the key does not exist + throw new QueryFailedException("key for " + fbUsername + " not found on Facebook"); + } + + } catch (IOException e) { + Log.e(Constants.TAG, "IOException at Facebook key download", e); + throw new QueryFailedException("Cannot connect to Facebook. " + + "Check your Internet connection!" + + (mProxy == Proxy.NO_PROXY ? "" : " Using proxy " + mProxy)); + } + } + + @Override + public void add(String armoredKey) throws AddKeyException { + // Implementing will require usage of FB API + throw new UnsupportedOperationException("Uploading keys not supported yet"); + } + + /** + * Facebook returns the entire key even during our searching phase. + * + * @throws UnsupportedOperationException if the key could not be parsed + */ + @NonNull + public static ImportKeysListEntry getEntry(UncachedKeyRing ring, String fbUsername) + throws UnsupportedOperationException { + ImportKeysListEntry entry = new ImportKeysListEntry(); + entry.setSecretKey(false); // keys imported from Facebook must be public + entry.addOrigin(ORIGIN); + + // so we can query for the Facebook username directly, and to identify the source to + // download the key from + entry.setFbUsername(fbUsername); + + UncachedPublicKey key = ring.getPublicKey(); + + entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback()); + entry.setUserIds(key.getUnorderedUserIds()); + entry.updateMergedUserIds(); + + entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback()); + + entry.setKeyId(key.getKeyId()); + entry.setKeyIdHex(KeyFormattingUtils.convertKeyIdToHex(key.getKeyId())); + + entry.setFingerprintHex(KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint())); + + + try { + if (key.isEC()) { // unsupported key format (ECDH or ECDSA) + Log.e(Constants.TAG, "ECDH/ECDSA key - not supported."); + throw new UnsupportedOperationException( + "ECDH/ECDSA keys not supported yet"); + } + entry.setBitStrength(key.getBitStrength()); + final int algorithm = key.getAlgorithm(); + entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithm, key.getBitStrength(), + key.getCurveOid())); + } catch (NumberFormatException | NullPointerException e) { + Log.e(Constants.TAG, "Conversion for bit size, algorithm, or creation date failed.", e); + // can't use this key + throw new UnsupportedOperationException( + "Conversion for bit size, algorithm, or creation date failed."); + } + + return entry; + } + + public static String getUsernameFromUri(Uri uri) { + // path pattern is /username/publickey/download + return uri.getPathSegments().get(0); + } + + public static boolean isFacebookHost(Uri uri) { + String host = uri.getHost(); + return host.equalsIgnoreCase(FB_HOST) || host.equalsIgnoreCase(FB_HOST_WWW); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java index 03439228b..75a219191 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java @@ -77,9 +77,15 @@ public class ImportKeysList extends ArrayList<ImportKeysListEntry> { for (String origin : incoming.getOrigins()) { existing.addOrigin(origin); - // to work properly, Keybase-sourced entries need to pass along the extra - if (KeybaseKeyserver.ORIGIN.equals(origin)) { - existing.setExtraData(incoming.getExtraData()); + // to work properly, Keybase-sourced/Facebook-sourced entries need to pass along the + // identifying name/id + if (incoming.getKeybaseName() != null) { + existing.setKeybaseName(incoming.getKeybaseName()); + // one of the origins is not a HKP keyserver + incomingFromHkpServer = false; + } + if (incoming.getFbUsername() != null) { + existing.setFbUsername(incoming.getFbUsername()); // one of the origins is not a HKP keyserver incomingFromHkpServer = false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index bb86d272f..b3cf70c9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -49,7 +49,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { private String mAlgorithm; private boolean mSecretKey; private String mPrimaryUserId; - private String mExtraData; + private String mKeybaseName; + private String mFbUsername; private String mQuery; private ArrayList<String> mOrigins; private Integer mHashCode = null; @@ -81,7 +82,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeString(mAlgorithm); dest.writeByte((byte) (mSecretKey ? 1 : 0)); dest.writeByte((byte) (mSelected ? 1 : 0)); - dest.writeString(mExtraData); + dest.writeString(mKeybaseName); + dest.writeString(mFbUsername); dest.writeStringList(mOrigins); } @@ -102,7 +104,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mAlgorithm = source.readString(); vr.mSecretKey = source.readByte() == 1; vr.mSelected = source.readByte() == 1; - vr.mExtraData = source.readString(); + vr.mKeybaseName = source.readString(); + vr.mFbUsername = source.readString(); vr.mOrigins = new ArrayList<>(); source.readStringList(vr.mOrigins); @@ -229,12 +232,20 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mPrimaryUserId = uid; } - public String getExtraData() { - return mExtraData; + public String getKeybaseName() { + return mKeybaseName; } - public void setExtraData(String extraData) { - mExtraData = extraData; + public String getFbUsername() { + return mFbUsername; + } + + public void setKeybaseName(String keybaseName) { + mKeybaseName = keybaseName; + } + + public void setFbUsername(String fbUsername) { + mFbUsername = fbUsername; } public String getQuery() { 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 e4cd6738b..9243926df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -81,8 +81,9 @@ public class KeybaseKeyserver extends Keyserver { entry.setFingerprintHex(fingerprint); entry.setKeyIdHex("0x" + match.getKeyID()); - // store extra info, so we can query for the keybase id directly - entry.setExtraData(username); + // so we can query for the keybase id directly, and to identify the location from which the + // key is to be retrieved + entry.setKeybaseName(username); final int bitStrength = match.getBitStrength(); entry.setBitStrength(bitStrength); 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 00e8d6ac5..53e71f7a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -21,7 +21,6 @@ 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 { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java index 6f6c816ea..a94ce0dce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -32,24 +32,39 @@ public class ParcelableKeyRing implements Parcelable { public final String mExpectedFingerprint; public final String mKeyIdHex; public final String mKeybaseName; + public final String mFbUsername; public ParcelableKeyRing(byte[] bytes) { + this(null, bytes, false); + } + + /** + * @param disAmbiguator useless parameter intended to distinguish this overloaded constructor + * for when null is passed as first two arguments + */ + public ParcelableKeyRing(String expectedFingerprint, byte[] bytes, boolean disAmbiguator) { mBytes = bytes; - mExpectedFingerprint = null; + mExpectedFingerprint = expectedFingerprint; mKeyIdHex = null; mKeybaseName = null; + mFbUsername = null; } - public ParcelableKeyRing(String expectedFingerprint, byte[] bytes) { - mBytes = bytes; + + public ParcelableKeyRing(String expectedFingerprint, String keyIdHex) { + mBytes = null; mExpectedFingerprint = expectedFingerprint; - mKeyIdHex = null; + mKeyIdHex = keyIdHex; mKeybaseName = null; + mFbUsername = null; } - public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName) { + + public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName, + String fbUsername) { mBytes = null; mExpectedFingerprint = expectedFingerprint; mKeyIdHex = keyIdHex; mKeybaseName = keybaseName; + mFbUsername = fbUsername; } private ParcelableKeyRing(Parcel source) { @@ -58,6 +73,7 @@ public class ParcelableKeyRing implements Parcelable { mExpectedFingerprint = source.readString(); mKeyIdHex = source.readString(); mKeybaseName = source.readString(); + mFbUsername = source.readString(); } public void writeToParcel(Parcel dest, int flags) { @@ -65,6 +81,7 @@ public class ParcelableKeyRing implements Parcelable { dest.writeString(mExpectedFingerprint); dest.writeString(mKeyIdHex); dest.writeString(mKeybaseName); + dest.writeString(mFbUsername); } public static final Creator<ParcelableKeyRing> CREATOR = new Creator<ParcelableKeyRing>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java index f6e157c74..988a3d16e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java @@ -41,6 +41,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; +import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; +import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -66,7 +68,7 @@ public class BenchmarkOperation extends BaseOperation<BenchmarkInputParcel> { log.add(LogType.MSG_BENCH, 0); // random data - byte[] buf = new byte[1024*1024*5]; + byte[] buf = new byte[1024*1024*10]; new Random().nextBytes(buf); Passphrase passphrase = new Passphrase("a"); @@ -83,6 +85,7 @@ public class BenchmarkOperation extends BaseOperation<BenchmarkInputParcel> { new ProgressScaler(mProgressable, i*(50/numRepeats), (i+1)*(50/numRepeats), 100), mCancelled); SignEncryptParcel input = new SignEncryptParcel(); input.setSymmetricPassphrase(passphrase); + input.setSymmetricEncryptionAlgorithm(OpenKeychainSymmetricKeyAlgorithmTags.AES_128); input.setBytes(buf); encryptResult = op.execute(input, new CryptoInputParcel()); log.add(encryptResult, 1); 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 70288123f..a20181a00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -38,6 +38,7 @@ import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; @@ -156,6 +157,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { double progSteps = 100.0 / num; KeybaseKeyserver keybaseServer = null; + FacebookKeyserver facebookServer = null; HkpKeyserver keyServer = null; // iterate over all entries @@ -228,6 +230,12 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes(); UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data); + if (keybaseKey != null) { + log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_OK, 3); + } else { + log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3); + } + // If there already is a key, merge the two if (key != null && keybaseKey != null) { log.add(LogType.MSG_IMPORT_MERGE, 3); @@ -247,6 +255,44 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER, 3, e.getMessage()); } } + + // if the key is from Facebook, fetch from there + if (entry.mFbUsername != null) { + // Make sure we have this cached + if (facebookServer == null) { + facebookServer = new FacebookKeyserver(proxy); + } + + try { + log.add(LogType.MSG_IMPORT_FETCH_FACEBOOK, 2, entry.mFbUsername); + byte[] data = facebookServer.get(entry.mFbUsername).getBytes(); + UncachedKeyRing facebookKey = UncachedKeyRing.decodeFromData(data); + + if (facebookKey != null) { + log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_OK, 3); + } else { + log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3); + } + + // If there already is a key, merge the two + if (key != null && facebookKey != null) { + log.add(LogType.MSG_IMPORT_MERGE, 3); + facebookKey = key.merge(facebookKey, log, 4); + // If the merge didn't fail, use the new merged key + if (facebookKey != null) { + key = facebookKey; + } else { + log.add(LogType.MSG_IMPORT_MERGE_ERROR, 4); + } + } else if (facebookKey != null) { + key = facebookKey; + } + } catch (Keyserver.QueryFailedException e) { + // download failed, too bad. just proceed + Log.e(Constants.TAG, "query failed", e); + log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER, 3, e.getMessage()); + } + } } if (key == null) { 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 3f90a08c2..ed2123987 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 @@ -233,6 +233,18 @@ public abstract class OperationResult implements Parcelable { dest.writeParcelable(mSubResult, 0); } + public static final Parcelable.Creator<SubLogEntryParcel> CREATOR = new Parcelable.Creator<SubLogEntryParcel>() { + @Override + public SubLogEntryParcel createFromParcel(Parcel in) { + return new SubLogEntryParcel(in); + } + + @Override + public SubLogEntryParcel[] newArray(int size) { + return new SubLogEntryParcel[size]; + } + }; + @Override StringBuilder getPrintableLogEntry(Resources resources, int indent) { @@ -757,6 +769,7 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_FETCH_ERROR_KEYSERVER(LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver), MSG_IMPORT_FETCH_ERROR_KEYSERVER_SECRET (LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver_secret), MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase), + MSG_IMPORT_FETCH_FACEBOOK (LogLevel.INFO, R.string.msg_import_fetch_facebook), MSG_IMPORT_FETCH_KEYSERVER (LogLevel.INFO, R.string.msg_import_fetch_keyserver), MSG_IMPORT_FETCH_KEYSERVER_OK (LogLevel.DEBUG, R.string.msg_import_fetch_keyserver_ok), MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver), 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 12b091e32..c4f66b950 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 @@ -27,6 +27,9 @@ public class PgpSignEncryptResult extends InputPendingResult { byte[] mDetachedSignature; public long mOperationTime; + // this is the micalg parameter used in PGP/MIME, see RFC3156: + // https://tools.ietf.org/html/rfc3156#section-5 + private String mMicAlgDigestName; public void setDetachedSignature(byte[] detachedSignature) { mDetachedSignature = detachedSignature; @@ -74,4 +77,11 @@ public class PgpSignEncryptResult extends InputPendingResult { } }; + public void setMicAlgDigestName(String micAlgDigestName) { + mMicAlgDigestName = micAlgDigestName; + } + + public String getMicAlgDigestName() { + return mMicAlgDigestName; + } } 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 45641b33a..40d6a710b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -32,6 +32,7 @@ import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; import org.spongycastle.openpgp.PGPLiteralDataGenerator; import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; @@ -574,6 +575,13 @@ public class PgpSignEncryptOperation extends BaseOperation { // silently catch } result.setDetachedSignature(detachedByteOut.toByteArray()); + try { + String digestName = PGPUtil.getDigestName(input.getSignatureHashAlgorithm()); + // construct micalg parameter according to https://tools.ietf.org/html/rfc3156#section-5 + result.setMicAlgDigestName("pgp-" + digestName.toLowerCase()); + } catch (PGPException e) { + Log.e(Constants.TAG, "error setting micalg parameter!", e); + } } return result; } 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 5f48e44bd..a6d505763 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -327,6 +327,7 @@ public class OpenPgpService extends Service { Intent result = new Intent(); if (pgpResult.getDetachedSignature() != null && !cleartextSign) { result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature()); + result.putExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG, pgpResult.getMicAlgDigestName()); } result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); return result; @@ -573,18 +574,11 @@ public class OpenPgpService extends Service { // case RESULT_NOT_ENCRYPTED, but a signature, fallback to deprecated signatureOnly variable if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) { - // noinspection deprecation, TODO + // noinspection deprecation signatureResult.setSignatureOnly(true); } - // case RESULT_INSECURE, fallback to an error - if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_INSECURE) { - Intent resultError = new Intent(); - resultError.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, - "Insecure encryption: An outdated algorithm has been used!")); - resultError.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); - return resultError; - } + // case RESULT_INSECURE, simply accept as a fallback like in previous API versions // case RESULT_ENCRYPTED // nothing to do! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index f1b4befe6..2985c2030 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.service; import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Activity; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.AbstractThreadedSyncAdapter; @@ -29,18 +27,16 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SyncResult; -import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceActivity; import android.provider.ContactsContract; import android.support.v4.app.NotificationCompat; -import android.widget.Toast; +import android.support.v4.app.NotificationManagerCompat; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.SettingsActivity; -import org.sufficientlysecure.keychain.ui.util.NotificationUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; @@ -61,7 +57,7 @@ public class ContactSyncAdapterService extends Service { final SyncResult syncResult) { Log.d(Constants.TAG, "Performing a contact sync!"); - ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); + new ContactHelper(ContactSyncAdapterService.this).writeKeysToContacts(); importKeys(); } @@ -86,14 +82,14 @@ public class ContactSyncAdapterService extends Service { ); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ContactSyncAdapterService.this) + .setAutoCancel(true) .setSmallIcon(R.drawable.ic_stat_notify_24dp) - .setLargeIcon(NotificationUtils.getBitmap(R.mipmap.ic_launcher, getBaseContext())) + .setColor(getResources().getColor(R.color.primary)) .setContentTitle(getString(R.string.sync_notification_permission_required_title)) .setContentText(getString(R.string.sync_notification_permission_required_text)) .setContentIntent(resultPendingIntent); - NotificationManager mNotifyMgr = - (NotificationManager) ContactSyncAdapterService.this.getSystemService(Activity.NOTIFICATION_SERVICE); - mNotifyMgr.notify(NOTIFICATION_ID_SYNC_SETTINGS, mBuilder.build()); + NotificationManagerCompat.from(ContactSyncAdapterService.this) + .notify(NOTIFICATION_ID_SYNC_SETTINGS, mBuilder.build()); } } @@ -155,22 +151,10 @@ public class ContactSyncAdapterService extends Service { } public static void enableContactsSync(Context context) { - try { - AccountManager manager = AccountManager.get(context); - Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); - - Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); - if (accounts.length == 0) { - if (!manager.addAccountExplicitly(account, null, null)) { - Log.d(Constants.TAG, "account already exists, the account is null, or another error occured"); - } - } - - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); - } catch (SecurityException e) { - Log.e(Constants.TAG, "SecurityException when adding the account", e); - Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show(); - } + AccountManager manager = AccountManager.get(context); + Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + + ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index fc3224e39..bacf56302 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -26,7 +26,6 @@ import android.os.Messenger; import android.os.PowerManager; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -55,7 +54,7 @@ public class KeyserverSyncAdapterService extends Service { // how often a sync should be initiated, in s public static final long SYNC_INTERVAL = Constants.DEBUG_KEYSERVER_SYNC - ? TimeUnit.MINUTES.toSeconds(2) : TimeUnit.DAYS.toSeconds(3); + ? TimeUnit.MINUTES.toSeconds(1) : TimeUnit.DAYS.toSeconds(3); // time since last update after which a key should be updated again, in s public static final long KEY_UPDATE_LIMIT = Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7); @@ -82,6 +81,12 @@ public class KeyserverSyncAdapterService extends Service { // introduced due to https://github.com/open-keychain/open-keychain/issues/1573 return START_NOT_STICKY; // we can't act on this Intent and don't want it redelivered } + + if (!isSyncEnabled()) { + // if we have initiated a sync, but the user disabled it in preferences since + return START_NOT_STICKY; + } + switch (intent.getAction()) { case ACTION_CANCEL: { mCancelled.set(true); @@ -440,7 +445,7 @@ public class KeyserverSyncAdapterService extends Service { String hexKeyId = KeyFormattingUtils .convertKeyIdToHex(keyId); // we aren't updating from keybase as of now - keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null)); + keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId)); } keyCursor.close(); @@ -505,30 +510,24 @@ public class KeyserverSyncAdapterService extends Service { } public static void enableKeyserverSync(Context context) { - try { - AccountManager manager = AccountManager.get(context); - Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); - - Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); - if (accounts.length == 0) { - if (!manager.addAccountExplicitly(account, null, null)) { - Log.d(Constants.TAG, "account already exists, the account is null, or another error occured"); - } - } - // for keyserver sync - ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, - true); - ContentResolver.addPeriodicSync( - account, - Constants.PROVIDER_AUTHORITY, - new Bundle(), - SYNC_INTERVAL - ); - } catch (SecurityException e) { - Log.e(Constants.TAG, "SecurityException when adding the account", e); - Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show(); - } + AccountManager manager = AccountManager.get(context); + Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + + ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); + ContentResolver.addPeriodicSync( + account, + Constants.PROVIDER_AUTHORITY, + new Bundle(), + SYNC_INTERVAL + ); + } + + private boolean isSyncEnabled() { + AccountManager manager = AccountManager.get(this); + Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + + return ContentResolver.getSyncAutomatically(account, Constants.PROVIDER_AUTHORITY); } private void startServiceWithUpdateAll() { 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 67c295b6d..73da3aff9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -18,8 +18,6 @@ package org.sufficientlysecure.keychain.service; -import java.util.Date; - import android.app.AlarmManager; import android.app.Notification; import android.app.PendingIntent; @@ -44,11 +42,12 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.util.NotificationUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; +import java.util.Date; + /** * This service runs in its own process, but is available to all other processes as the main * passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for @@ -473,7 +472,7 @@ public class PassphraseCacheService extends Service { private Notification getNotification() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) - .setLargeIcon(NotificationUtils.getBitmap(R.mipmap.ic_launcher, getBaseContext())) + .setColor(getResources().getColor(R.color.primary)) .setContentTitle(getResources().getQuantityString(R.plurals.passp_cache_notif_n_keys, mPassphraseCache.size(), mPassphraseCache.size())) .setContentText(getString(R.string.passp_cache_notif_touch_to_clear)); @@ -504,7 +503,7 @@ public class PassphraseCacheService extends Service { // Add clear PI action below text builder.addAction( - R.drawable.abc_ic_clear_mtrl_alpha, + R.drawable.ic_close_white_24dp, getString(R.string.passp_cache_notif_clear), clearCachePi ); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java index 0a2d52617..5712f4452 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java @@ -218,15 +218,10 @@ public class CreateYubiKeyImportResetFragment public void importKey() { ArrayList<ParcelableKeyRing> keyList = new ArrayList<>(); - keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null)); + keyList.add(new ParcelableKeyRing(mNfcFingerprint, null)); mKeyList = keyList; - { - Preferences prefs = Preferences.getPreferences(getActivity()); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - mKeyserver = cloudPrefs.keyserver; - } + mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); super.setProgressMessageResource(R.string.progress_importing); 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 37dd6afad..351b62ba7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -138,16 +138,11 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. final String keyserver; // search config - { - Preferences prefs = Preferences.getPreferences(getActivity()); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - keyserver = cloudPrefs.keyserver; - } + keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, - KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null); + KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); 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 922dd7307..9419cf8ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -746,16 +745,11 @@ public class DecryptListFragment final String keyserver; // search config - { - Preferences prefs = Preferences.getPreferences(getActivity()); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - keyserver = cloudPrefs.keyserver; - } + keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, - KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null); + KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); 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 7f7532ddf..c54f55b6f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -30,6 +30,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; @@ -41,6 +42,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify; 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 java.io.IOException; import java.util.ArrayList; @@ -50,6 +52,8 @@ public class ImportKeysActivity extends BaseNfcActivity public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; + public static final String ACTION_IMPORT_KEY_FROM_FACEBOOK + = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FACEBOOK"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN_RESULT"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE = Constants.INTENT_PREFIX @@ -101,11 +105,6 @@ public class ImportKeysActivity extends BaseNfcActivity if (getIntent().hasExtra(EXTRA_PENDING_INTENT_DATA)) { mPendingIntentData = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT_DATA); } - - // if we aren't being restored, initialize fragments - if (savedInstanceState == null) { - handleActions(getIntent()); - } } @Override @@ -113,6 +112,18 @@ public class ImportKeysActivity extends BaseNfcActivity setContentView(R.layout.import_keys_activity); } + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + + @Override + protected void onResumeFragments() { + super.onResumeFragments(); + handleActions(getIntent()); + } + protected void handleActions(Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); @@ -124,7 +135,9 @@ public class ImportKeysActivity extends BaseNfcActivity } if (Intent.ACTION_VIEW.equals(action)) { - if ("http".equals(scheme) || "https".equals(scheme)) { + if (FacebookKeyserver.isFacebookHost(dataUri)) { + action = ACTION_IMPORT_KEY_FROM_FACEBOOK; + } else if ("http".equals(scheme) || "https".equals(scheme)) { action = ACTION_SEARCH_KEYSERVER_FROM_URL; } else { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) @@ -205,26 +218,31 @@ public class ImportKeysActivity extends BaseNfcActivity } break; } + case ACTION_IMPORT_KEY_FROM_FACEBOOK: { + String fbUsername = FacebookKeyserver.getUsernameFromUri(dataUri); + + Preferences.CloudSearchPrefs cloudSearchPrefs = + new Preferences.CloudSearchPrefs(false, true, true, null); + // we allow our users to edit the query if they wish + startTopCloudFragment(fbUsername, false, cloudSearchPrefs); + // search immediately + startListFragment(null, null, fbUsername, cloudSearchPrefs); + break; + } case ACTION_SEARCH_KEYSERVER_FROM_URL: { // need to process URL to get search query and keyserver authority String query = dataUri.getQueryParameter("search"); - String keyserver = dataUri.getAuthority(); // if query not specified, we still allow users to search the keyserver in the link if (query == null) { Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE, Notify.Style.WARN).show(); - // we just set the keyserver - startTopCloudFragment(null, false, keyserver); - // 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(null, null, null, null); - } else { - // we allow our users to edit the query if they wish - startTopCloudFragment(query, false, keyserver); - // search immediately - startListFragment(null, null, query, keyserver); } + Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs( + true, true, true, dataUri.getAuthority()); + // we allow our users to edit the query if they wish + startTopCloudFragment(query, false, cloudSearchPrefs); + // search immediately (if query is not null) + startListFragment(null, null, query, cloudSearchPrefs); break; } case ACTION_IMPORT_KEY_FROM_FILE: @@ -254,18 +272,21 @@ public class ImportKeysActivity extends BaseNfcActivity } /** - * if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately - * load content + * Shows the list of keys to be imported. + * If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately + * load content. * - * @param bytes bytes containing list of keyrings to import - * @param dataUri uri to file to import keyrings from - * @param serverQuery query to search for on the keyserver - * @param keyserver keyserver authority to search on. If null will use keyserver from - * user preferences + * @param bytes bytes containing list of keyrings to import + * @param dataUri uri to file to import keyrings from + * @param serverQuery query to search for on the keyserver + * @param cloudSearchPrefs search specifications to use. If null will retrieve from user's + * preferences. */ - private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, String keyserver) { + private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, + Preferences.CloudSearchPrefs cloudSearchPrefs) { Fragment listFragment = - ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, keyserver); + ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, + cloudSearchPrefs); getSupportFragmentManager().beginTransaction() .replace(R.id.import_keys_list_container, listFragment, TAG_FRAG_LIST) .commit(); @@ -283,14 +304,16 @@ public class ImportKeysActivity extends BaseNfcActivity * loads the CloudFragment, which consists of the search bar, search button and settings icon * visually. * - * @param query search query - * @param disableQueryEdit if true, user will not be able to edit the search query - * @param keyserver keyserver authority to use for search, if null will use keyserver - * specified in user preferences + * @param query search query + * @param disableQueryEdit if true, user will not be able to edit the search query + * @param cloudSearchPrefs keyserver authority to use for search, if null will use keyserver + * specified in user preferences */ - private void startTopCloudFragment(String query, boolean disableQueryEdit, String keyserver) { + private void startTopCloudFragment(String query, boolean disableQueryEdit, + Preferences.CloudSearchPrefs cloudSearchPrefs) { findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); - Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver); + Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, + cloudSearchPrefs); getSupportFragmentManager().beginTransaction() .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP) .commit(); @@ -363,9 +386,8 @@ public class ImportKeysActivity extends BaseNfcActivity // change the format into ParcelableKeyRing ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries(); for (ImportKeysListEntry entry : entries) { - keys.add(new ParcelableKeyRing( - entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getExtraData()) - ); + keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), + entry.getKeyIdHex(), entry.getKeybaseName(), entry.getFbUsername())); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java index 1cd5c24f3..fb0217cda 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java @@ -42,33 +42,35 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.util.List; +/** + * Consists of the search bar, search button, and search settings button + */ public class ImportKeysCloudFragment extends Fragment { public static final String ARG_QUERY = "query"; public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit"; - public static final String ARG_KEYSERVER = "keyserver"; + public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; private ImportKeysActivity mImportActivity; - private View mSearchButton; private AutoCompleteTextView mQueryEditText; - private View mConfigButton; /** * Creates new instance of this fragment * * @param query query to search for * @param disableQueryEdit if true, user cannot edit query - * @param keyserver specified keyserver authority to use. If null, will use keyserver - * specified in user preferences + * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's + * preferences. */ public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit, - String keyserver) { + Preferences.CloudSearchPrefs + cloudSearchPrefs) { ImportKeysCloudFragment frag = new ImportKeysCloudFragment(); Bundle args = new Bundle(); args.putString(ARG_QUERY, query); args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit); - args.putString(ARG_KEYSERVER, keyserver); + args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); frag.setArguments(args); @@ -82,12 +84,11 @@ public class ImportKeysCloudFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false); - mSearchButton = view.findViewById(R.id.cloud_import_server_search); mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query); - mConfigButton = view.findViewById(R.id.cloud_import_server_config_button); - List<String> namesAndEmails = ContactHelper.getContactNames(getActivity()); - namesAndEmails.addAll(ContactHelper.getContactMails(getActivity())); + ContactHelper contactHelper = new ContactHelper(getActivity()); + List<String> namesAndEmails = contactHelper.getContactNames(); + namesAndEmails.addAll(contactHelper.getContactMails()); mQueryEditText.setThreshold(3); mQueryEditText.setAdapter( new ArrayAdapter<> @@ -96,7 +97,8 @@ public class ImportKeysCloudFragment extends Fragment { ) ); - mSearchButton.setOnClickListener(new OnClickListener() { + View searchButton = view.findViewById(R.id.cloud_import_server_search); + searchButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { search(mQueryEditText.getText().toString()); @@ -116,7 +118,8 @@ public class ImportKeysCloudFragment extends Fragment { } }); - mConfigButton.setOnClickListener(new OnClickListener() { + View configButton = view.findViewById(R.id.cloud_import_server_config_button); + configButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mImportActivity, SettingsActivity.class); @@ -159,15 +162,14 @@ public class ImportKeysCloudFragment extends Fragment { } private void search(String query) { - Preferences.CloudSearchPrefs cloudSearchPrefs; - String explicitKeyserver = getArguments().getString(ARG_KEYSERVER); - // no explicit keyserver passed - if (explicitKeyserver == null) { + Preferences.CloudSearchPrefs cloudSearchPrefs + = getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS); + + // no explicit search preferences passed + if (cloudSearchPrefs == null) { cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); - } else { - // assume we are also meant to search keybase.io - cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, explicitKeyserver); } + mImportActivity.loadCallback( new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs)); toggleKeyboard(false); 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 4955bad6e..b399af950 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -65,7 +65,7 @@ public class ImportKeysListFragment extends ListFragment implements private static final String ARG_BYTES = "bytes"; public static final String ARG_SERVER_QUERY = "query"; public static final String ARG_NON_INTERACTIVE = "non_interactive"; - public static final String ARG_KEYSERVER_URL = "keyserver_url"; + public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; @@ -140,32 +140,35 @@ public class ImportKeysListFragment extends ListFragment implements * 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 - * @param serverQuery query to search for on keyserver - * @param keyserver if not null, will perform search on specified keyserver. Else, uses - * keyserver specified in user preferences + * @param bytes byte data containing list of keyrings to be imported + * @param dataUri file from which keyrings are to be imported + * @param serverQuery query to search for on keyserver + * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's + * preferences. * @return fragment with arguments set based on passed parameters */ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, - String keyserver) { - return newInstance(bytes, dataUri, serverQuery, false, keyserver); + Preferences.CloudSearchPrefs cloudSearchPrefs) { + return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); } /** * Visually consists of a list of keyrings with checkboxes to specify which are to be imported * 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 - * @param serverQuery query to search for on keyserver - * @param nonInteractive if true, users will not be able to check/uncheck items in the list - * @param keyserver if set, will perform search on specified keyserver. If null, falls back - * to keyserver specified in user preferences + * @param bytes byte data containing list of keyrings to be imported + * @param dataUri file from which keyrings are to be imported + * @param serverQuery query to search for on keyserver + * @param nonInteractive if true, users will not be able to check/uncheck items in the list + * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's + * preferences. * @return fragment with arguments set based on passed parameters */ - public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, - boolean nonInteractive, String keyserver) { + public static ImportKeysListFragment newInstance(byte[] bytes, + Uri dataUri, + String serverQuery, + boolean nonInteractive, + Preferences.CloudSearchPrefs cloudSearchPrefs) { ImportKeysListFragment frag = new ImportKeysListFragment(); Bundle args = new Bundle(); @@ -173,7 +176,7 @@ public class ImportKeysListFragment extends ListFragment implements args.putParcelable(ARG_DATA_URI, dataUri); args.putString(ARG_SERVER_QUERY, serverQuery); args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); - args.putString(ARG_KEYSERVER_URL, keyserver); + args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); frag.setArguments(args); @@ -223,7 +226,6 @@ public class ImportKeysListFragment extends ListFragment implements Uri dataUri = args.getParcelable(ARG_DATA_URI); byte[] bytes = args.getByteArray(ARG_BYTES); String query = args.getString(ARG_SERVER_QUERY); - String keyserver = args.getString(ARG_KEYSERVER_URL); mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); getListView().setOnTouchListener(new OnTouchListener() { @@ -241,11 +243,10 @@ public class ImportKeysListFragment extends ListFragment implements if (dataUri != null || bytes != null) { mLoaderState = new BytesLoaderState(bytes, dataUri); } else if (query != null) { - Preferences.CloudSearchPrefs cloudSearchPrefs; - if (keyserver == null) { + Preferences.CloudSearchPrefs cloudSearchPrefs + = args.getParcelable(ARG_CLOUD_SEARCH_PREFS); + if (cloudSearchPrefs == null) { cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); - } else { - cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, keyserver); } mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); 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 45ce604c3..3969f4039 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; @@ -130,7 +129,6 @@ public class ImportKeysProxyActivity extends FragmentActivity String scannedContent = scanResult.getContents(); processScannedContent(scannedContent); - return; } } @@ -199,7 +197,7 @@ public class ImportKeysProxyActivity extends FragmentActivity } public void importKeys(String fingerprint) { - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); @@ -209,12 +207,7 @@ public class ImportKeysProxyActivity extends FragmentActivity private void startImportService(ArrayList<ParcelableKeyRing> keyRings) { // search config - { - Preferences prefs = Preferences.getPreferences(this); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - mKeyserver = cloudPrefs.keyserver; - } + mKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); mKeyList = keyRings; 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 db31bd0a1..dd8107304 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -588,7 +588,7 @@ public class KeyListFragment extends LoaderFragment while (cursor.moveToNext()) { byte[] blob = cursor.getBlob(0);//fingerprint column is 0 String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); keyList.add(keyEntry); } mKeyList = keyList; @@ -597,12 +597,7 @@ public class KeyListFragment extends LoaderFragment } // search config - { - Preferences prefs = Preferences.getPreferences(getActivity()); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - mKeyserver = cloudPrefs.keyserver; - } + mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); mImportOpHelper.setProgressCancellable(true); 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 9e962fa0d..cd754d60e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -29,11 +29,9 @@ import android.content.Intent; import android.content.pm.PackageManager; 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.preference.SwitchPreference; @@ -115,6 +113,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } + public static abstract class PresetPreferenceFragment extends PreferenceFragment { + @Override + public void addPreferencesFromResource(int preferencesResId) { + // so that preferences are written to our preference file, not the default + Preferences.setPreferenceManagerFileAndMode(this.getPreferenceManager()); + super.addPreferencesFromResource(preferencesResId); + } + } + @Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); @@ -124,7 +131,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { /** * This fragment shows the Cloud Search preferences */ - public static class CloudSearchPrefsFragment extends PreferenceFragment { + public static class CloudSearchPrefsFragment extends PresetPreferenceFragment { private PreferenceScreen mKeyServerPreference = null; @@ -149,12 +156,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return false; } }); - initializeSearchKeyserver( - (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) - ); - initializeSearchKeybase( - (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) - ); } @Override @@ -172,12 +173,20 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } } } + + public static String keyserverSummary(Context context) { + 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(); + } } /** * This fragment shows the PIN/password preferences */ - public static class PassphrasePrefsFragment extends PreferenceFragment { + public static class PassphrasePrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -186,18 +195,27 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // Load the preferences from an XML resource addPreferencesFromResource(R.xml.passphrase_preferences); - initializePassphraseCacheSubs( - (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS)); - initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); + } - initializeUseNumKeypadForYubiKeyPin( - (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); + private static void initializePassphraseCacheTtl( + final IntegerListPreference passphraseCacheTtl) { + passphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl()); + passphraseCacheTtl.setSummary(passphraseCacheTtl.getEntry()); + passphraseCacheTtl + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + passphraseCacheTtl.setValue(newValue.toString()); + passphraseCacheTtl.setSummary(passphraseCacheTtl.getEntry()); + sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString())); + return false; + } + }); } } - public static class ProxyPrefsFragment extends PreferenceFragment { + public static class ProxyPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -213,37 +231,18 @@ public class SettingsActivity extends AppCompatPreferenceActivity { private EditTextPreference mProxyHost; private EditTextPreference mProxyPort; private ListPreference mProxyType; - private PreferenceActivity mActivity; - private PreferenceFragment mFragment; + private PresetPreferenceFragment mFragment; - public Initializer(PreferenceFragment fragment) { + public Initializer(PresetPreferenceFragment 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); - } + return mFragment.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_preferences); - } else { - Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager()); - // Load the preferences from an XML resource - mActivity.addPreferencesFromResource(R.xml.proxy_preferences); - } + mFragment.addPreferencesFromResource(R.xml.proxy_preferences); mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); mUseNormalProxy = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); @@ -268,7 +267,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + Activity activity = mFragment.getActivity(); if ((Boolean) newValue) { boolean installed = OrbotHelper.isOrbotInstalled(activity); if (!installed) { @@ -314,7 +313,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + Activity activity = mFragment.getActivity(); if (TextUtils.isEmpty((String) newValue)) { Notify.create( activity, @@ -332,7 +331,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + Activity activity = mFragment.getActivity(); try { int port = Integer.parseInt((String) newValue); if (port < 0 || port > 65535) { @@ -407,7 +406,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { /** * This fragment shows the keyserver/contacts sync preferences */ - public static class SyncPrefsFragment extends PreferenceFragment { + public static class SyncPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -477,6 +476,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { private boolean checkContactsPermission(String authority) { if (!ContactsContract.AUTHORITY.equals(authority)) { + // provides convenience of not using separate checks for keyserver and contact sync + // in initializeSyncCheckBox return true; } @@ -542,7 +543,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { /** * This fragment shows experimental features */ - public static class ExperimentalPrefsFragment extends PreferenceFragment { + public static class ExperimentalPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -551,17 +552,23 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // Load the preferences from an XML resource addPreferencesFromResource(R.xml.experimental_preferences); - initializeExperimentalEnableWordConfirm( - (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM)); + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); - initializeExperimentalEnableLinkedIdentities( - (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES)); + } - initializeExperimentalEnableKeybase( - (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE)); + private static void initializeTheme(final ListPreference themePref) { + themePref.setSummary(themePref.getEntry() + "\n" + + themePref.getContext().getString(R.string.label_experimental_settings_theme_summary)); + themePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + themePref.setSummary(newValue + "\n" + + themePref.getContext().getString(R.string.label_experimental_settings_theme_summary)); - initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + ((SettingsActivity) themePref.getContext()).recreate(); + return true; + } + }); } } @@ -573,125 +580,4 @@ public class SettingsActivity extends AppCompatPreferenceActivity { || ExperimentalPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } - - private static void initializePassphraseCacheSubs(final CheckBoxPreference mPassphraseCacheSubs) { - mPassphraseCacheSubs.setChecked(sPreferences.getPassphraseCacheSubs()); - mPassphraseCacheSubs.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mPassphraseCacheSubs.setChecked((Boolean) newValue); - sPreferences.setPassphraseCacheSubs((Boolean) newValue); - return false; - } - }); - } - - private static void initializePassphraseCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { - mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl()); - mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); - mPassphraseCacheTtl - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mPassphraseCacheTtl.setValue(newValue.toString()); - mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); - sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString())); - return false; - } - }); - } - - private static void initializeTheme(final ListPreference mTheme) { - mTheme.setValue(sPreferences.getTheme()); - mTheme.setSummary(mTheme.getEntry() + "\n" - + mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary)); - mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mTheme.setValue((String) newValue); - mTheme.setSummary(mTheme.getEntry() + "\n" - + mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary)); - sPreferences.setTheme((String) newValue); - - ((SettingsActivity) mTheme.getContext()).recreate(); - - return false; - } - }); - } - - private static void initializeSearchKeyserver(final SwitchPreference mSearchKeyserver) { - Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); - mSearchKeyserver.setChecked(prefs.searchKeyserver); - mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - mSearchKeyserver.setChecked((Boolean) newValue); - sPreferences.setSearchKeyserver((Boolean) newValue); - return false; - } - }); - } - - private static void initializeSearchKeybase(final SwitchPreference mSearchKeybase) { - Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); - mSearchKeybase.setChecked(prefs.searchKeybase); - mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - mSearchKeybase.setChecked((Boolean) newValue); - sPreferences.setSearchKeybase((Boolean) newValue); - return false; - } - }); - } - - public static String keyserverSummary(Context context) { - 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(); - } - - private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) { - mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin()); - mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mUseNumKeypadForYubiKeyPin.setChecked((Boolean) newValue); - sPreferences.setUseNumKeypadForYubiKeyPin((Boolean) newValue); - return false; - } - }); - } - - private static void initializeExperimentalEnableWordConfirm(final SwitchPreference mExperimentalEnableWordConfirm) { - mExperimentalEnableWordConfirm.setChecked(sPreferences.getExperimentalEnableWordConfirm()); - mExperimentalEnableWordConfirm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mExperimentalEnableWordConfirm.setChecked((Boolean) newValue); - sPreferences.setExperimentalEnableWordConfirm((Boolean) newValue); - return false; - } - }); - } - - private static void initializeExperimentalEnableLinkedIdentities(final SwitchPreference mExperimentalEnableLinkedIdentities) { - mExperimentalEnableLinkedIdentities.setChecked(sPreferences.getExperimentalEnableLinkedIdentities()); - mExperimentalEnableLinkedIdentities.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mExperimentalEnableLinkedIdentities.setChecked((Boolean) newValue); - sPreferences.setExperimentalEnableLinkedIdentities((Boolean) newValue); - return false; - } - }); - } - - private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) { - mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase()); - mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mExperimentalKeybase.setChecked((Boolean) newValue); - sPreferences.setExperimentalEnableKeybase((Boolean) newValue); - return false; - } - }); - } } 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 e1b796f38..35e00ff21 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -243,7 +243,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements return; } if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { - mDataUri = ContactHelper.dataUriFromContactUri(this, mDataUri); + mDataUri = new ContactHelper(this).dataUriFromContactUri(mDataUri); if (mDataUri == null) { Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!"); Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); @@ -855,8 +855,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(ViewKeyActivity.this, - getContentResolver(), mMasterKeyId[0], true); + return new ContactHelper(ViewKeyActivity.this) + .loadPhotoByMasterKeyId(mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { @@ -1038,18 +1038,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, 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; - } + mKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); mOperationHelper.cryptoOperation(); } 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 bba6a6dc1..94a171f14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -88,7 +88,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements return; } if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { - mDataUri = ContactHelper.dataUriFromContactUri(this, mDataUri); + mDataUri = new ContactHelper(this).dataUriFromContactUri(mDataUri); if (mDataUri == null) { Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!"); Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index dda2a680a..f75012731 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -224,17 +224,17 @@ public class ViewKeyFragment extends LoaderFragment implements if(contactId == -1) return; final Context context = mSystemContactName.getContext(); - final ContentResolver resolver = context.getContentResolver(); + ContactHelper contactHelper = new ContactHelper(context); String contactName = null; if (mIsSecret) {//all secret keys are linked to "me" profile in contacts - List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context); + List<String> mainProfileNames = contactHelper.getMainProfileContactName(); if (mainProfileNames != null && mainProfileNames.size() > 0) { contactName = mainProfileNames.get(0); } } else { - contactName = ContactHelper.getContactName(resolver, contactId); + contactName = contactHelper.getContactName(contactId); } if (contactName != null) {//contact name exists for given master key @@ -244,9 +244,9 @@ public class ViewKeyFragment extends LoaderFragment implements Bitmap picture; if (mIsSecret) { - picture = ContactHelper.loadMainProfilePhoto(getActivity(), resolver, false); + picture = contactHelper.loadMainProfilePhoto(false); } else { - picture = ContactHelper.loadPhotoByContactId(getActivity(), resolver, contactId, false); + picture = contactHelper.loadPhotoByContactId(contactId, false); } if (picture != null) mSystemContactPicture.setImageBitmap(picture); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java index e09b1e755..51f047952 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java @@ -208,18 +208,12 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen @Override public void onCryptoOperationSuccess(OperationResult result) { - // if bad -> display here! - if (!result.success()) { - result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this); - return; - } - getActivity().finish(); } @Override public void onCryptoOperationError(OperationResult result) { - + result.createNotify(getActivity()).show(this); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/NotificationUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/NotificationUtils.java deleted file mode 100644 index 40ff92a1c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/NotificationUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.util; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; - -public class NotificationUtils { - - // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android - public static Bitmap getBitmap(int resId, Context context) { - int mLargeIconWidth = (int) context.getResources().getDimension( - android.R.dimen.notification_large_icon_width); - int mLargeIconHeight = (int) context.getResources().getDimension( - android.R.dimen.notification_large_icon_height); - Drawable d; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // noinspection deprecation (can't help it at this api level) - d = context.getResources().getDrawable(resId); - } else { - d = context.getDrawable(resId); - } - if (d == null) { - return null; - } - Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight); - d.draw(c); - return b; - } -} 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 494ccb6d3..49b37692c 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 @@ -90,7 +90,7 @@ public class EmailEditText extends AppCompatAutoCompleteTextView { private void initAdapter() { setThreshold(1); // Start working from first character setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, - ContactHelper.getPossibleUserEmails(getContext()))); + new ContactHelper(getContext()).getPossibleUserEmails())); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index c812edf71..f98fda56f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.widget; + import android.content.Context; import android.database.Cursor; import android.graphics.Rect; @@ -37,7 +38,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import com.tokenautocomplete.TokenCompleteTextView; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -55,7 +55,6 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> private KeyAdapter mAdapter; private LoaderManager mLoaderManager; - private CharSequence mPrefix; public EncryptKeyCompletionView(Context context) { super(context); @@ -73,21 +72,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> } private void initView() { - setPrefix(getContext().getString(R.string.label_to) + " "); - allowDuplicates(false); + mAdapter = new KeyAdapter(getContext(), null, 0); setAdapter(mAdapter); } @Override - public void setPrefix(CharSequence p) { - // this one is private in the superclass, but we need it here - mPrefix = p; - super.setPrefix(p); - } - - @Override protected View getViewForObject(KeyItem keyItem) { LayoutInflater l = LayoutInflater.from(getContext()); View view = l.inflate(R.layout.recipient_box_entry, null); @@ -97,10 +88,6 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> @Override protected KeyItem defaultObject(String completionText) { - // TODO: We could try to automagically download the key if it's unknown but a key id - /*if (completionText.startsWith("0x")) { - - }*/ return null; } @@ -173,13 +160,10 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> @Override protected void performFiltering(@NonNull CharSequence text, int start, int end, int keyCode) { - super.performFiltering(text, start, end, keyCode); - if (start < mPrefix.length()) { - start = mPrefix.length(); - } +// super.performFiltering(text, start, end, keyCode); String query = text.subSequence(start, end).toString(); if (TextUtils.isEmpty(query) || query.length() < 2) { - mLoaderManager.destroyLoader(0); + mAdapter.swapCursor(null); return; } Bundle args = new Bundle(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NameEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NameEditText.java index 1a034537c..af259ca44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NameEditText.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NameEditText.java @@ -50,7 +50,7 @@ public class NameEditText extends AppCompatAutoCompleteTextView { setThreshold(1); // Start working from first character setAdapter(new ArrayAdapter<>( getContext(), android.R.layout.simple_spinner_dropdown_item, - ContactHelper.getPossibleUserNames(getContext()))); + new ContactHelper(getContext()).getPossibleUserNames())); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index ab9587910..ea038aa1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -54,14 +54,17 @@ public class ContactHelper { private static final Map<Long, Bitmap> photoCache = new HashMap<>(); - public static List<String> getPossibleUserEmails(Context context) { - if (!isContactsPermissionGranted(context)) { - Log.w(Constants.TAG, "getting emails not possible READ_CONTACTS permission denied!"); - return new ArrayList<>(); - } + private Context mContext; + private ContentResolver mContentResolver; - Set<String> accountMails = getAccountEmails(context); - accountMails.addAll(getMainProfileContactEmails(context)); + public ContactHelper(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + } + + public List<String> getPossibleUserEmails() { + Set<String> accountMails = getAccountEmails(); + accountMails.addAll(getMainProfileContactEmails()); // remove items that are not an email Iterator<String> it = accountMails.iterator(); @@ -77,15 +80,10 @@ public class ContactHelper { return new ArrayList<>(accountMails); } - public static List<String> getPossibleUserNames(Context context) { - if (!isContactsPermissionGranted(context)) { - Log.w(Constants.TAG, "getting names not possible READ_CONTACTS permission denied!"); - return new ArrayList<>(); - } - - Set<String> accountMails = getAccountEmails(context); - Set<String> names = getContactNamesFromEmails(context, accountMails); - names.addAll(getMainProfileContactName(context)); + public List<String> getPossibleUserNames() { + Set<String> accountMails = getAccountEmails(); + Set<String> names = getContactNamesFromEmails(accountMails); + names.addAll(getMainProfileContactName()); // remove items that are an email Iterator<String> it = names.iterator(); @@ -102,12 +100,9 @@ public class ContactHelper { /** * Get emails from AccountManager - * - * @param context - * @return */ - private static Set<String> getAccountEmails(Context context) { - final Account[] accounts = AccountManager.get(context).getAccounts(); + private Set<String> getAccountEmails() { + final Account[] accounts = AccountManager.get(mContext).getAccounts(); final Set<String> emailSet = new HashSet<>(); for (Account account : accounts) { emailSet.add(account.name); @@ -118,16 +113,15 @@ public class ContactHelper { /** * Search for contact names based on a list of emails (to find out the names of the * device owner based on the email addresses from AccountsManager) - * - * @param context - * @param emails - * @return */ - private static Set<String> getContactNamesFromEmails(Context context, Set<String> emails) { + private Set<String> getContactNamesFromEmails(Set<String> emails) { + if (!isContactsPermissionGranted()) { + return new HashSet<>(); + } + Set<String> names = new HashSet<>(); for (String email : emails) { - ContentResolver resolver = context.getContentResolver(); - Cursor profileCursor = resolver.query( + Cursor profileCursor = mContentResolver.query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Email.ADDRESS, @@ -156,13 +150,13 @@ public class ContactHelper { /** * Retrieves the emails of the primary profile contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html - * - * @param context - * @return */ - private static Set<String> getMainProfileContactEmails(Context context) { - ContentResolver resolver = context.getContentResolver(); - Cursor profileCursor = resolver.query( + private Set<String> getMainProfileContactEmails() { + if (!isContactsPermissionGranted()) { + return new HashSet<>(); + } + + Cursor profileCursor = mContentResolver.query( Uri.withAppendedPath( ContactsContract.Profile.CONTENT_URI, ContactsContract.Contacts.Data.CONTENT_DIRECTORY), @@ -197,13 +191,13 @@ public class ContactHelper { /** * Retrieves the name of the primary profile contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html - * - * @param context - * @return */ - public static List<String> getMainProfileContactName(Context context) { - ContentResolver resolver = context.getContentResolver(); - Cursor profileCursor = resolver.query( + public List<String> getMainProfileContactName() { + if (!isContactsPermissionGranted()) { + return new ArrayList<>(); + } + + Cursor profileCursor = mContentResolver.query( ContactsContract.Profile.CONTENT_URI, new String[]{ ContactsContract.Profile.DISPLAY_NAME @@ -228,12 +222,9 @@ public class ContactHelper { /** * returns the CONTACT_ID of the main ("me") contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html - * - * @param resolver - * @return */ - public static long getMainProfileContactId(ContentResolver resolver) { - Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI, + private long getMainProfileContactId() { + Cursor profileCursor = mContentResolver.query(ContactsContract.Profile.CONTENT_URI, new String[]{ContactsContract.Profile._ID}, null, null, null); if (profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) { @@ -252,18 +243,24 @@ public class ContactHelper { * loads the profile picture of the main ("me") contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html * - * @param contentResolver - * @param highRes true for large image if present, false for thumbnail + * @param highRes true for large image if present, false for thumbnail * @return bitmap of loaded photo */ - public static Bitmap loadMainProfilePhoto(Context context, ContentResolver contentResolver, boolean highRes) { + public Bitmap loadMainProfilePhoto(boolean highRes) { + if (!isContactsPermissionGranted()) { + return null; + } + try { - long mainProfileContactId = getMainProfileContactId(contentResolver); + long mainProfileContactId = getMainProfileContactId(); Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Long.toString(mainProfileContactId)); - InputStream photoInputStream = - ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes); + InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream( + mContentResolver, + contactUri, + highRes + ); if (photoInputStream == null) { return null; } @@ -273,9 +270,12 @@ public class ContactHelper { } } - public static List<String> getContactMails(Context context) { - ContentResolver resolver = context.getContentResolver(); - Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, + public List<String> getContactMails() { + if (!isContactsPermissionGranted()) { + return new ArrayList<>(); + } + + Cursor mailCursor = mContentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ContactsContract.CommonDataKinds.Email.DATA}, null, null, null); if (mailCursor == null) { @@ -293,9 +293,12 @@ public class ContactHelper { return new ArrayList<>(mails); } - public static List<String> getContactNames(Context context) { - ContentResolver resolver = context.getContentResolver(); - Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, + public List<String> getContactNames() { + if (!isContactsPermissionGranted()) { + return new ArrayList<>(); + } + + Cursor cursor = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, new String[]{ContactsContract.Contacts.DISPLAY_NAME}, null, null, null); if (cursor == null) { @@ -313,9 +316,12 @@ public class ContactHelper { return new ArrayList<>(names); } - public static Uri dataUriFromContactUri(Context context, Uri contactUri) { + public Uri dataUriFromContactUri(Uri contactUri) { + if (!isContactsPermissionGranted()) { + return null; + } - Cursor contactMasterKey = context.getContentResolver().query(contactUri, + Cursor contactMasterKey = mContentResolver.query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null); if (contactMasterKey != null) { try { @@ -333,13 +339,11 @@ public class ContactHelper { * returns the CONTACT_ID of the raw contact to which a masterKeyId is associated, if the * raw contact has not been marked for deletion. * - * @param resolver - * @param masterKeyId * @return CONTACT_ID (id of aggregated contact) linked to masterKeyId */ - public static long findContactId(ContentResolver resolver, long masterKeyId) { + private long findContactId(long masterKeyId) { long contactId = -1; - Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, + Cursor raw = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ ContactsContract.RawContacts.CONTACT_ID }, @@ -364,14 +368,12 @@ public class ContactHelper { * Returns the display name of the system contact associated with contactId, null if the * contact does not exist * - * @param resolver - * @param contactId * @return primary display name of system contact associated with contactId, null if it does * not exist */ - public static String getContactName(ContentResolver resolver, long contactId) { + public String getContactName(long contactId) { String contactName = null; - Cursor raw = resolver.query(ContactsContract.Contacts.CONTENT_URI, + Cursor raw = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, new String[]{ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY }, @@ -388,21 +390,18 @@ public class ContactHelper { return contactName; } - public static Bitmap getCachedPhotoByMasterKeyId(Context context, ContentResolver contentResolver, - long masterKeyId) { + private Bitmap getCachedPhotoByMasterKeyId(long masterKeyId) { if (masterKeyId == -1) { return null; } if (!photoCache.containsKey(masterKeyId)) { - photoCache.put(masterKeyId, loadPhotoByMasterKeyId(context, contentResolver, masterKeyId, false)); + photoCache.put(masterKeyId, loadPhotoByMasterKeyId(masterKeyId, false)); } return photoCache.get(masterKeyId); } - public static Bitmap loadPhotoByMasterKeyId(Context context, ContentResolver contentResolver, - long masterKeyId, boolean highRes) { - if (!isContactsPermissionGranted(context)) { - Log.w(Constants.TAG, "loading photo not possible READ_CONTACTS permission denied!"); + public Bitmap loadPhotoByMasterKeyId(long masterKeyId, boolean highRes) { + if (!isContactsPermissionGranted()) { return null; } @@ -410,18 +409,16 @@ public class ContactHelper { return null; } try { - long contactId = findContactId(contentResolver, masterKeyId); - return loadPhotoByContactId(context, contentResolver, contactId, highRes); + long contactId = findContactId(masterKeyId); + return loadPhotoByContactId(contactId, highRes); } catch (Throwable ignored) { return null; } } - public static Bitmap loadPhotoByContactId(Context context, ContentResolver contentResolver, - long contactId, boolean highRes) { - if (!isContactsPermissionGranted(context)) { - Log.w(Constants.TAG, "loading photo not possible READ_CONTACTS permission denied!"); + public Bitmap loadPhotoByContactId(long contactId, boolean highRes) { + if (!isContactsPermissionGranted()) { return null; } @@ -436,7 +433,7 @@ public class ContactHelper { // Also, we don't need a permanent shortcut to the contact since we load it afresh each time InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream( - contentResolver, + mContentResolver, contactUri, highRes); @@ -466,40 +463,39 @@ public class ContactHelper { /** * Write/Update the current OpenKeychain keys to the contact db */ - public static void writeKeysToContacts(Context context) { - ContentResolver resolver = context.getContentResolver(); - + public void writeKeysToContacts() { if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) { - debugDeleteRawContacts(resolver); + debugDeleteRawContacts(); } - writeKeysToMainProfileContact(context, resolver); + writeKeysToMainProfileContact(); - writeKeysToNormalContacts(context, resolver); + writeKeysToNormalContacts(); } - private static boolean isContactsPermissionGranted(Context context) { + private boolean isContactsPermissionGranted() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } - if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) + if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { return true; } + Log.w(Constants.TAG, "READ_CONTACTS permission denied!"); return false; } - private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) { + private void writeKeysToNormalContacts() { // delete raw contacts flagged for deletion by user so they can be reinserted - deleteFlaggedNormalRawContacts(resolver); + deleteFlaggedNormalRawContacts(); - Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); + Set<Long> deletedKeys = getRawContactMasterKeyIds(); // Load all public Keys from OK // TODO: figure out why using selectionArgs does not work in this case - Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + Cursor cursor = mContentResolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, KeychainContract.KeyRings.HAS_ANY_SECRET + "=0", null, null); @@ -524,28 +520,28 @@ public class ContactHelper { Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting masterKeyId " + masterKeyId); if (masterKeyId != -1) { - deleteRawContactByMasterKeyId(resolver, masterKeyId); + deleteRawContactByMasterKeyId(masterKeyId); } } else if (userIdSplit.name != null) { // get raw contact to this master key id - long rawContactId = findRawContactId(resolver, masterKeyId); + long rawContactId = findRawContactId(masterKeyId); Log.d(Constants.TAG, "rawContactId: " + rawContactId); // Create a new rawcontact with corresponding key if it does not exist yet if (rawContactId == -1) { Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId); - insertContact(ops, context, masterKeyId); - writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name); + insertContact(ops, masterKeyId); + writeContactKey(ops, rawContactId, masterKeyId, userIdSplit.name); } // We always update the display name (which is derived from primary user id) // and email addresses from user id writeContactDisplayName(ops, rawContactId, userIdSplit.name); - writeContactEmail(ops, resolver, rawContactId, masterKeyId); + writeContactEmail(ops, rawContactId, masterKeyId); try { - resolver.applyBatch(ContactsContract.AUTHORITY, ops); + mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { Log.w(Constants.TAG, e); } @@ -557,25 +553,23 @@ public class ContactHelper { // Delete master key ids that are no longer present in OK for (Long masterKeyId : deletedKeys) { Log.d(Constants.TAG, "Delete raw contact with masterKeyId " + masterKeyId); - deleteRawContactByMasterKeyId(resolver, masterKeyId); + deleteRawContactByMasterKeyId(masterKeyId); } } /** * Links all keys with secrets to the main ("me") contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html - * - * @param context */ - public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) { + private void writeKeysToMainProfileContact() { // deletes contacts hidden by the user so they can be reinserted if necessary - deleteFlaggedMainProfileRawContacts(resolver); + deleteFlaggedMainProfileRawContacts(); - Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver); + Set<Long> keysToDelete = getMainProfileMasterKeyIds(); // get all keys which have associated secret keys // TODO: figure out why using selectionArgs does not work in this case - Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + Cursor cursor = mContentResolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0", null, null); @@ -597,10 +591,10 @@ public class ContactHelper { ArrayList<ContentProviderOperation> ops = new ArrayList<>(); insertMainProfileRawContact(ops, masterKeyId); - writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name); + writeContactKey(ops, rawContactId, masterKeyId, userIdSplit.name); try { - resolver.applyBatch(ContactsContract.AUTHORITY, ops); + mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { Log.w(Constants.TAG, e); } @@ -612,7 +606,7 @@ public class ContactHelper { } for (long masterKeyId : keysToDelete) { - deleteMainProfileRawContactByMasterKeyId(resolver, masterKeyId); + deleteMainProfileRawContactByMasterKeyId(masterKeyId); Log.d(Constants.TAG, "Delete main profile raw contact with masterKeyId " + masterKeyId); } } @@ -620,12 +614,9 @@ public class ContactHelper { /** * Inserts a raw contact into the table defined by ContactsContract.Profile * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html - * - * @param ops - * @param masterKeyId */ - private static void insertMainProfileRawContact(ArrayList<ContentProviderOperation> ops, - long masterKeyId) { + private void insertMainProfileRawContact(ArrayList<ContentProviderOperation> ops, + long masterKeyId) { ops.add(ContentProviderOperation.newInsert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) @@ -637,18 +628,15 @@ public class ContactHelper { * deletes a raw contact from the main profile table ("me" contact) * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html * - * @param resolver - * @param masterKeyId * @return number of rows deleted */ - private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, - long masterKeyId) { + private int deleteMainProfileRawContactByMasterKeyId(long masterKeyId) { // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise // would be just flagged for deletion Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - return resolver.delete(deleteUri, + return mContentResolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?", new String[]{ @@ -660,16 +648,15 @@ public class ContactHelper { * deletes all raw contact entries in the "me" contact flagged for deletion ('hidden'), * presumably by the user * - * @param resolver * @return number of raw contacts deleted */ - private static int deleteFlaggedMainProfileRawContacts(ContentResolver resolver) { + private int deleteFlaggedMainProfileRawContacts() { // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise // would be just flagged for deletion Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - return resolver.delete(deleteUri, + return mContentResolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.DELETED + "=?", new String[]{ @@ -684,14 +671,14 @@ public class ContactHelper { * * @return number of rows deleted */ - private static int debugDeleteRawContacts(ContentResolver resolver) { + private int debugDeleteRawContacts() { // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); Log.d(Constants.TAG, "Deleting all raw contacts associated to OK..."); - int delete = resolver.delete(deleteUri, + int delete = mContentResolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", new String[]{ Constants.ACCOUNT_TYPE @@ -700,7 +687,7 @@ public class ContactHelper { Uri mainProfileDeleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon() .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - delete += resolver.delete(mainProfileDeleteUri, + delete += mContentResolver.delete(mainProfileDeleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", new String[]{ Constants.ACCOUNT_TYPE @@ -713,17 +700,15 @@ public class ContactHelper { * Deletes raw contacts from ContactsContract.RawContacts based on masterKeyId. Does not * delete contacts from the "me" contact defined in ContactsContract.Profile * - * @param resolver - * @param masterKeyId * @return number of rows deleted */ - private static int deleteRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { + private int deleteRawContactByMasterKeyId(long masterKeyId) { // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - return resolver.delete(deleteUri, + return mContentResolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?", new String[]{ @@ -731,13 +716,13 @@ public class ContactHelper { }); } - private static int deleteFlaggedNormalRawContacts(ContentResolver resolver) { + private int deleteFlaggedNormalRawContacts() { // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise // would be just flagged for deletion Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - return resolver.delete(deleteUri, + return mContentResolver.delete(deleteUri, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.DELETED + "=?", new String[]{ @@ -749,9 +734,9 @@ public class ContactHelper { /** * @return a set of all key master key ids currently present in the contact db */ - private static Set<Long> getRawContactMasterKeyIds(ContentResolver resolver) { + private Set<Long> getRawContactMasterKeyIds() { HashSet<Long> result = new HashSet<>(); - Cursor masterKeyIds = resolver.query(ContactsContract.RawContacts.CONTENT_URI, + Cursor masterKeyIds = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ ContactsContract.RawContacts.SOURCE_ID }, @@ -771,9 +756,9 @@ public class ContactHelper { /** * @return a set of all key master key ids currently present in the contact db */ - private static Set<Long> getMainProfileMasterKeyIds(ContentResolver resolver) { + private Set<Long> getMainProfileMasterKeyIds() { HashSet<Long> result = new HashSet<>(); - Cursor masterKeyIds = resolver.query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI, + Cursor masterKeyIds = mContentResolver.query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI, new String[]{ ContactsContract.RawContacts.SOURCE_ID }, @@ -795,9 +780,9 @@ public class ContactHelper { * * @return raw contact id or -1 if not found */ - private static long findRawContactId(ContentResolver resolver, long masterKeyId) { + private long findRawContactId(long masterKeyId) { long rawContactId = -1; - Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, + Cursor raw = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ ContactsContract.RawContacts._ID }, @@ -817,7 +802,7 @@ public class ContactHelper { /** * Creates a empty raw contact with a given masterKeyId */ - private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, long masterKeyId) { + private void insertContact(ArrayList<ContentProviderOperation> ops, long masterKeyId) { ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) @@ -830,11 +815,11 @@ public class ContactHelper { * <p/> * This creates the link to OK in contact details */ - private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, long rawContactId, - long masterKeyId, String keyName) { + private void writeContactKey(ArrayList<ContentProviderOperation> ops, long rawContactId, + long masterKeyId, String keyName) { ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId) .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) - .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyName)) + .withValue(ContactsContract.Data.DATA1, mContext.getString(R.string.contact_show_key, keyName)) .withValue(ContactsContract.Data.DATA2, masterKeyId) .build()); } @@ -842,12 +827,12 @@ public class ContactHelper { /** * Write all known email addresses of a key (derived from user ids) to a given raw contact */ - private static void writeContactEmail(ArrayList<ContentProviderOperation> ops, ContentResolver resolver, - long rawContactId, long masterKeyId) { + private void writeContactEmail(ArrayList<ContentProviderOperation> ops, + long rawContactId, long masterKeyId) { ops.add(selectByRawContactAndItemType( ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); - Cursor ids = resolver.query(UserPackets.buildUserIdsUri(masterKeyId), + Cursor ids = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), new String[]{ UserPackets.USER_ID }, @@ -870,8 +855,8 @@ public class ContactHelper { } } - private static void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, long rawContactId, - String displayName) { + private void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, long rawContactId, + String displayName) { if (displayName != null) { ops.add(insertOrUpdateForRawContact(ContactsContract.Data.CONTENT_URI, rawContactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) @@ -880,15 +865,15 @@ public class ContactHelper { } } - private static ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder, - long rawContactId) { + private ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder, + long rawContactId) { return rawContactId == -1 ? builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) : builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId); } - private static ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, long rawContactId, - String itemType) { + private ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, long rawContactId, + String itemType) { if (rawContactId == -1) { return referenceRawContact(ContentProviderOperation.newInsert(uri), rawContactId).withValue( ContactsContract.Data.MIMETYPE, itemType); @@ -897,7 +882,7 @@ public class ContactHelper { } } - private static ContentProviderOperation.Builder selectByRawContactAndItemType( + private ContentProviderOperation.Builder selectByRawContactAndItemType( ContentProviderOperation.Builder builder, long rawContactId, String itemType) { return builder.withSelection( ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?", 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 a55249842..15ff4d47a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -44,7 +44,7 @@ public class EmailKeyHelper { private String mKeyserver; public ImportContactKeysCallback(Context context, String keyserver, Proxy proxy) { - this(context, ContactHelper.getContactMails(context), keyserver, proxy); + this(context, new ContactHelper(context).getContactMails(), keyserver, proxy); } public ImportContactKeysCallback(Context context, List<String> mails, String keyserver, @@ -57,7 +57,7 @@ public class EmailKeyHelper { // Put them in a list and import ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size()); for (ImportKeysListEntry entry : entries) { - keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null)); + keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex())); } mKeyList = keys; mKeyserver = keyserver; 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 559c5556f..ce81bbcac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -21,10 +21,10 @@ package org.sufficientlysecure.keychain.util; import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Resources; +import android.os.Parcel; +import android.os.Parcelable; import android.preference.PreferenceManager; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; @@ -42,7 +42,6 @@ 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; @@ -62,10 +61,15 @@ public class Preferences { } private Preferences(Context context) { - mResources = context.getResources(); updateSharedPreferences(context); } + /** + * Makes android's preference framework write to our file instead of default. + * This allows us to use the "persistent" attribute to simplify code, which automatically + * writes and reads preference values. + * @param manager + */ public static void setPreferenceManagerFileAndMode(PreferenceManager manager) { manager.setSharedPreferencesName(PREF_FILE_NAME); manager.setSharedPreferencesMode(PREF_FILE_MODE); @@ -130,12 +134,6 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, true); } - public void setUseNumKeypadForYubiKeyPin(boolean useNumKeypadForYubikeyPin) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, useNumKeypadForYubikeyPin); - editor.commit(); - } - public void setFirstTime(boolean value) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Constants.Pref.FIRST_TIME, value); @@ -181,18 +179,6 @@ public class Preferences { editor.commit(); } - public void setSearchKeyserver(boolean searchKeyserver) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.SEARCH_KEYSERVER, searchKeyserver); - editor.commit(); - } - - public void setSearchKeybase(boolean searchKeybase) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.SEARCH_KEYBASE, searchKeybase); - editor.commit(); - } - public void setFilesUseCompression(boolean compress) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Pref.FILE_USE_COMPRESSION, compress); @@ -266,17 +252,6 @@ public class Preferences { 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 = Pref.ProxyType.TYPE_HTTP; final String typeSocks = Pref.ProxyType.TYPE_SOCKS; @@ -338,12 +313,14 @@ public class Preferences { public CloudSearchPrefs getCloudSearchPrefs() { return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true), + false, getPreferredKeyserver()); } - public static class CloudSearchPrefs { + public static class CloudSearchPrefs implements Parcelable { public final boolean searchKeyserver; public final boolean searchKeybase; + public final boolean searchFacebook; public final String keyserver; /** @@ -351,41 +328,58 @@ public class Preferences { * @param searchKeybase should keybase.io be searched * @param keyserver the keyserver url authority to search on */ - public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, String keyserver) { + public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, + boolean searchFacebook, String keyserver) { this.searchKeyserver = searchKeyserver; this.searchKeybase = searchKeybase; + this.searchFacebook = searchFacebook; this.keyserver = keyserver; } - } - // experimental prefs + protected CloudSearchPrefs(Parcel in) { + searchKeyserver = in.readByte() != 0x00; + searchKeybase = in.readByte() != 0x00; + searchFacebook = in.readByte() != 0x00; + keyserver = in.readString(); + } - public void setExperimentalEnableWordConfirm(boolean enableWordConfirm) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, enableWordConfirm); - editor.commit(); + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (searchKeyserver ? 0x01 : 0x00)); + dest.writeByte((byte) (searchKeybase ? 0x01 : 0x00)); + dest.writeByte((byte) (searchFacebook ? 0x01 : 0x00)); + dest.writeString(keyserver); + } + + public static final Parcelable.Creator<CloudSearchPrefs> CREATOR + = new Parcelable.Creator<CloudSearchPrefs>() { + @Override + public CloudSearchPrefs createFromParcel(Parcel in) { + return new CloudSearchPrefs(in); + } + + @Override + public CloudSearchPrefs[] newArray(int size) { + return new CloudSearchPrefs[size]; + } + }; } + // experimental prefs + public boolean getExperimentalEnableWordConfirm() { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, false); } - public void setExperimentalEnableLinkedIdentities(boolean enableLinkedIdentities) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, enableLinkedIdentities); - editor.commit(); - } - public boolean getExperimentalEnableLinkedIdentities() { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, false); } - public void setExperimentalEnableKeybase(boolean enableKeybase) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, enableKeybase); - editor.commit(); - } - public boolean getExperimentalEnableKeybase() { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false); } diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index 56647ec65..ab1bf3d4a 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -40,6 +40,7 @@ <org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView android:id="@+id/recipient_list" android:layout_width="match_parent" + android:hint="@string/label_to" android:minHeight="56dip" android:paddingLeft="8dp" android:paddingRight="8dp" diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 075d5377c..81f0f2be4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -150,7 +150,7 @@ <string name="label_write_version_header_summary">"Writes 'OpenKeychain v2.7' to OpenPGP signatures, ciphertext, and exported keys"</string> <string name="label_use_num_keypad_for_yubikey_pin">Use number keypad for YubiKey PIN</string> <string name="label_asymmetric_from">"Sign with:"</string> - <string name="label_to">"Encrypt to:"</string> + <string name="label_to">"Encrypt to"</string> <string name="label_delete_after_encryption">"Delete files after encryption"</string> <string name="label_delete_after_decryption">"Delete after decryption"</string> <string name="label_encryption_algorithm">"Encryption algorithm"</string> @@ -193,6 +193,8 @@ <string name="pref_keyserver_summary">"Search keys on selected OpenPGP keyservers (HKP protocol)"</string> <string name="pref_keybase">"keybase.io"</string> <string name="pref_keybase_summary">"Search keys on keybase.io"</string> + <string name="pref_facebook">"Facebook"</string> + <string name="pref_facebook_summary">"Search keys on Facebook by username"</string> <string name="label_sync_settings_keyserver_title">"Automatic key updates"</string> <string name="label_sync_settings_keyserver_summary_on">"Every three days, keys are updated from the preferred keyserver"</string> @@ -1294,6 +1296,7 @@ <string name="msg_import_fetch_error_keyserver">"Could not retrieve key from keyservers: %s"</string> <string name="msg_import_fetch_error_keyserver_secret">"Cannot import secret key from keyserver!"</string> <string name="msg_import_fetch_keybase">"Retrieving from keybase.io: %s"</string> + <string name="msg_import_fetch_facebook">"Retrieving from Facebook: %s"</string> <string name="msg_import_fetch_keyserver">"Retrieving from keyserver: %s"</string> <string name="msg_import_fetch_keyserver_ok">"Key retrieval successful"</string> <string name="msg_import_keyserver">"Using keyserver %s"</string> diff --git a/OpenKeychain/src/main/res/xml/experimental_preferences.xml b/OpenKeychain/src/main/res/xml/experimental_preferences.xml index ff1fa5a95..9312997b0 100644 --- a/OpenKeychain/src/main/res/xml/experimental_preferences.xml +++ b/OpenKeychain/src/main/res/xml/experimental_preferences.xml @@ -1,37 +1,38 @@ <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <Preference - android:persistent="false" + android:persistent="true" android:selectable="false" - android:title="@string/label_experimental_settings_desc_title" - android:summary="@string/label_experimental_settings_desc_summary" /> + android:summary="@string/label_experimental_settings_desc_summary" + android:title="@string/label_experimental_settings_desc_title" /> <SwitchPreference android:defaultValue="false" android:key="experimentalEnableWordConfirm" - android:persistent="false" + android:persistent="true" android:summary="@string/label_experimental_settings_word_confirm_summary" android:title="@string/label_experimental_settings_word_confirm_title" /> <SwitchPreference android:defaultValue="false" android:key="experimentalEnableLinkedIdentities" - android:persistent="false" + android:persistent="true" android:summary="@string/label_experimental_settings_linked_identities_summary" android:title="@string/label_experimental_settings_linked_identities_title" /> <SwitchPreference android:defaultValue="false" android:key="experimentalEnableKeybase" - android:persistent="false" + android:persistent="true" android:summary="@string/label_experimental_settings_keybase_summary" android:title="@string/label_experimental_settings_keybase_title" /> <ListPreference + android:defaultValue="light" android:dialogTitle="@string/label_theme" android:entries="@array/theme_entries" android:entryValues="@array/theme_values" android:key="theme" - android:persistent="false" + android:persistent="true" android:title="@string/label_theme" /> </PreferenceScreen> diff --git a/OpenKeychain/src/main/res/xml/passphrase_preferences.xml b/OpenKeychain/src/main/res/xml/passphrase_preferences.xml index 86de8a4b7..75f293c43 100644 --- a/OpenKeychain/src/main/res/xml/passphrase_preferences.xml +++ b/OpenKeychain/src/main/res/xml/passphrase_preferences.xml @@ -7,11 +7,11 @@ android:title="@string/label_passphrase_cache_ttl" /> <CheckBoxPreference android:key="passphraseCacheSubs" - android:persistent="false" + android:persistent="true" android:title="@string/label_passphrase_cache_subs" /> <CheckBoxPreference android:defaultValue="false" android:key="useNumKeypadForYubikeyPin" - android:persistent="false" + android:persistent="true" android:title="@string/label_use_num_keypad_for_yubikey_pin" /> </PreferenceScreen> diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java new file mode 100644 index 000000000..b4a7e1fd3 --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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 java.io.PrintStream; +import java.security.Security; +import java.util.ArrayList; +import java.util.Date; +import java.util.Random; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.sufficientlysecure.keychain.WorkaroundBuildConfig; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +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.util.Passphrase; +import org.sufficientlysecure.keychain.util.ProgressScaler; +import org.sufficientlysecure.keychain.util.TestingUtils; + + +@RunWith(RobolectricGradleTestRunner.class) +@Config(constants = WorkaroundBuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml") +public class BenchmarkOperationTest { + + static PrintStream oldShadowStream; + + @BeforeClass + public static void setUpOnce() throws Exception { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + oldShadowStream = ShadowLog.stream; + ShadowLog.stream = System.out; + } + + @Test + public void testBenchmark() throws Exception { + BenchmarkOperation op = new BenchmarkOperation(RuntimeEnvironment.application, + new ProviderHelper(RuntimeEnvironment.application), null); + + op.execute(new BenchmarkInputParcel(), null); + } + +} |