From 1425f3432126e6e6ac8dbb235b375290dd262a96 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 16:59:20 +0200 Subject: [PATCH] back to using an operation for interactive update (for progress dialog) --- .../keychain/Constants.java | 1 - .../keysync/KeyserverSyncManager.java | 45 +---- .../keychain/keysync/KeyserverSyncWorker.java | 170 ++---------------- .../keychain/operations/BackupOperation.java | 3 +- .../keychain/operations/BaseOperation.java | 9 +- .../operations/BaseReadWriteOperation.java | 10 +- .../keychain/operations/CertifyOperation.java | 3 +- .../keychain/operations/EditKeyOperation.java | 6 +- .../keychain/operations/ImportOperation.java | 6 +- .../keychain/operations/KeySyncOperation.java | 158 ++++++++++++++++ .../keychain/operations/KeySyncParcel.java | 20 +++ .../operations/PromoteKeyOperation.java | 3 +- .../operations/SignEncryptOperation.java | 5 +- .../keychain/operations/UploadOperation.java | 16 +- .../keychain/pgp/PgpKeyOperation.java | 9 +- .../keychain/pgp/PgpSignEncryptOperation.java | 4 +- .../keychain/service/KeychainService.java | 14 +- .../keychain/ui/KeyListFragment.java | 39 +++- OpenKeychain/src/main/res/values/strings.xml | 1 - 19 files changed, 276 insertions(+), 246 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncParcel.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index f4683edca..fce89989b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -116,7 +116,6 @@ public final class Constants { public static final class NotificationChannels { public static final String KEYSERVER_SYNC = "keyserverSync"; - public static final String KEYSERVER_SYNC_FOREGROUND = "keyserverSyncForeground"; } public static final class Pref { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java index 9a6ade475..ada2614bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java @@ -18,24 +18,17 @@ package org.sufficientlysecure.keychain.keysync; -import java.util.List; import java.util.concurrent.TimeUnit; -import android.arch.lifecycle.LiveData; import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.annotation.NonNull; import androidx.work.Constraints.Builder; -import androidx.work.Data; -import androidx.work.ExistingWorkPolicy; import androidx.work.NetworkType; import androidx.work.OneTimeWorkRequest; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; -import androidx.work.WorkStatus; -import androidx.work.Worker; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -45,7 +38,6 @@ public class KeyserverSyncManager { private static final TimeUnit SYNC_INTERVAL_UNIT = TimeUnit.DAYS; private static final String PERIODIC_WORK_TAG = "keyserverSync"; - private static final String UNIQUE_WORK_NAME = "keySync"; public static void updateKeyserverSyncSchedule(Context context, boolean forceReschedule) { Preferences prefs = Preferences.getPreferences(context); @@ -73,7 +65,7 @@ public class KeyserverSyncManager { } PeriodicWorkRequest workRequest = - new PeriodicWorkRequest.Builder(KeyserverSyncLauncherWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT) + new PeriodicWorkRequest.Builder(KeyserverSyncWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT) .setConstraints(constraints.build()) .addTag(PERIODIC_WORK_TAG) .build(); @@ -82,36 +74,9 @@ public class KeyserverSyncManager { prefs.setKeyserverSyncScheduled(true); } - public static class KeyserverSyncLauncherWorker extends Worker { - @NonNull - @Override - public WorkerResult doWork() { - runSyncNow(false, false); - return WorkerResult.SUCCESS; - } - } - - public static void runSyncNow(boolean isForeground, boolean isForceUpdate) { + public static void debugRunSyncNow() { WorkManager workManager = WorkManager.getInstance(); - if (workManager == null) { - Timber.e("WorkManager unavailable!"); - return; - } - - Data workData = new Data.Builder() - .putBoolean(KeyserverSyncWorker.DATA_IS_FOREGROUND, isForeground) - .putBoolean(KeyserverSyncWorker.DATA_IS_FORCE, isForceUpdate) - .build(); - - OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class) - .setInputData(workData) - .build(); - workManager.beginUniqueWork(UNIQUE_WORK_NAME, - isForeground ? ExistingWorkPolicy.REPLACE : ExistingWorkPolicy.KEEP, workRequest).enqueue(); - } - - public static LiveData> getSyncWorkerLiveData() { - WorkManager workManager = WorkManager.getInstance(); - return workManager.getStatusesForUniqueWork(UNIQUE_WORK_NAME); - } + OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class).build(); + workManager.enqueue(workRequest); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java index bbcac9f08..9f935b9d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -1,12 +1,6 @@ package org.sufficientlysecure.keychain.keysync; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; @@ -14,104 +8,39 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; +import android.support.v4.os.CancellationSignal; import androidx.work.Worker; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.NotificationChannels; import org.sufficientlysecure.keychain.Constants.NotificationIds; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; -import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.KeySyncOperation; +import org.sufficientlysecure.keychain.operations.KeySyncParcel; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ResourceUtils; import timber.log.Timber; public class KeyserverSyncWorker extends Worker { - public static final String DATA_IS_FOREGROUND = "foreground"; - public static final String DATA_IS_FORCE = "force"; - - // time since last update after which a key should be updated again, in s - private static final long KEY_STALE_THRESHOLD_MILLIS = - Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toMillis(7); - // Time taken by Orbot before a new circuit is created - private static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS = - Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10); - - private AtomicBoolean cancellationSignal = new AtomicBoolean(false); - private KeyMetadataDao keyMetadataDao; - private KeyWritableRepository keyWritableRepository; - private Preferences preferences; + private CancellationSignal cancellationSignal = new CancellationSignal(); @NonNull @Override public WorkerResult doWork() { - keyMetadataDao = KeyMetadataDao.create(getApplicationContext()); - keyWritableRepository = KeyWritableRepository.create(getApplicationContext()); - preferences = Preferences.getPreferences(getApplicationContext()); - - boolean isForeground = getInputData().getBoolean(DATA_IS_FOREGROUND, false); - boolean isForceUpdate = getInputData().getBoolean(DATA_IS_FORCE, false); + KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(getApplicationContext()); Timber.d("Starting key sync…"); - Progressable notificationProgressable = notificationShowForProgress(isForeground); - ImportKeyResult result = updateKeysFromKeyserver(getApplicationContext(), isForceUpdate, notificationProgressable); + Progressable notificationProgressable = notificationShowForProgress(); + KeySyncOperation keySync = new KeySyncOperation(getApplicationContext(), keyWritableRepository, notificationProgressable, cancellationSignal); + ImportKeyResult result = keySync.execute(KeySyncParcel.createRefreshOutdated(), CryptoInputParcel.createCryptoInputParcel()); return handleUpdateResult(result); } - private ImportKeyResult updateKeysFromKeyserver(Context context, boolean isForceUpdate, - Progressable notificationProgressable) { - long staleKeyThreshold = System.currentTimeMillis() - (isForceUpdate ? 0 : KEY_STALE_THRESHOLD_MILLIS); - List staleKeyFingerprints = - keyMetadataDao.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS); - List staleKeyParcelableKeyRings = fingerprintListToParcelableKeyRings(staleKeyFingerprints); - - if (isStopped()) { // if we've already been cancelled - return new ImportKeyResult(OperationResult.RESULT_CANCELLED, new OperationResult.OperationLog()); - } - - // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync - CryptoInputParcel cryptoInputParcel = CryptoInputParcel.createCryptoInputParcel(); - - ImportKeyResult importKeyResult; - if (preferences.getParcelableProxy().isTorEnabled()) { - importKeyResult = staggeredUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); - } else { - importKeyResult = - directUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel, notificationProgressable); - } - return importKeyResult; - } - - private List fingerprintListToParcelableKeyRings(List staleKeyFingerprints) { - ArrayList result = new ArrayList<>(staleKeyFingerprints.size()); - for (byte[] fingerprint : staleKeyFingerprints) { - Timber.d("Keyserver sync: Updating %s", KeyFormattingUtils.beautifyKeyId(fingerprint)); - result.add(ParcelableKeyRing.createFromReference(fingerprint, null, null, null)); - } - return result; - } - - private ImportKeyResult directUpdate(Context context, List keyList, - CryptoInputParcel cryptoInputParcel, Progressable notificationProgressable) { - Timber.d("Starting normal update"); - ImportOperation importOp = new ImportOperation(context, keyWritableRepository, notificationProgressable); - return importOp.execute( - ImportKeyringParcel.createImportKeyringParcel(keyList, preferences.getPreferredKeyserver()), - cryptoInputParcel - ); - } - /** * Since we're returning START_REDELIVER_INTENT in onStartCommand, we need to remember to call * stopSelf(int) to prevent the Intent from being redelivered if our work is already done @@ -144,74 +73,7 @@ public class KeyserverSyncWorker extends Worker { } } - /** - * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as - * performed by parcimonie. Relevant issue and method at: - * https://github.com/open-keychain/open-keychain/issues/1337 - * - * @return result of the sync - */ - private ImportKeyResult staggeredUpdate(Context context, List keyList, - CryptoInputParcel cryptoInputParcel) { - Timber.d("Starting staggered update"); - // final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); - // we are limiting our randomness to ORBOT_CIRCUIT_TIMEOUT_SECONDS for now - final int WEEK_IN_SECONDS = 0; - - ImportOperation.KeyImportAccumulator accumulator - = new ImportOperation.KeyImportAccumulator(keyList.size(), null); - - // so that the first key can be updated without waiting. This is so that there isn't a - // large gap between a "Start Orbot" notification and the next key update - boolean first = true; - - for (ParcelableKeyRing keyRing : keyList) { - int waitTime; - int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size())); - if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT_SECONDS) { - waitTime = staggeredTime; - } else { - waitTime = ORBOT_CIRCUIT_TIMEOUT_SECONDS - + new Random().nextInt(1 + ORBOT_CIRCUIT_TIMEOUT_SECONDS); - } - - if (first) { - waitTime = 0; - first = false; - } - - Timber.d("Updating key with a wait time of %d seconds", waitTime); - try { - Thread.sleep(waitTime * 1000); - } catch (InterruptedException e) { - Timber.e(e, "Exception during sleep between key updates"); - // skip this one - continue; - } - ArrayList keyWrapper = new ArrayList<>(); - keyWrapper.add(keyRing); - if (isStopped()) { - return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED, - new OperationResult.OperationLog()); - } - ImportKeyResult result = - new ImportOperation(context, keyWritableRepository, null, cancellationSignal) - .execute( - ImportKeyringParcel.createImportKeyringParcel( - keyWrapper, - preferences.getPreferredKeyserver() - ), - cryptoInputParcel - ); - if (result.isPending()) { - return result; - } - accumulator.accumulateKeyImport(result); - } - return accumulator.getConsolidatedResult(); - } - - private Progressable notificationShowForProgress(boolean isForeground) { + private Progressable notificationShowForProgress() { final Context context = getApplicationContext(); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -221,12 +83,11 @@ public class KeyserverSyncWorker extends Worker { createNotificationChannelsIfNecessary(context, notificationManager); - NotificationCompat.Builder builder = new Builder(context, isForeground ? - NotificationChannels.KEYSERVER_SYNC_FOREGROUND : NotificationChannels.KEYSERVER_SYNC) + NotificationCompat.Builder builder = new Builder(context, NotificationChannels.KEYSERVER_SYNC) .setSmallIcon(R.drawable.ic_stat_notify_24dp) .setLargeIcon(ResourceUtils.getDrawableAsNotificationBitmap(context, R.mipmap.ic_launcher)) .setContentTitle(context.getString(R.string.notify_title_keysync)) - .setPriority(isForeground ? NotificationCompat.PRIORITY_LOW : NotificationCompat.PRIORITY_MIN) + .setPriority(NotificationCompat.PRIORITY_MIN) .setTimeoutAfter(5000) .setVibrate(null) .setSound(null) @@ -274,18 +135,11 @@ public class KeyserverSyncWorker extends Worker { NotificationChannels.KEYSERVER_SYNC, name, NotificationManager.IMPORTANCE_MIN); notificationManager.createNotificationChannel(channel); } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = context.getString(R.string.notify_channel_keysync_foreground); - NotificationChannel channel = new NotificationChannel( - NotificationChannels.KEYSERVER_SYNC_FOREGROUND, name, NotificationManager.IMPORTANCE_LOW); - notificationManager.createNotificationChannel(channel); - } } @Override public void onStopped() { super.onStopped(); - cancellationSignal.set(true); + cancellationSignal.cancel(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java index f88731800..7bb28029d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -36,6 +36,7 @@ import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.os.CancellationSignal; import android.text.TextUtils; import org.bouncycastle.bcpg.ArmoredOutputStream; @@ -95,7 +96,7 @@ public class BackupOperation extends BaseOperation { } public BackupOperation(Context context, KeyRepository keyRepository, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, keyRepository, progressable, cancelled); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java index 3e77ef6f0..60e4e6acd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -18,12 +18,11 @@ package org.sufficientlysecure.keychain.operations; -import java.util.concurrent.atomic.AtomicBoolean; - import android.content.Context; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.StringRes; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -38,7 +37,7 @@ public abstract class BaseOperation implements PassphraseC final public Context mContext; final public Progressable mProgressable; - final public AtomicBoolean mCancelled; + final public CancellationSignal mCancelled; final public KeyRepository mKeyRepository; @@ -72,7 +71,7 @@ public abstract class BaseOperation implements PassphraseC } public BaseOperation(Context context, KeyRepository keyRepository, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { mContext = context; mProgressable = progressable; mKeyRepository = keyRepository; @@ -101,7 +100,7 @@ public abstract class BaseOperation implements PassphraseC } protected boolean checkCancelled() { - return mCancelled != null && mCancelled.get(); + return mCancelled != null && mCancelled.isCanceled(); } protected void setPreventCancel () { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java index 155441cdc..dccd3abe5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java @@ -17,16 +17,16 @@ package org.sufficientlysecure.keychain.operations; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.os.Parcelable; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -abstract class BaseReadWriteOperation extends BaseOperation { - final KeyWritableRepository mKeyWritableRepository; +public abstract class BaseReadWriteOperation extends BaseOperation { + protected final KeyWritableRepository mKeyWritableRepository; BaseReadWriteOperation(Context context, KeyWritableRepository databaseInteractor, @@ -36,8 +36,8 @@ abstract class BaseReadWriteOperation extends BaseOperatio mKeyWritableRepository = databaseInteractor; } - BaseReadWriteOperation(Context context, KeyWritableRepository databaseInteractor, - Progressable progressable, AtomicBoolean cancelled) { + protected BaseReadWriteOperation(Context context, KeyWritableRepository databaseInteractor, + Progressable progressable, CancellationSignal cancelled) { super(context, databaseInteractor, progressable, cancelled); mKeyWritableRepository = databaseInteractor; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 92bea7d39..f6981d33a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -64,7 +65,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; public class CertifyOperation extends BaseReadWriteOperation { private final KeyMetadataDao keyMetadataDao; - public CertifyOperation(Context context, KeyWritableRepository keyWritableRepository, Progressable progressable, AtomicBoolean + public CertifyOperation(Context context, KeyWritableRepository keyWritableRepository, Progressable progressable, CancellationSignal cancelled) { super(context, keyWritableRepository, progressable, cancelled); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index ddef5d325..ea1b90a04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -19,10 +19,10 @@ package org.sufficientlysecure.keychain.operations; import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; @@ -35,9 +35,9 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; @@ -61,7 +61,7 @@ public class EditKeyOperation extends BaseReadWriteOperation public EditKeyOperation(Context context, KeyWritableRepository databaseInteractor, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, databaseInteractor, progressable, cancelled); this.keyMetadataDao = KeyMetadataDao.create(context); 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 4ec08b046..bafb9f22c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -29,11 +29,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.FacebookKeyserverClient; @@ -54,8 +54,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeyMetadataDao; +import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -102,7 +102,7 @@ public class ImportOperation extends BaseReadWriteOperation } public ImportOperation(Context context, KeyWritableRepository databaseInteractor, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, databaseInteractor, progressable, cancelled); this.keyMetadataDao = KeyMetadataDao.create(context); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java new file mode 100644 index 000000000..d36692117 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java @@ -0,0 +1,158 @@ +package org.sufficientlysecure.keychain.operations; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; +import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Preferences; +import timber.log.Timber; + + +public class KeySyncOperation extends BaseReadWriteOperation { + // time since last update after which a key should be updated again, in s + private static final long KEY_STALE_THRESHOLD_MILLIS = + Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toMillis(7); + // Time taken by Orbot before a new circuit is created + private static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS = + Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10); + + private final KeyMetadataDao keyMetadataDao; + private final Preferences preferences; + + public KeySyncOperation(Context context, KeyWritableRepository databaseInteractor, + Progressable progressable, CancellationSignal cancellationSignal) { + super(context, databaseInteractor, progressable, cancellationSignal); + + keyMetadataDao = KeyMetadataDao.create(context); + preferences = Preferences.getPreferences(context); + } + + @NonNull + @Override + public ImportKeyResult execute(KeySyncParcel input, CryptoInputParcel cryptoInput) { + long staleKeyThreshold = System.currentTimeMillis() - (input.getRefreshAll() ? 0 : KEY_STALE_THRESHOLD_MILLIS); + List staleKeyFingerprints = + keyMetadataDao.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS); + List staleKeyParcelableKeyRings = fingerprintListToParcelableKeyRings(staleKeyFingerprints); + + if (checkCancelled()) { // if we've already been cancelled + return new ImportKeyResult(OperationResult.RESULT_CANCELLED, new OperationResult.OperationLog()); + } + + // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync + CryptoInputParcel cryptoInputParcel = CryptoInputParcel.createCryptoInputParcel(); + + ImportKeyResult importKeyResult; + if (preferences.getParcelableProxy().isTorEnabled()) { + importKeyResult = staggeredUpdate(staleKeyParcelableKeyRings, cryptoInputParcel); + } else { + importKeyResult = + directUpdate(staleKeyParcelableKeyRings, cryptoInputParcel); + } + return importKeyResult; + } + + private List fingerprintListToParcelableKeyRings(List staleKeyFingerprints) { + ArrayList result = new ArrayList<>(staleKeyFingerprints.size()); + for (byte[] fingerprint : staleKeyFingerprints) { + Timber.d("Keyserver sync: Updating %s", KeyFormattingUtils.beautifyKeyId(fingerprint)); + result.add(ParcelableKeyRing.createFromReference(fingerprint, null, null, null)); + } + return result; + } + + private ImportKeyResult directUpdate(List keyList, + CryptoInputParcel cryptoInputParcel) { + Timber.d("Starting normal update"); + ImportOperation importOp = new ImportOperation(mContext, mKeyWritableRepository, mProgressable, mCancelled); + return importOp.execute( + ImportKeyringParcel.createImportKeyringParcel(keyList, preferences.getPreferredKeyserver()), + cryptoInputParcel + ); + } + + + /** + * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as + * performed by parcimonie. Relevant issue and method at: + * https://github.com/open-keychain/open-keychain/issues/1337 + * + * @return result of the sync + */ + private ImportKeyResult staggeredUpdate(List keyList, + CryptoInputParcel cryptoInputParcel) { + Timber.d("Starting staggered update"); + // final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); + // we are limiting our randomness to ORBOT_CIRCUIT_TIMEOUT_SECONDS for now + final int WEEK_IN_SECONDS = 0; + + ImportOperation.KeyImportAccumulator accumulator + = new ImportOperation.KeyImportAccumulator(keyList.size(), null); + + // so that the first key can be updated without waiting. This is so that there isn't a + // large gap between a "Start Orbot" notification and the next key update + boolean first = true; + + for (ParcelableKeyRing keyRing : keyList) { + int waitTime; + int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size())); + if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT_SECONDS) { + waitTime = staggeredTime; + } else { + waitTime = ORBOT_CIRCUIT_TIMEOUT_SECONDS + + new Random().nextInt(1 + ORBOT_CIRCUIT_TIMEOUT_SECONDS); + } + + if (first) { + waitTime = 0; + first = false; + } + + Timber.d("Updating key with a wait time of %d seconds", waitTime); + try { + Thread.sleep(waitTime * 1000); + } catch (InterruptedException e) { + Timber.e(e, "Exception during sleep between key updates"); + // skip this one + continue; + } + ArrayList keyWrapper = new ArrayList<>(); + keyWrapper.add(keyRing); + if (checkCancelled()) { + return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED, + new OperationResult.OperationLog()); + } + ImportKeyResult result = + new ImportOperation(mContext, mKeyWritableRepository, null, mCancelled) + .execute( + ImportKeyringParcel.createImportKeyringParcel( + keyWrapper, + preferences.getPreferredKeyserver() + ), + cryptoInputParcel + ); + if (result.isPending()) { + return result; + } + accumulator.accumulateKeyImport(result); + } + return accumulator.getConsolidatedResult(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncParcel.java new file mode 100644 index 000000000..007d233b5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncParcel.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.operations; + + +import android.os.Parcelable; + +import com.google.auto.value.AutoValue; + + +@AutoValue +public abstract class KeySyncParcel implements Parcelable { + public abstract boolean getRefreshAll(); + + public static KeySyncParcel createRefreshAll() { + return new AutoValue_KeySyncParcel(true); + } + + public static KeySyncParcel createRefreshOutdated() { + return new AutoValue_KeySyncParcel(false); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index 84f453702..25af114ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -50,7 +51,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; */ public class PromoteKeyOperation extends BaseReadWriteOperation { public PromoteKeyOperation(Context context, KeyWritableRepository databaseInteractor, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, databaseInteractor, progressable, cancelled); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index ef7387e46..734ff795b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -20,17 +20,16 @@ package org.sufficientlysecure.keychain.operations; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -53,7 +52,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler; public class SignEncryptOperation extends BaseOperation { public SignEncryptOperation(Context context, KeyRepository keyRepository, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, keyRepository, progressable, cancelled); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java index f9a428008..37b477655 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java @@ -18,15 +18,21 @@ package org.sufficientlysecure.keychain.operations; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Proxy; + import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.os.CancellationSignal; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient; import org.sufficientlysecure.keychain.keyimport.KeyserverClient.AddKeyException; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.UploadResult; @@ -43,14 +49,8 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import timber.log.Timber; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.Proxy; -import java.util.concurrent.atomic.AtomicBoolean; - /** * An operation class which implements the upload of a single key to a key server. @@ -58,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class UploadOperation extends BaseOperation { public UploadOperation(Context context, KeyRepository keyRepository, - Progressable progressable, AtomicBoolean cancelled) { + Progressable progressable, CancellationSignal cancelled) { super(context, keyRepository, progressable, cancelled); } 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 47fdd57fa..8fd9867cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -33,7 +33,8 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Stack; -import java.util.concurrent.atomic.AtomicBoolean; + +import android.support.v4.os.CancellationSignal; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.nist.NISTNamedCurves; @@ -108,7 +109,7 @@ import timber.log.Timber; public class PgpKeyOperation { private Stack mProgress; - private AtomicBoolean mCancelled; + private CancellationSignal mCancelled; public PgpKeyOperation(Progressable progress) { super(); @@ -118,13 +119,13 @@ public class PgpKeyOperation { } } - public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled) { + public PgpKeyOperation(Progressable progress, CancellationSignal cancelled) { this(progress); mCancelled = cancelled; } private boolean checkCancelled() { - return mCancelled != null && mCancelled.get(); + return mCancelled != null && mCancelled.isCanceled(); } private void subProgressPush(int from, int to) { 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 bf24bebfb..828094d9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -33,11 +33,11 @@ import java.security.SignatureException; import java.util.Collection; import java.util.Date; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.v4.os.CancellationSignal; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; @@ -105,7 +105,7 @@ public class PgpSignEncryptOperation extends BaseOperation mMessenger = new ThreadLocal<>(); @@ -86,16 +87,13 @@ public class KeychainService extends Service implements Progressable { public int onStartCommand(final Intent intent, int flags, int startId) { if (intent.getAction() != null && intent.getAction().equals(ACTION_CANCEL)) { - mActionCanceled.set(true); + mActionCanceled.cancel(); return START_NOT_STICKY; } Runnable actionRunnable = new Runnable() { @Override public void run() { - // We have not been cancelled! (yet) - mActionCanceled.set(false); - Bundle extras = intent.getExtras(); // Set messenger for communication (for this particular thread) @@ -140,6 +138,8 @@ public class KeychainService extends Service implements Progressable { op = new InputDataOperation(outerThis, databaseInteractor, outerThis); } else if (inputParcel instanceof BenchmarkInputParcel) { op = new BenchmarkOperation(outerThis, databaseInteractor, outerThis); + } else if (inputParcel instanceof KeySyncParcel) { + op = new KeySyncOperation(outerThis, databaseInteractor, outerThis, mActionCanceled); } else { throw new AssertionError("Unrecognized input parcel in KeychainService!"); } 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 871a7335b..635919a1c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -32,6 +32,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; +import android.support.v4.os.CancellationSignal; import android.support.v4.view.MenuItemCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -57,9 +58,12 @@ import eu.davidea.flexibleadapter.SelectableAdapter.Mode; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.KeySyncOperation; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.operations.KeySyncParcel; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.provider.KeyRepository; @@ -499,7 +503,7 @@ public class KeyListFragment extends RecyclerFragment callback + = new CryptoOperationHelper.Callback() { + + @Override + public KeySyncParcel createOperationInput() { + return KeySyncParcel.createRefreshAll(); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public void onCryptoOperationCancelled() { + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + CryptoOperationHelper opHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_importing); + opHelper.setProgressCancellable(true); + opHelper.cryptoOperation(); } private void benchmark() { diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 1b3a4aa17..da278d2b2 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -2024,7 +2024,6 @@ View Keyserver update - Keyserver foreground update Updating keys… Finished updating %d keys Key %d / %d