back to using an operation for interactive update (for progress dialog)

This commit is contained in:
Vincent Breitmoser
2018-06-25 16:59:20 +02:00
parent a64d898716
commit 1425f34321
19 changed files with 276 additions and 246 deletions

View File

@@ -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<List<WorkStatus>> getSyncWorkerLiveData() {
WorkManager workManager = WorkManager.getInstance();
return workManager.getStatusesForUniqueWork(UNIQUE_WORK_NAME);
}
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class).build();
workManager.enqueue(workRequest);
}
}

View File

@@ -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<byte[]> staleKeyFingerprints =
keyMetadataDao.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS);
List<ParcelableKeyRing> 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<ParcelableKeyRing> fingerprintListToParcelableKeyRings(List<byte[]> staleKeyFingerprints) {
ArrayList<ParcelableKeyRing> 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<ParcelableKeyRing> 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<ParcelableKeyRing> 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<ParcelableKeyRing> 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();
}
}