diff options
Diffstat (limited to 'OpenKeychain/src/main')
30 files changed, 537 insertions, 350 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 50ab9aaae..74bf936b4 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -76,6 +76,7 @@ <!-- other group (for free) --> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> @@ -89,6 +90,15 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.Keychain.Light"> + <!-- broadcast receiver for Wi-Fi Connection --> + <receiver + android:name=".receiver.NetworkReceiver" + android:enabled="false" + android:exported="true" > + <intent-filter> + <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> + </intent-filter> + </receiver> <!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity --> <activity android:name=".ui.MainActivity" diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 53fb5afc6..fd6e903fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -118,6 +118,7 @@ public final class Constants { // keyserver sync settings public static final String SYNC_CONTACTS = "syncContacts"; public static final String SYNC_KEYSERVER = "syncKeyserver"; + public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly"; // other settings public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index e1f61a5ef..2f0ebe904 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -100,6 +100,11 @@ public class KeychainApplication extends Application { // Add OpenKeychain account to Android to link contacts with keys and keyserver sync createAccountIfNecessary(this); + if (Preferences.getKeyserverSyncEnabled(this)) { + // will update a keyserver sync if the interval has changed + KeyserverSyncAdapterService.enableKeyserverSync(this); + } + // if first time, enable keyserver and contact sync if (Preferences.getPreferences(this).isFirstTime()) { KeyserverSyncAdapterService.enableKeyserverSync(this); 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 ec2fddbd0..a3979904c 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 @@ -566,8 +566,6 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size), MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), - MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), - MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin), MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index e43548165..102e11674 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,6 +18,23 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.spec.ECGenParameterSpec; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; + import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; @@ -57,13 +74,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; 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.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -71,22 +87,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.SignatureException; -import java.security.spec.ECGenParameterSpec; -import java.util.Arrays; -import java.util.Date; -import java.util.Iterator; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicBoolean; - /** * This class is the single place where ALL operations that actually modify a PGP public or secret * key take place. @@ -1058,8 +1058,8 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; - sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + saveParcel.mNewUnlock.mNewPassphrase, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -1191,76 +1191,6 @@ public class PgpKeyOperation { } - - private static PGPSecretKeyRing applyNewUnlock( - PGPSecretKeyRing sKR, - PGPPublicKey masterPublicKey, - PGPPrivateKey masterPrivateKey, - Passphrase passphrase, - ChangeUnlockParcel newUnlock, - OperationLog log, int indent) throws PGPException { - - if (newUnlock.mNewPassphrase != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); - - // if there is any old packet with notation data - if (hasNotationData(sKR)) { - - log.add(LogType.MSG_MF_NOTATION_EMPTY, indent); - - // add packet with EMPTY notation data (updates old one, but will be stripped later) - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); - } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - } - - return sKR; - } - - if (newUnlock.mNewPin != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); - - log.add(LogType.MSG_MF_NOTATION_PIN, indent); - - // add packet with "pin" notation data - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1"); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); - } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - - return sKR; - } - - throw new UnsupportedOperationException("PIN passphrases not yet implemented!"); - - } - /** This method returns true iff the provided keyring has a local direct key signature * with notation data. */ @@ -1294,8 +1224,7 @@ public class PgpKeyOperation { PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); - // noinspection unchecked - for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { + for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) { log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index d696b9d70..b0db36b06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -35,6 +35,8 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import android.support.annotation.VisibleForTesting; + import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -1310,4 +1319,37 @@ public class UncachedKeyRing { || algorithm == PGPPublicKey.ECDH; } + // ONLY TO BE USED FOR TESTING!! + @VisibleForTesting + public static UncachedKeyRing forTestingOnlyAddDummyLocalSignature( + UncachedKeyRing uncachedKeyRing, String passphrase) throws Exception { + PGPSecretKeyRing sKR = (PGPSecretKeyRing) uncachedKeyRing.mRing; + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey masterPrivateKey = sKR.getSecretKey().extractPrivateKey(keyDecryptor); + PGPPublicKey masterPublicKey = uncachedKeyRing.mRing.getPublicKey(); + + // add packet with "pin" notation data + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + hashedPacketsGen.setNotationData(false, true, "dummynotationdata", "some data"); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + + return new UncachedKeyRing(sKR); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java new file mode 100644 index 000000000..7c103a9a3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java @@ -0,0 +1,52 @@ +package org.sufficientlysecure.keychain.receiver; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; +import org.sufficientlysecure.keychain.util.Log; + +public class NetworkReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = conn.getActiveNetworkInfo(); + boolean isTypeWifi = (networkInfo.getType() == ConnectivityManager.TYPE_WIFI); + boolean isConnected = networkInfo.isConnected(); + + if (isTypeWifi && isConnected) { + + // broadcaster receiver disabled + setWifiReceiverComponent(false, context); + Intent serviceIntent = new Intent(context, KeyserverSyncAdapterService.class); + serviceIntent.setAction(KeyserverSyncAdapterService.ACTION_SYNC_NOW); + context.startService(serviceIntent); + } + } + + public void setWifiReceiverComponent(Boolean isEnabled, Context context) { + + PackageManager pm = context.getPackageManager(); + ComponentName compName = new ComponentName(context, + NetworkReceiver.class); + + if (isEnabled) { + pm.setComponentEnabledSetting(compName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + Log.d(Constants.TAG, "Wifi Receiver is enabled!"); + } else { + pm.setComponentEnabledSetting(compName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + Log.d(Constants.TAG, "Wifi Receiver is disabled!"); + } + } +} 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 15f8a47db..965d15138 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -144,12 +144,13 @@ public class ContactSyncAdapterService extends Service { public static void requestContactsSync() { // if user has disabled automatic sync, do nothing - if (!ContentResolver.getSyncAutomatically( - new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), - ContactsContract.AUTHORITY)) { + boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account + (Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY); + + if (!isSyncEnabled) { return; } - + Bundle extras = new Bundle(); // no need to wait, do it immediately extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, 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 1c59782fc..b71fbada8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -11,11 +11,14 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.PeriodicSync; import android.content.SyncResult; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -35,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.receiver.NetworkReceiver; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -68,7 +72,7 @@ public class KeyserverSyncAdapterService extends Service { private static final String ACTION_IGNORE_TOR = "ignore_tor"; private static final String ACTION_UPDATE_ALL = "update_all"; - private static final String ACTION_SYNC_NOW = "sync_now"; + public static final String ACTION_SYNC_NOW = "sync_now"; private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync"; private static final String ACTION_START_ORBOT = "start_orbot"; private static final String ACTION_CANCEL = "cancel"; @@ -176,8 +180,25 @@ public class KeyserverSyncAdapterService extends Service { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - Log.d(Constants.TAG, "Performing a keyserver sync!"); + Preferences prefs = Preferences.getPreferences(getContext()); + + // for a wifi-ONLY sync + if (prefs.getWifiOnlySync()) { + + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + boolean isNotOnWifi = !(networkInfo.getType() == ConnectivityManager.TYPE_WIFI); + boolean isNotConnected = !(networkInfo.isConnected()); + + // if Wi-Fi connection doesn't exist then receiver is enabled + if (isNotOnWifi && isNotConnected) { + new NetworkReceiver().setWifiReceiverComponent(true, getContext()); + return; + } + } + Log.d(Constants.TAG, "Performing a keyserver sync!"); PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this .getSystemService(Context.POWER_SERVICE); @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 @@ -509,6 +530,10 @@ public class KeyserverSyncAdapterService extends Service { return builder.build(); } + /** + * creates a new sync if one does not exist, or updates an existing sync if the sync interval + * has changed. + */ public static void enableKeyserverSync(Context context) { Account account = KeychainApplication.createAccountIfNecessary(context); @@ -519,12 +544,26 @@ public class KeyserverSyncAdapterService extends Service { ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); - ContentResolver.addPeriodicSync( - account, - Constants.PROVIDER_AUTHORITY, - new Bundle(), - SYNC_INTERVAL - ); + + boolean intervalChanged = false; + boolean syncExists = Preferences.getKeyserverSyncEnabled(context); + + if (syncExists) { + long oldInterval = ContentResolver.getPeriodicSyncs( + account, Constants.PROVIDER_AUTHORITY).get(0).period; + if (oldInterval != SYNC_INTERVAL) { + intervalChanged = true; + } + } + + if (!syncExists || intervalChanged) { + ContentResolver.addPeriodicSync( + account, + Constants.PROVIDER_AUTHORITY, + new Bundle(), + SYNC_INTERVAL + ); + } } private boolean isSyncEnabled() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 472eb3b18..dc892ecc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -356,29 +356,21 @@ public class SaveKeyringParcel implements Parcelable { // The new passphrase to use public final Passphrase mNewPassphrase; - // A new pin to use. Must only contain [0-9]+ - public final Passphrase mNewPin; public ChangeUnlockParcel(Passphrase newPassphrase) { - this(newPassphrase, null); - } - public ChangeUnlockParcel(Passphrase newPassphrase, Passphrase newPin) { - if (newPassphrase == null && newPin == null) { - throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!"); + if (newPassphrase == null) { + throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); } mNewPassphrase = newPassphrase; - mNewPin = newPin; } public ChangeUnlockParcel(Parcel source) { mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader()); - mNewPin = source.readParcelable(Passphrase.class.getClassLoader()); } @Override public void writeToParcel(Parcel destination, int flags) { destination.writeParcelable(mNewPassphrase, flags); - destination.writeParcelable(mNewPin, flags); } @Override @@ -397,9 +389,7 @@ public class SaveKeyringParcel implements Parcelable { }; public String toString() { - return mNewPassphrase != null - ? ("passphrase (" + mNewPassphrase + ")") - : ("pin (" + mNewPin + ")"); + return "passphrase (" + mNewPassphrase + ")"; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 3845e07cb..09149716c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -28,7 +28,8 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; - public static final String EXTRA_KEY_IDS = "extra_key_ids"; + // For sending masterKeyIds to MultiUserIdsFragment to display list of keys + public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 357b445f0..ad39ff43d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -62,58 +62,26 @@ import java.util.ArrayList; import java.util.Date; public class CertifyKeyFragment - extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> - implements LoaderManager.LoaderCallbacks<Cursor> { - - public static final String ARG_CHECK_STATES = "check_states"; + extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> { private CheckBox mUploadKeyCheckbox; - ListView mUserIds; private CertifyKeySpinner mCertifyKeySpinner; - private long[] mPubMasterKeyIds; - - public static final String[] USER_IDS_PROJECTION = new String[]{ - UserPackets._ID, - UserPackets.MASTER_KEY_ID, - UserPackets.USER_ID, - UserPackets.IS_PRIMARY, - UserPackets.IS_REVOKED - }; - private static final int INDEX_MASTER_KEY_ID = 1; - private static final int INDEX_USER_ID = 2; - @SuppressWarnings("unused") - private static final int INDEX_IS_PRIMARY = 3; - @SuppressWarnings("unused") - private static final int INDEX_IS_REVOKED = 4; - - private MultiUserIdsAdapter mUserIdsAdapter; + private MultiUserIdsFragment mMultiUserIdsFragment; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); - if (mPubMasterKeyIds == null) { - Log.e(Constants.TAG, "List of key ids to certify missing!"); - getActivity().finish(); - return; - } - - ArrayList<Boolean> checkedStates; - if (savedInstanceState != null) { - checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES); - // key spinner and the checkbox keep their own state - } else { - checkedStates = null; - + if (savedInstanceState == null) { // preselect certify key id if given long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { try { - CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId); + CachedPublicKeyRing key = (new ProviderHelper(getActivity())) + .getCachedPublicKeyRing(certifyKeyId); if (key.canCertify()) { mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } @@ -121,15 +89,8 @@ public class CertifyKeyFragment Log.e(Constants.TAG, "certify certify check failed", e); } } - } - mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates); - mUserIds.setAdapter(mUserIdsAdapter); - mUserIds.setDividerHeight(0); - - getLoaderManager().initLoader(0, null, this); - OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); if (result != null) { // display result from import @@ -138,21 +99,13 @@ public class CertifyKeyFragment } @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates(); - // no proper parceling method available :( - outState.putSerializable(ARG_CHECK_STATES, states); - } - - @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); - mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mMultiUserIdsFragment = (MultiUserIdsFragment) + getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); // make certify image gray, like action icons ImageView vActionCertifyImage = @@ -184,127 +137,10 @@ public class CertifyKeyFragment } @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - Uri uri = UserPackets.buildUserIdsUri(); - - String selection, ids[]; - { - // generate placeholders and string selection args - ids = new String[mPubMasterKeyIds.length]; - StringBuilder placeholders = new StringBuilder("?"); - for (int i = 0; i < mPubMasterKeyIds.length; i++) { - ids[i] = Long.toString(mPubMasterKeyIds[i]); - if (i != 0) { - placeholders.append(",?"); - } - } - // put together selection string - selection = UserPackets.IS_REVOKED + " = 0" + " AND " - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + " IN (" + placeholders + ")"; - } - - return new CursorLoader(getActivity(), uri, - USER_IDS_PROJECTION, selection, ids, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" - + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC" - ); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - - MatrixCursor matrix = new MatrixCursor(new String[]{ - "_id", "user_data", "grouped" - }) { - @Override - public byte[] getBlob(int column) { - return super.getBlob(column); - } - }; - data.moveToFirst(); - - long lastMasterKeyId = 0; - String lastName = ""; - ArrayList<String> uids = new ArrayList<>(); - - boolean header = true; - - // Iterate over all rows - while (!data.isAfterLast()) { - long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - String userId = data.getString(INDEX_USER_ID); - KeyRing.UserId pieces = KeyRing.splitUserId(userId); - - // Two cases: - - boolean grouped = masterKeyId == lastMasterKeyId; - boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); - // Remember for next loop - lastName = pieces.name; - - Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); - - if (!subGrouped) { - // 1. This name should NOT be grouped with the previous, so we flush the buffer - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - // indicate that we have a header for this masterKeyId - header = false; - - // Now clear the buffer, and add the new user id, for the next round - uids.clear(); - - } - - // 2. This name should be grouped with the previous, just add to buffer - uids.add(userId); - lastMasterKeyId = masterKeyId; - - // If this one wasn't grouped, the next one's gotta be a header - if (!grouped) { - header = true; - } - - // Regardless of the outcome, move to next entry - data.moveToNext(); - - } - - // If there is anything left in the buffer, flush it one last time - if (!uids.isEmpty()) { - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - - } - - mUserIdsAdapter.swapCursor(matrix); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - mUserIdsAdapter.swapCursor(null); - } - - @Override public CertifyActionsParcel createOperationInput() { // Bail out if there is not at least one user id selected - ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); + ArrayList<CertifyAction> certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { Notify.create(getActivity(), "No identities selected!", Notify.Style.ERROR).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index e1d20f2e1..cbf862074 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -278,7 +278,7 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase - saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); + saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -288,7 +288,7 @@ public class CreateKeyFinalFragment extends Fragment { 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) + ? new ChangeUnlockParcel(createKeyActivity.mPassphrase) : null; } String userId = KeyRing.createUserId( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1571a7104..4cbb4724e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -339,8 +339,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring // cache new returned passphrase! mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel( - (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), - null + (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index ca5d20fb9..b2b85ec14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -79,9 +79,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - mEncryptKeyView.setDeletionStyle(TokenCompleteTextView.TokenDeleteStyle.ToString); final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon); mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java new file mode 100644 index 000000000..8ba695cf7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -0,0 +1,223 @@ +package org.sufficientlysecure.keychain.ui; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{ + public static final String ARG_CHECK_STATES = "check_states"; + public static final String EXTRA_KEY_IDS = "extra_key_ids"; + private boolean checkboxVisibility = true; + + ListView mUserIds; + private MultiUserIdsAdapter mUserIdsAdapter; + + private long[] mPubMasterKeyIds; + + public static final String[] USER_IDS_PROJECTION = new String[]{ + KeychainContract.UserPackets._ID, + KeychainContract.UserPackets.MASTER_KEY_ID, + KeychainContract.UserPackets.USER_ID, + KeychainContract.UserPackets.IS_PRIMARY, + KeychainContract.UserPackets.IS_REVOKED + }; + private static final int INDEX_MASTER_KEY_ID = 1; + private static final int INDEX_USER_ID = 2; + @SuppressWarnings("unused") + private static final int INDEX_IS_PRIMARY = 3; + @SuppressWarnings("unused") + private static final int INDEX_IS_REVOKED = 4; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.multi_user_ids_fragment, null); + + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS); + if (mPubMasterKeyIds == null) { + Log.e(Constants.TAG, "List of key ids to certify missing!"); + getActivity().finish(); + return; + } + + ArrayList<Boolean> checkedStates = null; + if (savedInstanceState != null) { + checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES); + } + + mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility); + mUserIds.setAdapter(mUserIdsAdapter); + mUserIds.setDividerHeight(0); + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates(); + // no proper parceling method available :( + outState.putSerializable(ARG_CHECK_STATES, states); + } + + public ArrayList<CertifyActionsParcel.CertifyAction> getSelectedCertifyActions() { + if (!checkboxVisibility) { + throw new AssertionError("Item selection not allowed"); + } + + return mUserIdsAdapter.getSelectedCertifyActions(); + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + Uri uri = KeychainContract.UserPackets.buildUserIdsUri(); + + String selection, ids[]; + { + // generate placeholders and string selection args + ids = new String[mPubMasterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < mPubMasterKeyIds.length; i++) { + ids[i] = Long.toString(mPubMasterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + // put together selection string + selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND " + + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + + " IN (" + placeholders + ")"; + } + + return new CursorLoader(getActivity(), uri, + USER_IDS_PROJECTION, selection, ids, + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC" + + ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC" + ); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + + MatrixCursor matrix = new MatrixCursor(new String[]{ + "_id", "user_data", "grouped" + }) { + @Override + public byte[] getBlob(int column) { + return super.getBlob(column); + } + }; + data.moveToFirst(); + + long lastMasterKeyId = 0; + String lastName = ""; + ArrayList<String> uids = new ArrayList<>(); + + boolean header = true; + + // Iterate over all rows + while (!data.isAfterLast()) { + long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + String userId = data.getString(INDEX_USER_ID); + KeyRing.UserId pieces = KeyRing.splitUserId(userId); + + // Two cases: + + boolean grouped = masterKeyId == lastMasterKeyId; + boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); + // Remember for next loop + lastName = pieces.name; + + Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); + + if (!subGrouped) { + // 1. This name should NOT be grouped with the previous, so we flush the buffer + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + // indicate that we have a header for this masterKeyId + header = false; + + // Now clear the buffer, and add the new user id, for the next round + uids.clear(); + + } + + // 2. This name should be grouped with the previous, just add to buffer + uids.add(userId); + lastMasterKeyId = masterKeyId; + + // If this one wasn't grouped, the next one's gotta be a header + if (!grouped) { + header = true; + } + + // Regardless of the outcome, move to next entry + data.moveToNext(); + + } + + // If there is anything left in the buffer, flush it one last time + if (!uids.isEmpty()) { + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + + } + + mUserIdsAdapter.swapCursor(matrix); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mUserIdsAdapter.swapCursor(null); + } + + public void setCheckboxVisibility(boolean checkboxVisibility) { + this.checkboxVisibility = checkboxVisibility; + } +} 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 ea70cde2a..4fd327c8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.List; - import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -49,6 +47,7 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; @@ -59,6 +58,8 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.util.List; + public class SettingsActivity extends AppCompatPreferenceActivity { public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; @@ -405,7 +406,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } /** - * This fragment shows the keyserver/contacts sync preferences + * This fragment shows the keyserver/wifi-only-sync/contacts sync preferences */ public static class SyncPrefsFragment extends PresetPreferenceFragment { @@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { super.onResume(); // this needs to be done in onResume since the user can change sync values from Android // settings and we need to reflect that change when the user navigates back - AccountManager manager = AccountManager.get(getActivity()); - final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + final Account account = KeychainApplication.createAccountIfNecessary(getActivity()); // for keyserver sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), @@ -441,8 +441,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, final Account account, final String authority) { - boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) - && checkContactsPermission(authority); + // account is null if it could not be created for some reason + boolean syncEnabled = + account != null + && ContentResolver.getSyncAutomatically(account, authority) + && checkContactsPermission(authority); syncCheckBox.setChecked(syncEnabled); setSummary(syncCheckBox, authority, syncEnabled); @@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return false; } } else { + if (account == null) { + // if account could not be created for some reason, + // we can't have our sync + return false; + } // disable syncs ContentResolver.setSyncAutomatically(account, authority, false); // immediately delete any linked contacts diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index f38e4928d..306b022c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -31,10 +31,7 @@ import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.UploadResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity // CryptoOperationHelper.Callback vars private String mKeyserver; - private long mMasterKeyId; private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; @Override @@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity mUploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); + MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment) + getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); + mMultiUserIdsFragment.setCheckboxVisibility(false); + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) .getKeyServers() @@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity return; } - try { - mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "Intent data pointed to bad key!"); - finish(); - return; - } - } @Override @@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity @Override public UploadKeyringParcel createOperationInput() { - return new UploadKeyringParcel(mKeyserver, mMasterKeyId); + long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); + + return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 03fc07936..35c1615c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -469,8 +469,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements // use new passphrase! mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel( - (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), - null + (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); mEditOpHelper.cryptoOperation(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index ce2f2def8..02eae1b2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // let user choose application Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.setType("text/plain"); + sendIntent.setType(Constants.MIME_TYPE_KEYS); // NOTE: Don't use Intent.EXTRA_TEXT to send the key // better send it via a Uri! @@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } private void uploadToKeyserver() { + long keyId; + try { + keyId = new ProviderHelper(getActivity()) + .getCachedPublicKeyRing(mDataUri) + .extractOrGetMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + Notify.create(getActivity(), "key not found", Style.ERROR).show(); + return; + } Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class); uploadIntent.setData(mDataUri); + uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId}); startActivityForResult(uploadIntent, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index fb72a263e..4a68c55fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -333,14 +333,6 @@ public class KeyAdapter extends CursorAdapter { return mUserId.email; } } - - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - @Override - public String toString() { - return " "; - } - } public static String[] getProjectionWith(String[] projection) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java index b91abf076..d247faddc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -39,6 +39,7 @@ import java.util.ArrayList; public class MultiUserIdsAdapter extends CursorAdapter { private LayoutInflater mInflater; private final ArrayList<Boolean> mCheckStates; + private boolean checkboxVisibility = true; public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) { super(context, c, flags); @@ -46,6 +47,11 @@ public class MultiUserIdsAdapter extends CursorAdapter { mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates; } + public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates, boolean checkboxVisibility) { + this(context,c,flags,preselectStates); + this.checkboxVisibility = checkboxVisibility; + } + @Override public Cursor swapCursor(Cursor newCursor) { if (newCursor != null) { @@ -138,6 +144,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { } }); vCheckBox.setClickable(false); + vCheckBox.setVisibility(checkboxVisibility?View.VISIBLE:View.GONE); View vUidBody = view.findViewById(R.id.user_id_body); vUidBody.setClickable(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index beaf68372..063181dfe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -65,6 +66,16 @@ public abstract class BaseActivity extends AppCompatActivity { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home : + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + public static void onResumeChecks(Context context) { KeyserverSyncAdapterService.cancelUpdates(context); // in case user has disabled sync from Android account settings 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 b3d679a0e..5f53845d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -19,20 +19,9 @@ package org.sufficientlysecure.keychain.util; -import java.io.Serializable; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - +import android.accounts.Account; import android.annotation.SuppressLint; +import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.os.Parcel; @@ -42,9 +31,23 @@ import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; +import java.io.Serializable; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + /** * Singleton Implementation of a Preference Helper */ @@ -76,9 +79,8 @@ public class Preferences { /** * 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 + * This allows us to use the xml "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); @@ -302,6 +304,23 @@ public class Preferences { } + /** + * @return true if a periodic sync exists and is set to run automatically, false otherwise + */ + public static boolean getKeyserverSyncEnabled(Context context) { + Account account = KeychainApplication.createAccountIfNecessary(context); + + if (account == null) { + // if the account could not be created for some reason, we can't have a sync + return false; + } + + String authority = Constants.PROVIDER_AUTHORITY; + + return ContentResolver.getSyncAutomatically(account, authority) && + !ContentResolver.getPeriodicSyncs(account, authority).isEmpty(); + } + public CacheTTLPrefs getPassphraseCacheTtl() { Set<String> pref = mSharedPreferences.getStringSet(Constants.Pref.PASSPHRASE_CACHE_TTLS, null); if (pref == null) { @@ -424,6 +443,12 @@ public class Preferences { }; } + // sync preferences + + public boolean getWifiOnlySync() { + return mSharedPreferences.getBoolean(Pref.ENABLE_WIFI_SYNC_ONLY, true); + } + // experimental prefs public boolean getExperimentalEnableWordConfirm() { diff --git a/OpenKeychain/src/main/res/layout/certify_item.xml b/OpenKeychain/src/main/res/layout/certify_item.xml index 71838c2fc..8aff5823e 100644 --- a/OpenKeychain/src/main/res/layout/certify_item.xml +++ b/OpenKeychain/src/main/res/layout/certify_item.xml @@ -13,7 +13,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:clickable="true" - android:layout_marginLeft="8dip" android:layout_marginTop="8dip"/> <LinearLayout android:id="@+id/user_id_body" @@ -21,7 +20,6 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:singleLine="true" - android:layout_marginLeft="8dip" android:layout_marginTop="4dip"> <CheckBox diff --git a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml index 23685ce15..01837240b 100644 --- a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml @@ -22,8 +22,9 @@ android:id="@+id/textView" android:layout_weight="1" /> - <org.sufficientlysecure.keychain.ui.widget.FixedListView - android:id="@+id/view_key_user_ids" + <fragment + android:id="@+id/multi_user_ids_fragment" + android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment" android:layout_width="match_parent" android:layout_height="wrap_content" /> diff --git a/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml b/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml new file mode 100644 index 000000000..560934f50 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<org.sufficientlysecure.keychain.ui.widget.FixedListView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> diff --git a/OpenKeychain/src/main/res/layout/upload_key_activity.xml b/OpenKeychain/src/main/res/layout/upload_key_activity.xml index 6a0be9ce5..6acb22c3a 100644 --- a/OpenKeychain/src/main/res/layout/upload_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/upload_key_activity.xml @@ -41,6 +41,12 @@ android:layout_marginBottom="4dp" android:layout_marginTop="4dp" /> + <fragment + android:id="@+id/multi_user_ids_fragment" + android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <TextView style="@style/SectionHeader" android:layout_width="wrap_content" diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 4ac0037ac..8e29fc00f 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -203,6 +203,7 @@ <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> <string name="label_sync_settings_keyserver_summary_off">"Keys are not automatically updated"</string> + <string name="label_sync_settings_wifi_title">"Sync only on Wi-Fi"</string> <string name="label_sync_settings_contacts_title">"Link keys to contacts"</string> <string name="label_sync_settings_contacts_summary_on">"Link keys to contacts based on names and email addresses. This happens completely offline on your device."</string> <string name="label_sync_settings_contacts_summary_off">"New keys will not be linked to contacts"</string> diff --git a/OpenKeychain/src/main/res/xml/sync_preferences.xml b/OpenKeychain/src/main/res/xml/sync_preferences.xml index de41ff030..600ccc9e8 100644 --- a/OpenKeychain/src/main/res/xml/sync_preferences.xml +++ b/OpenKeychain/src/main/res/xml/sync_preferences.xml @@ -4,6 +4,12 @@ android:persistent="false" android:title="@string/label_sync_settings_keyserver_title"/> <SwitchPreference + android:key="enableWifiSyncOnly" + android:defaultValue="true" + android:persistent="true" + android:dependency="syncKeyserver" + android:title="@string/label_sync_settings_wifi_title"/> + <SwitchPreference android:key="syncContacts" android:persistent="false" android:title="@string/label_sync_settings_contacts_title" /> |