diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index fce89989b..f4683edca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -116,6 +116,7 @@ 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/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 14be057de..3cd3353ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -103,9 +103,6 @@ public class KeychainApplication extends Application { TlsCertificatePinning.addPinnedCertificate("keyserver.ubuntu.com", getAssets(), "DigiCertGlobalRootCA.cer"); KeyserverSyncManager.updateKeyserverSyncSchedule(this, Constants.DEBUG_KEYSERVER_SYNC); - if (Constants.DEBUG_KEYSERVER_SYNC) { - KeyserverSyncManager.runSyncNow(); - } TemporaryFileProvider.scheduleCleanupImmediately(); } 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 79dbc1179..9a6ade475 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java @@ -18,18 +18,24 @@ 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 org.sufficientlysecure.keychain.Constants; +import androidx.work.WorkStatus; +import androidx.work.Worker; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -38,7 +44,8 @@ public class KeyserverSyncManager { private static final long SYNC_INTERVAL = 3; private static final TimeUnit SYNC_INTERVAL_UNIT = TimeUnit.DAYS; - private static final String WORK_TAG = "keyserverSync"; + 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); @@ -50,12 +57,14 @@ public class KeyserverSyncManager { Timber.e("WorkManager unavailable!"); return; } - workManager.cancelAllWorkByTag(WORK_TAG); + workManager.cancelAllWorkByTag(PERIODIC_WORK_TAG); if (!prefs.isKeyserverSyncEnabled()) { return; } + /* Periodic syncs can't be unique, so we just use this to launch a uniquely queued worker */ + Builder constraints = new Builder() .setRequiredNetworkType(prefs.getWifiOnlySync() ? NetworkType.UNMETERED : NetworkType.CONNECTED) .setRequiresBatteryNotLow(true); @@ -64,23 +73,45 @@ public class KeyserverSyncManager { } PeriodicWorkRequest workRequest = - new PeriodicWorkRequest.Builder(KeyserverSyncWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT) + new PeriodicWorkRequest.Builder(KeyserverSyncLauncherWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT) .setConstraints(constraints.build()) - .addTag(WORK_TAG) + .addTag(PERIODIC_WORK_TAG) .build(); workManager.enqueue(workRequest); prefs.setKeyserverSyncScheduled(true); } - public static void runSyncNow() { + 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) { WorkManager workManager = WorkManager.getInstance(); if (workManager == null) { Timber.e("WorkManager unavailable!"); return; } - OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class).build(); - workManager.enqueue(workRequest); + 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); } } 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 94b996644..4429c254a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -38,6 +38,9 @@ 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); @@ -57,38 +60,37 @@ public class KeyserverSyncWorker extends Worker { keyWritableRepository = KeyWritableRepository.create(getApplicationContext()); preferences = Preferences.getPreferences(getApplicationContext()); + boolean isForeground = getInputData().getBoolean(DATA_IS_FOREGROUND, false); + boolean isForceUpdate = getInputData().getBoolean(DATA_IS_FORCE, false); + Timber.d("Starting key sync…"); - ImportKeyResult result = updateKeysFromKeyserver(getApplicationContext()); + Progressable notificationProgressable = notificationShowForProgress(isForeground); + ImportKeyResult result = updateKeysFromKeyserver(getApplicationContext(), isForceUpdate, notificationProgressable); return handleUpdateResult(result); } - private ImportKeyResult updateKeysFromKeyserver(Context context) { - long staleKeyThreshold = System.currentTimeMillis() - KEY_STALE_THRESHOLD_MILLIS; + private ImportKeyResult updateKeysFromKeyserver(Context context, boolean isForceUpdate, + Progressable notificationProgressable) { + long staleKeyThreshold = System.currentTimeMillis() - (isForceUpdate ? 0 : KEY_STALE_THRESHOLD_MILLIS); List staleKeyFingerprints = lastUpdateInteractor.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()); + 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(); - try { - Progressable notificationProgressable = notificationShowForProgress(); - ImportKeyResult importKeyResult; - if (preferences.getParcelableProxy().isTorEnabled()) { - importKeyResult = staggeredUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); - } else { - importKeyResult = - directUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel, notificationProgressable); - } - return importKeyResult; - } finally { - notificationRemove(); + 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) { @@ -209,7 +211,7 @@ public class KeyserverSyncWorker extends Worker { return accumulator.getConsolidatedResult(); } - private Progressable notificationShowForProgress() { + private Progressable notificationShowForProgress(boolean isForeground) { final Context context = getApplicationContext(); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -217,39 +219,44 @@ public class KeyserverSyncWorker extends Worker { return null; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = context.getString(R.string.notify_channel_keysync); - NotificationChannel channel = new NotificationChannel( - NotificationChannels.KEYSERVER_SYNC, name, NotificationManager.IMPORTANCE_LOW); - notificationManager.createNotificationChannel(channel); - } + createNotificationChannelsIfNecessary(context, notificationManager); - NotificationCompat.Builder builder = new Builder(context, NotificationChannels.KEYSERVER_SYNC) + NotificationCompat.Builder builder = new Builder(context, isForeground ? + NotificationChannels.KEYSERVER_SYNC_FOREGROUND : 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(NotificationCompat.PRIORITY_LOW) + .setPriority(isForeground ? NotificationCompat.PRIORITY_LOW : NotificationCompat.PRIORITY_MIN) + .setTimeoutAfter(5000) + .setVibrate(null) + .setSound(null) .setProgress(0, 0, true); return new Progressable() { @Override public void setProgress(String message, int current, int total) { - builder.setProgress(total, current, false); - builder.setContentText(context.getString(R.string.notify_content_keysync, current, total)); - notificationManager.notify(NotificationIds.KEYSERVER_SYNC, builder.build()); + setProgress(current, total); } @Override public void setProgress(int resourceId, int current, int total) { - builder.setProgress(total, current, false); - builder.setContentText(context.getString(R.string.notify_content_keysync, current, total)); - notificationManager.notify(NotificationIds.KEYSERVER_SYNC, builder.build()); + setProgress(current, total); } @Override public void setProgress(int current, int total) { + if (total == 0) { + notificationManager.cancel(NotificationIds.KEYSERVER_SYNC); + return; + } + builder.setProgress(total, current, false); - builder.setContentText(context.getString(R.string.notify_content_keysync, current, total)); + if (current == total) { + builder.setContentTitle(context.getString(R.string.notify_title_keysync_finished, total)); + builder.setContentText(null); + } else { + builder.setContentText(context.getString(R.string.notify_content_keysync, current, total)); + } notificationManager.notify(NotificationIds.KEYSERVER_SYNC, builder.build()); } @@ -259,11 +266,20 @@ public class KeyserverSyncWorker extends Worker { }; } - private void notificationRemove() { - NotificationManager notificationManager = - (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager != null) { - notificationManager.cancel(NotificationIds.KEYSERVER_SYNC); + private void createNotificationChannelsIfNecessary(Context context, + NotificationManager notificationManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = context.getString(R.string.notify_channel_keysync); + NotificationChannel channel = new NotificationChannel( + 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); } } 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 acfb1759b..68e4e4844 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.ui; import java.io.IOException; -import java.util.ArrayList; +import java.util.List; import android.animation.ObjectAnimator; import android.app.Activity; @@ -45,6 +45,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ViewAnimator; +import androidx.work.WorkStatus; import com.futuremind.recyclerviewfastscroll.FastScroller; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; @@ -52,24 +53,19 @@ import com.tonicartos.superslim.LayoutManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; 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; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; -import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; @@ -93,11 +89,6 @@ public class KeyListFragment extends RecyclerFragment private FloatingActionsMenu mFab; - // for CryptoOperationHelper import - private ArrayList mKeyList; - private HkpKeyserverAddress mKeyserver; - private CryptoOperationHelper mImportOpHelper; - // Callbacks related to listview and menu events private final ActionMode.Callback mActionCallback = new ActionMode.Callback() { @@ -370,6 +361,7 @@ public class KeyListFragment extends RecyclerFragment menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); + menu.findItem(R.id.menu_key_list_debug_bgsync).setVisible(true); } // Get the searchview @@ -444,6 +436,10 @@ public class KeyListFragment extends RecyclerFragment getActivity().finish(); return true; } + case R.id.menu_key_list_debug_bgsync: { + KeyserverSyncManager.runSyncNow(false, false); + return true; + } case R.id.menu_key_list_debug_bench: { benchmark(); return true; @@ -508,70 +504,8 @@ public class KeyListFragment extends RecyclerFragment } private void updateAllKeys() { - Activity activity = getActivity(); - if (activity == null) { - return; - } - - KeyRepository keyRepository = - KeyRepository.create(getContext()); - Cursor cursor = keyRepository.getContentResolver().query( - KeyRings.buildUnifiedKeyRingsUri(), new String[]{ - KeyRings.FINGERPRINT - }, null, null, null - ); - - if (cursor == null) { - Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR).show(); - return; - } - - ArrayList keyList = new ArrayList<>(); - try { - while (cursor.moveToNext()) { - byte[] fingerprint = cursor.getBlob(0); //fingerprint column is 0 - ParcelableKeyRing keyEntry = ParcelableKeyRing.createFromReference(fingerprint, null, null, null); - keyList.add(keyEntry); - } - mKeyList = keyList; - } finally { - cursor.close(); - } - - // search config - mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); - - CryptoOperationHelper.Callback callback - = new CryptoOperationHelper.Callback() { - - @Override - public ImportKeyringParcel createOperationInput() { - return ImportKeyringParcel.createImportKeyringParcel(mKeyList, mKeyserver); - } - - @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; - } - }; - - mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_updating); - mImportOpHelper.setProgressCancellable(true); - mImportOpHelper.cryptoOperation(); + KeyserverSyncManager.getSyncWorkerLiveData().observe(this, this::onSyncWorkerUpdate); + KeyserverSyncManager.runSyncNow(true, true); } private void benchmark() { @@ -609,10 +543,6 @@ public class KeyListFragment extends RecyclerFragment @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mImportOpHelper != null) { - mImportOpHelper.handleActivityResult(requestCode, resultCode, data); - } - switch (requestCode) { case REQUEST_DELETE: { if (mActionMode != null) { diff --git a/OpenKeychain/src/main/res/menu/key_list.xml b/OpenKeychain/src/main/res/menu/key_list.xml index d694060b5..776242c64 100644 --- a/OpenKeychain/src/main/res/menu/key_list.xml +++ b/OpenKeychain/src/main/res/menu/key_list.xml @@ -37,6 +37,12 @@ android:visible="false" app:showAsAction="never" /> + + View Keyserver update + Keyserver foreground update Updating keys… + Finished updating %d keys Key %d / %d + Started updating all keys… + Key update successful + An error occurred while updating all keys